/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ddmuilib.log.event;

import com.android.ddmlib.log.EventContainer;
import com.android.ddmlib.log.EventLogParser;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.SimpleTimePeriod;
import org.jfree.data.time.TimePeriodValues;
import org.jfree.data.time.TimePeriodValuesCollection;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

public class DisplaySyncHistogram extends SyncCommon {

    Map<SimpleTimePeriod, Integer> mTimePeriodMap[];

    // Information to graph for each authority
    private TimePeriodValues mDatasetsSyncHist[];

    public DisplaySyncHistogram(String name) {
        super(name);
    }

    /**
     * Creates the UI for the event display.
     * @param parent the parent composite.
     * @param logParser the current log parser.
     * @return the created control (which may have children).
     */
    @Override
    public Control createComposite(final Composite parent, EventLogParser logParser,
            final ILogColumnListener listener) {
        Control composite = createCompositeChart(parent, logParser, "Sync Histogram");
        resetUI();
        return composite;
    }

    /**
     * Resets the display.
     */
    @Override
    void resetUI() {
        super.resetUI();
        XYPlot xyPlot = mChart.getXYPlot();

        AbstractXYItemRenderer br = new XYBarRenderer();
        mDatasetsSyncHist = new TimePeriodValues[NUM_AUTHS+1];

        @SuppressWarnings("unchecked")
        Map<SimpleTimePeriod, Integer> mTimePeriodMapTmp[] = new HashMap[NUM_AUTHS + 1];
        mTimePeriodMap = mTimePeriodMapTmp;

        TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection();
        xyPlot.setDataset(tpvc);
        xyPlot.setRenderer(br);

        for (int i = 0; i < NUM_AUTHS + 1; i++) {
            br.setSeriesPaint(i, AUTH_COLORS[i]);
            mDatasetsSyncHist[i] = new TimePeriodValues(AUTH_NAMES[i]);
            tpvc.addSeries(mDatasetsSyncHist[i]);
            mTimePeriodMap[i] = new HashMap<SimpleTimePeriod, Integer>();

        }
    }

    /**
     * Callback to process a sync event.
     *
     * @param event      The sync event
     * @param startTime Start time (ms) of events
     * @param stopTime Stop time (ms) of events
     * @param details Details associated with the event.
     * @param newEvent True if this event is a new sync event.  False if this event
     * @param syncSource
     */
    @Override
    void processSyncEvent(EventContainer event, int auth, long startTime, long stopTime,
            String details, boolean newEvent, int syncSource) {
        if (newEvent) {
            if (details.indexOf('x') >= 0 || details.indexOf('X') >= 0) {
                auth = ERRORS;
            }
            double delta = (stopTime - startTime) * 100. / 1000 / 3600; // Percent of hour
            addHistEvent(0, auth, delta);
        } else {
            // sync_details arrived for an event that has already been graphed.
            if (details.indexOf('x') >= 0 || details.indexOf('X') >= 0) {
                // Item turns out to be in error, so transfer time from old auth to error.
                double delta = (stopTime - startTime) * 100. / 1000 / 3600; // Percent of hour
                addHistEvent(0, auth, -delta);
                addHistEvent(0, ERRORS, delta);
            }
        }
    }

    /**
     * Helper to add an event to the data series.
     * Also updates error series if appropriate (x or X in details).
     * @param stopTime Time event ends
     * @param auth Sync authority
     * @param value Value to graph for event
     */
    private void addHistEvent(long stopTime, int auth, double value) {
        SimpleTimePeriod hour = getTimePeriod(stopTime, mHistWidth);

        // Loop over all datasets to do the stacking.
        for (int i = auth; i <= ERRORS; i++) {
            addToPeriod(mDatasetsSyncHist, i, hour, value);
        }
    }

    private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period,
            double value) {
        int index;
        if (mTimePeriodMap[auth].containsKey(period)) {
            index = mTimePeriodMap[auth].get(period);
            double oldValue = tpv[auth].getValue(index).doubleValue();
            tpv[auth].update(index, oldValue + value);
        } else {
            index = tpv[auth].getItemCount();
            mTimePeriodMap[auth].put(period, index);
            tpv[auth].add(period, value);
        }
    }

    /**
     * Creates a multiple-hour time period for the histogram.
     * @param time Time in milliseconds.
     * @param numHoursWide: should divide into a day.
     * @return SimpleTimePeriod covering the number of hours and containing time.
     */
    private SimpleTimePeriod getTimePeriod(long time, long numHoursWide) {
        Date date = new Date(time);
        TimeZone zone = RegularTimePeriod.DEFAULT_TIME_ZONE;
        Calendar calendar = Calendar.getInstance(zone);
        calendar.setTime(date);
        long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) +
                calendar.get(Calendar.DAY_OF_YEAR) * 24;
        int year = calendar.get(Calendar.YEAR);
        hoursOfYear = (hoursOfYear / numHoursWide) * numHoursWide;
        calendar.clear();
        calendar.set(year, 0, 1, 0, 0); // Jan 1
        long start = calendar.getTimeInMillis() + hoursOfYear * 3600 * 1000;
        return new SimpleTimePeriod(start, start + numHoursWide * 3600 * 1000);
    }

    /**
     * Gets display type
     *
     * @return display type as an integer
     */
    @Override
    int getDisplayType() {
        return DISPLAY_TYPE_SYNC_HIST;
    }
}
