/*--------------------------------------------------------------*
  Copyright (C) 2006-2015 OpenSim Ltd.

  This file is distributed WITHOUT ANY WARRANTY. See the file
  'License' for details on this and other legal matters.
*--------------------------------------------------------------*/

package org.omnetpp.scave.charting;

import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_AXIS_LABEL_FONT;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_AXIS_TITLE_FONT;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_BAR_BASELINE;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_GRID;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_GRID_COLOR;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_HIST_BAR;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_HIST_COLOR;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_HIST_CUMULATIVE;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_HIST_DENSITY;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_SHOW_OVERFLOW_CELL;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_AXIS_SHOW_LABELS;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_AXIS_SHOW_TITLE;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_X_AXIS_TITLE;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_LOGARITHMIC;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_SHOW_LABELS;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_SHOW_TITLE;
import static org.omnetpp.scave.charting.properties.PlotProperty.PROP_Y_AXIS_TITLE;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.omnetpp.common.canvas.ICoordsMapping;
import org.omnetpp.common.canvas.RectangularArea;
import org.omnetpp.common.util.Converter;
import org.omnetpp.common.util.GraphicsUtils;
import org.omnetpp.scave.charting.dataset.IDataset;
import org.omnetpp.scave.charting.dataset.IHistogramDataset;
import org.omnetpp.scave.charting.properties.PlotProperty;
import org.omnetpp.scave.charting.properties.PlotProperty.HistogramBar;
import org.omnetpp.scave.charting.properties.PlotProperty.ShowGrid;
import org.omnetpp.scave.python.HistogramDataset;

/**
 * Histogram plot widget.
 */
public class HistogramPlot extends PlotBase {
    //private static final boolean debug = false;

    private static final PlotProperty[] HISTOGRAMPLOT_PROPERTIES = ArrayUtils.addAll(PLOTBASE_PROPERTIES, new PlotProperty[] {
            PROP_X_AXIS_TITLE,
            PROP_Y_AXIS_TITLE,
            PROP_GRID,
            PROP_GRID_COLOR,
            PROP_X_AXIS_SHOW_TITLE,
            PROP_X_AXIS_SHOW_LABELS,
            PROP_Y_AXIS_SHOW_TITLE,
            PROP_Y_AXIS_SHOW_LABELS,
            PROP_AXIS_TITLE_FONT,
            PROP_AXIS_LABEL_FONT,
            PROP_HIST_BAR,
            PROP_SHOW_OVERFLOW_CELL,
            PROP_BAR_BASELINE,
            PROP_HIST_COLOR,
            PROP_HIST_CUMULATIVE,
            PROP_HIST_DENSITY,
            PROP_Y_AXIS_LOGARITHMIC,
    });

    private IHistogramDataset dataset = IHistogramDataset.EMPTY;
    private LinearAxis xAxis = new LinearAxis(false, false, false);
    private LinearAxis yAxis = new LinearAxis(true, false, false);
    private Histograms histograms;

    private RGB defaultHistogramColor;
    private boolean defaultHistogramCumulative;
    private boolean defaultHistogramDensity;
    private PlotProperty.HistogramBar defaultBarType;

    class HistogramProperties {
        Boolean cumulative;
        Boolean density;
        RGB color;
        HistogramBar drawStyle;

        public boolean getEffectiveCumulative() {
            return cumulative != null ? cumulative : defaultHistogramCumulative;
        }

        public boolean getEffectiveDensity() {
            return density != null ? density : defaultHistogramDensity;
        }

        public Color getEffectiveColor() {
            return new Color(Display.getCurrent(), color != null ? color : defaultHistogramColor);
        }
    }

    private Map<String,HistogramProperties> histogramProperties = new HashMap<>();

    public HistogramPlot(Composite parent, int style) {
        super(parent, style);
        histograms = new Histograms(this);
        new Tooltip(this);

        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                setSelection(new IPlotSelection() {
                });
            }
        });

        resetProperties();
    }

    @Override
    protected double transformX(double x) {
        return xAxis.transform(x);
    }

    @Override
    protected double transformY(double y) {
        return yAxis.transform(y);
    }

    @Override
    protected double inverseTransformX(double x) {
        return xAxis.inverseTransform(x);
    }

    @Override
    protected double inverseTransformY(double y) {
        return yAxis.inverseTransform(y);
    }

    /*=============================================
     *               Properties
     *=============================================*/

    public PlotProperty[] getProperties() {
        return HISTOGRAMPLOT_PROPERTIES;
    }

    @Override
    public void setProperty(PlotProperty prop, String value) {
        switch (prop) {
        case PROP_X_AXIS_TITLE: setXAxisTitle(value); break;
        case PROP_Y_AXIS_TITLE: setYAxisTitle(value); break;
        case PROP_GRID: setShowGrid(Converter.stringToEnum(value, ShowGrid.class)); break;
        case PROP_GRID_COLOR: setGridColor(Converter.stringToRGB(value)); break;
        case PROP_X_AXIS_SHOW_TITLE:  setXAxisShowTitle(Converter.stringToBoolean(value)); break;
        case PROP_X_AXIS_SHOW_LABELS:  setXAxisShowLabels(Converter.stringToBoolean(value)); break;
        case PROP_Y_AXIS_SHOW_TITLE:  setYAxisShowTitle(Converter.stringToBoolean(value)); break;
        case PROP_Y_AXIS_SHOW_LABELS:  setYAxisShowLabels(Converter.stringToBoolean(value)); break;
        case PROP_AXIS_TITLE_FONT: setAxisTitleFont(Converter.stringToSwtfont(value)); break;
        case PROP_AXIS_LABEL_FONT: setTickLabelFont(Converter.stringToSwtfont(value)); break;
        case PROP_HIST_BAR: setBarType(Converter.stringToEnum(value, HistogramBar.class)); break;
        case PROP_HIST_COLOR: setHistogramColor(Converter.stringToRGB(value)); break;
        case PROP_HIST_CUMULATIVE: setHistogramCumulative(Converter.stringToBoolean(value)); break;
        case PROP_HIST_DENSITY: setHistogramDensity(Converter.stringToBoolean(value)); break;
        case PROP_SHOW_OVERFLOW_CELL: setShowOverflowCell(Converter.stringToBoolean(value)); break;
        case PROP_BAR_BASELINE: setBarBaseline(Converter.stringToDouble(value)); break;
        case PROP_Y_AXIS_LOGARITHMIC: setLogarithmicY(Converter.stringToBoolean(value)); break;
        default: super.setProperty(prop, value);
        }
    }

    @Override
    public void setProperty(PlotProperty prop, String key, String value) {
        switch (prop) {
        case PROP_HIST_COLOR: setHistogramColor(key, Converter.stringToOptionalRGB(value)); break;
        case PROP_HIST_CUMULATIVE: setHistogramCumulative(key, Converter.stringToOptionalBoolean(value)); break;
        case PROP_HIST_DENSITY: setHistogramDensity(key, Converter.stringToOptionalBoolean(value)); break;
        case PROP_HIST_BAR: setBarType(key, Converter.stringToOptionalEnum(value, HistogramBar.class)); break;
        case PROP_BAR_BASELINE: /* TODO? */ break;
        default: super.setProperty(prop, key, value);
        }
    }

    @Override
    public void clear() {
        super.clear();
        histogramProperties.clear();
        setDataset(new HistogramDataset());
    }

    public void setXAxisTitle(String value) {
        Assert.isNotNull(value);
        xAxis.setTitle(value);
        chartChanged();
    }

    public void setYAxisTitle(String value) {
        Assert.isNotNull(value);
        yAxis.setTitle(value);
        chartChanged();
    }

    public void setAxisTitleFont(Font value) {
        Assert.isNotNull(value);
        xAxis.setTitleFont(value);
        yAxis.setTitleFont(value);
        chartChanged();
    }

    public void setTickLabelFont(Font font) {
        Assert.isNotNull(font);
        xAxis.setTickFont(font);
        yAxis.setTickFont(font);
        chartChanged();
    }

    public void setShowGrid(ShowGrid value) {
        Assert.isNotNull(value);
        xAxis.setShowGrid(value);
        yAxis.setShowGrid(value);
        chartChanged();
    }

    public void setGridColor(RGB color) {
        Assert.isNotNull(color);
        xAxis.setGridColor(color);
        yAxis.setGridColor(color);
        chartChanged();
    }

    public void setXAxisShowTitle(boolean show) {
        xAxis.setDrawTitle(show);
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    public void setXAxisShowLabels(boolean show) {
        xAxis.setDrawTickLabels(show);
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    public void setYAxisShowTitle(boolean show) {
        yAxis.setDrawTitle(show);
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    public void setYAxisShowLabels(boolean show) {
        yAxis.setDrawTickLabels(show);
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    public void setHistogramColor(RGB color) {
        Assert.isNotNull(color);
        defaultHistogramColor = color;
        updateLegends();
        chartChanged();
    }

    public void setHistogramCumulative(boolean value) {
        defaultHistogramCumulative = value;
        chartArea = calculatePlotArea();
        updateArea();
        updateLegends();
        chartChanged();
    }

    public void setHistogramDensity(boolean value) {
        defaultHistogramDensity = value;
        chartArea = calculatePlotArea();
        updateArea();
        updateLegends();
        chartChanged();
    }

    public void setBarType(PlotProperty.HistogramBar barType) {
        defaultBarType = barType;
        updateLegends();
        chartChanged();
    }


    public void setHistogramCumulative(String key, Boolean value) {
        HistogramProperties histProps = getOrCreateHistogramProperties(key);
        histProps.cumulative = value;
        chartArea = calculatePlotArea();
        updateArea();
        updateLegends();
        chartChanged();
    }

    public void setHistogramDensity(String key, boolean value) {
        HistogramProperties histProps = getOrCreateHistogramProperties(key);
        histProps.density = value;
        chartArea = calculatePlotArea();
        updateArea();
        updateLegends();
        chartChanged();
    }

    public void setBarType(String key, PlotProperty.HistogramBar barType) {
        HistogramProperties histProps = getOrCreateHistogramProperties(key);
        histProps.drawStyle = barType;
        chartArea = calculatePlotArea();
        updateLegends();
        chartChanged();
    }

    public void setShowOverflowCell(boolean value) {
        histograms.showOverflowCell = value;
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }


    public void setBarBaseline(double value) {
        histograms.baseline = value;
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    public void setLogarithmicY(boolean value) {
        yAxis.setLogarithmic(value);
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    public RGB getHistogramColor(String key) {
        HistogramProperties histProps = histogramProperties.get(key);
        RGB color = (histProps == null || histProps.color == null) ? defaultHistogramColor : histProps.color;
        return color;
    }

    public PlotProperty.HistogramBar getBarType(String key) {
        HistogramProperties histProps = histogramProperties.get(key);
        PlotProperty.HistogramBar barType = (histProps == null || histProps.drawStyle == null) ? defaultBarType: histProps.drawStyle;
        return barType;
    }

    public void setHistogramColor(String key, RGB color) {
        Assert.isNotNull(color);
        HistogramProperties histProps = getOrCreateHistogramProperties(key);
        histProps.color = color;
        updateLegends();
        chartChanged();
    }

    protected HistogramProperties getOrCreateHistogramProperties(String key) {
        HistogramProperties properties = histogramProperties.get(key);
        if (properties == null) {
            properties = new HistogramProperties();
            histogramProperties.put(key, properties);
        }
        return properties;
    }

    @Override
    void doSetDataset(IDataset dataset) {
        if (dataset != null && !(dataset instanceof IHistogramDataset))
            throw new IllegalArgumentException("must be an IHistogramDataset");
        this.dataset = (IHistogramDataset)dataset;
        updateLegends();
        chartArea = calculatePlotArea();
        updateArea();
        chartChanged();
    }

    @Override
    protected void updateLegend(ILegend legend) {
        histograms.updateLegend(legend);
    }

    IHistogramDataset getDataset() {
        return dataset;
    }

    @Override
    String getHoverHtmlText(int x, int y) {
        if (histograms.getArea().contains(x, y))
            return histograms.getTooltipText(x, y);
        else
            return null;
    }

    @Override
    protected RectangularArea calculatePlotArea() {
        return histograms.calculatePlotArea();
    }

    Rectangle mainArea; // containing plots and axes
    Insets axesInsets; // space occupied by axes

    @Override
    protected Rectangle doLayoutChart(Graphics graphics, int pass) {
        ICoordsMapping coordsMapping = getOptimizedCoordinateMapper();
        if (pass == 1) {
            // Calculate space occupied by title and legend and set insets accordingly
            Rectangle area = new Rectangle(getClientArea());
            Rectangle remaining = legendTooltip.layout(graphics, area);
            remaining = title.layout(graphics, area);
            remaining = legend.layout(graphics, remaining, pass);

            mainArea = remaining.getCopy();
            axesInsets = xAxis.layout(graphics, mainArea, new Insets(), coordsMapping, pass);
            axesInsets = yAxis.layout(graphics, mainArea, axesInsets, coordsMapping, pass);

            // tentative plotArea calculation (y axis ticks width missing from the picture yet)
            Rectangle plotArea = mainArea.getCopy().shrink(axesInsets);
            return plotArea;
        }
        else if (pass == 2) {
            // now the coordinate mapping is set up, so the y axis knows what tick labels
            // will appear, and can calculate the occupied space from the longest tick label.
            yAxis.layout(graphics, mainArea, axesInsets, coordsMapping, pass);
            xAxis.layout(graphics, mainArea, axesInsets, coordsMapping, pass);
            Rectangle remaining = mainArea.getCopy().shrink(axesInsets);
            Rectangle plotArea = histograms.layout(graphics, remaining);
            legend.layout(graphics, plotArea, pass);
            //FIXME how to handle it when plotArea.height/width comes out negative??
            return plotArea;
        }
        return null;
    }

    @Override
    protected void doPaintCachableLayer(Graphics graphics, ICoordsMapping coordsMapping) {
        graphics.fillRectangle(GraphicsUtils.getClip(graphics));
        yAxis.drawGrid(graphics, coordsMapping);
        histograms.draw(graphics, coordsMapping);
    }

    @Override
    protected void doPaintNoncachableLayer(Graphics graphics, ICoordsMapping coordsMapping) {
        int highlightedItem = legend.getHighlightedItem();

        if (highlightedItem >= 0) {
            graphics.setAlpha(veilAlpha);
            graphics.setBackgroundColor(backgroundColor);
            graphics.fillRectangle(getViewportRectangle());
            graphics.setAlpha(255);
            yAxis.drawGrid(graphics, coordsMapping);

            if (legend.isItemEnabled(highlightedItem))
                histograms.drawSingle(graphics, coordsMapping, highlightedItem);
        }

        graphics.setAlpha(255);
        graphics.setLineWidth(1);
        paintInsets(graphics);
        title.draw(graphics);
        legend.draw(graphics);
        xAxis.drawAxis(graphics, coordsMapping);
        yAxis.drawAxis(graphics, coordsMapping);
        legendTooltip.draw(graphics);
        drawStatusText(graphics);
        drawRubberband(graphics);
    }

    @Override
    public void setDisplayAxesDetails(boolean value) {
        xAxis.setDrawTickLabels(value);
        xAxis.setDrawTitle(value);
        yAxis.setDrawTickLabels(value);
        yAxis.setDrawTitle(value);
        chartChanged();
    }
}
