package vismon.plot;

import vismon.data.Indicator;
import vismon.data.Scenario;
import vismon.data.Session;
import vismon.gui.OptionsPanel;
import vismon.gui.TradeoffsPanel;
import vismon.util.Constants;
import vismon.util.Util;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.util.HashMap;
import java.util.Map;

public class MultipodesScenario extends JPanel {

    private final double podRadiusProportion = 0.5;
    private final double holeRadiusProportion = 0.33;
    private final double angelDif = Math.PI/30;

    private int x;
    private int y;
    private int w;
    private int h;
    private int plotLength;
    private double podLength;

    private int xCentre;
    private int yCentre;

    private Scenario scenario;

    private Map<Indicator, Double> scenarioAngles;

    public MultipodesScenario(Scenario scenario) {
        this.scenario = scenario;

        scenarioAngles = new HashMap<Indicator, Double>();

        x = 0;
        y = 0;
    }

    @Override
    public void paint(Graphics g) {
        if (!scenario.isVisible())
            return;

        w = this.getWidth();
        h = this.getHeight();

        plotLength = w;
        if (w > h)
            plotLength = h;

        drawPods((Graphics2D) g);
    }

    private void drawPods(Graphics2D g) {
        int indicatorNum = Indicator.numOfVisibleIndicators;
        xCentre = x + (int) (0.5 * w);
        yCentre = y + (int) (0.5 * h);

        double angle = 2 * Math.PI / indicatorNum;
        double podRadius = podRadiusProportion * plotLength;
        double holeRadius = holeRadiusProportion * podRadius;
        podLength = (podRadius - holeRadius);

        int[] basePolygonXCoords = new int[indicatorNum];
        int[] basePolygonYCoords = new int[indicatorNum];
        for (int m = 0; m < indicatorNum; ++m) {
            basePolygonXCoords[m] = xCentre + (int) (holeRadius * Math.cos(m * angle - 0.5 * angle));
            basePolygonYCoords[m] = yCentre + (int) (holeRadius * Math.sin(m * angle - 0.5 * angle));
        }

        int index = 0;
        for (int i = 0; i < Session.getIndicatorNum(); ++i) {
            Indicator ithIndicator = Session.getIndicator(i);
            if (!ithIndicator.isVisible())
                continue;

            double ownAngle = index * 2 * Math.PI / indicatorNum;
            int nextIndex = (index + 1) % indicatorNum;

            //DRAWING THE LINES OF EACH POD
            //drawPodBorderLines(g, basePolygonXCoords, basePolygonYCoords, ithIndicator, ownAngle, index, nextIndex);

            if (TradeoffsPanel.uncertainty == TradeoffsPanel.NO_UNCERTAINTY ||
                    TradeoffsPanel.uncertainty == TradeoffsPanel.ERROR_BAR) {
                drawAveragePod(g, basePolygonXCoords, basePolygonYCoords, ithIndicator, ownAngle, index, nextIndex);

                if (TradeoffsPanel.uncertainty == TradeoffsPanel.ERROR_BAR)
                    drawErrorBar(g, basePolygonXCoords, basePolygonYCoords, ithIndicator, ownAngle, index, nextIndex);
            } else if (TradeoffsPanel.uncertainty == TradeoffsPanel.BOX_PLOT) {
                drawBoxPlot(g, basePolygonXCoords, basePolygonYCoords, ithIndicator, ownAngle, index, nextIndex);
            } else {
                drawDistribution(g, basePolygonXCoords, basePolygonYCoords, ithIndicator, ownAngle, index, nextIndex);
            }
            //DRAWING THE LINES OF EACH POD
            drawPodBorderLines(g, basePolygonXCoords, basePolygonYCoords, ithIndicator, ownAngle, index, nextIndex);
            ++index;
        }
    }

    private void drawPodBorderLines(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int i, int nextIndex) {
        int outerXCoords0 = basePolygonXCoords[i] + (int) (podLength * Math.cos(ownAngle));
        int outerYCoords0 = basePolygonYCoords[i] + (int) (podLength * Math.sin(ownAngle));
        int outerXCoords1 = basePolygonXCoords[nextIndex] + (int) (podLength * Math.cos(ownAngle));
        int outerYCoords1 = basePolygonYCoords[nextIndex] + (int) (podLength * Math.sin(ownAngle));

        g.setColor(Color.LIGHT_GRAY);
        if (TradeoffsPanel.sortByIndicator != null && indicator.equals(TradeoffsPanel.sortByIndicator)) {
            g.setColor(Constants.MAIN_COLOR);
            g.setStroke(new BasicStroke(2.0f));
        }
        g.drawLine(basePolygonXCoords[i], basePolygonYCoords[i], outerXCoords0, outerYCoords0);
        g.drawLine(basePolygonXCoords[nextIndex], basePolygonYCoords[nextIndex], outerXCoords1, outerYCoords1);
        g.setStroke(new BasicStroke(1.0f));
    }

    private void drawAveragePod(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int index, int nextIndex) {
        double value = indicator.getMin();
        double averageValue = scenario.getAverageOrMedianValue(indicator, OptionsPanel.contoursBasedOn);

        g.setColor(scenario.getColor());
        fillPod(g, basePolygonXCoords, basePolygonYCoords, indicator, ownAngle, index, nextIndex, value, averageValue);
        g.drawLine(basePolygonXCoords[index], basePolygonYCoords[index], basePolygonXCoords[nextIndex], basePolygonYCoords[nextIndex]);
    }

    private void drawErrorBar(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int index, int nextIndex) {
        double[] errors = scenario.getErrorBar(indicator, Session.getErrorBarPercentage());

        g.setStroke(new BasicStroke(2.0f));
        g.setColor(Constants.MAIN_COLOR);
        drawMiddleLine(g, basePolygonXCoords, basePolygonYCoords, indicator, ownAngle, index, nextIndex, errors[0], errors[1]);
        g.setStroke(new BasicStroke(1.0f));
    }

    private void drawBoxPlot(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int index, int nextIndex) {
        double scnMin = scenario.getMinValue(indicator);
        double scnMax = scenario.getMaxValue(indicator);
        double scnAvg = scenario.getAverageValue(indicator);
        double scnMed = scenario.getMedianValue(indicator);
        double[] scnQuar = scenario.getQuartiles(indicator);

        double min = indicator.getMin();
        double max = indicator.getMax();
        double portionAvg = (scnAvg - min) / (max - min);
        double portionMed = (scnMed - min) / (max - min);

        // filling the pod from the first quarlite to the second quartile
        g.setColor(scenario.getColor());
        fillPod(g, basePolygonXCoords, basePolygonYCoords, indicator, ownAngle, index, nextIndex, scnQuar[0], scnQuar[1]);

        // draw a line from scenanioMin to scenarioMax
        g.setStroke(new BasicStroke(2.0f));
        g.setColor(Constants.MAIN_COLOR);
        drawMiddleLine(g, basePolygonXCoords, basePolygonYCoords, indicator, ownAngle, index, nextIndex, scnMin, scnMax);
        g.setStroke(new BasicStroke(1.0f));

        // indicating where scenarioAverage is
        int xAvgIndexLocal = basePolygonXCoords[index] + (int) (portionAvg * podLength * Math.cos(ownAngle));
        int yAvgIndexLocal = basePolygonYCoords[index] + (int) (portionAvg * podLength * Math.sin(ownAngle));
        int xAvgNextIndexLocal = basePolygonXCoords[nextIndex] + (int) (portionAvg * podLength * Math.cos(ownAngle));
        int yAvgNextIndexLocal = basePolygonYCoords[nextIndex] + (int) (portionAvg * podLength * Math.sin(ownAngle));
        g.drawLine(xAvgIndexLocal, yAvgIndexLocal, xAvgNextIndexLocal, yAvgNextIndexLocal);

        // indicating where scenarioMedian is
        int xCommon = (int) (0.5 * (basePolygonXCoords[index] + basePolygonXCoords[nextIndex]));
        int xMedLocal = xCommon + (int) (portionMed * podLength * Math.cos(ownAngle));
        int yCommon = (int) (0.5 * (basePolygonYCoords[index] + basePolygonYCoords[nextIndex]));
        int yMedLocal = yCommon + (int) (portionMed * podLength * Math.sin(ownAngle));
        g.setColor(Constants.MAIN_COLOR);
        g.fillOval(xMedLocal - 3, yMedLocal - 3, 6, 6);
    }

    private void drawDistribution(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int index, int nextIndex) {
        double min = indicator.getMin();
        double max = indicator.getMax();

        java.util.List<Double> pdfValues = scenario.getPdfValues().get(indicator);
        double maxValue = scenario.getPdfMax().get(indicator);
        int binNum = pdfValues.size();
        double[] scenarioMinMax = scenario.getErrorBar(indicator, 100);
        for (int j = 0; j < binNum-1; ++j) {
            //int grayScale = (int)(255*(1-pdfValues.get(j)/maxValue));
            //Color color = new Color(grayScale, grayScale, grayScale);

            double grayScale = 1.0 - pdfValues.get(j) / maxValue;
            Color color = Util.getBrighterColor(scenario.getColor(), grayScale);

            double podMinValue = scenarioMinMax[0] + (j*1.0/binNum) * (scenarioMinMax[1]-scenarioMinMax[0]);
            double podMaxValue = scenarioMinMax[0] + ((j+1)*1.0/binNum) * (scenarioMinMax[1]-scenarioMinMax[0]);

            double portion1 = (podMinValue - min)/(max - min);
            double portion2 = (podMaxValue - min)/(max - min);

            int[] coloredPodXCoords = new int[4];
            int[] coloredPodYCoords = new int[4];

            coloredPodXCoords[0] = basePolygonXCoords[index] + (int) (portion1 * podLength * Math.cos(ownAngle));
            coloredPodXCoords[1] = basePolygonXCoords[index] + (int) (portion2 * podLength * Math.cos(ownAngle));
            coloredPodXCoords[2] = basePolygonXCoords[nextIndex] + (int) (portion2 * podLength * Math.cos(ownAngle));
            coloredPodXCoords[3] = basePolygonXCoords[nextIndex] + (int) (portion1 * podLength * Math.cos(ownAngle));

            coloredPodYCoords[0] = basePolygonYCoords[index] + (int) (portion1 * podLength * Math.sin(ownAngle));
            coloredPodYCoords[1] = basePolygonYCoords[index] + (int) (portion2 * podLength * Math.sin(ownAngle));
            coloredPodYCoords[2] = basePolygonYCoords[nextIndex] + (int) (portion2 * podLength * Math.sin(ownAngle));
            coloredPodYCoords[3] = basePolygonYCoords[nextIndex] + (int) (portion1 * podLength * Math.sin(ownAngle));

            g.setColor(color);
            g.fillPolygon(coloredPodXCoords, coloredPodYCoords, 4);
        }
    }

    private void fillPod(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int index, int nextIndex, double value1, double value2) {
        double min = indicator.getMin();
        double max = indicator.getMax();

        double portion1 = (value1 - min) / (max - min);
        double portion2 = (value2 - min) / (max - min);

        int[] coloredPodXCoords = new int[4];
        int[] coloredPodYCoords = new int[4];

        coloredPodXCoords[0] = basePolygonXCoords[index] + (int) (portion1 * podLength * Math.cos(ownAngle));
        coloredPodXCoords[1] = basePolygonXCoords[index] + (int) (portion2 * podLength * Math.cos(ownAngle));
        coloredPodXCoords[2] = basePolygonXCoords[nextIndex] + (int) (portion2 * podLength * Math.cos(ownAngle));
        coloredPodXCoords[3] = basePolygonXCoords[nextIndex] + (int) (portion1 * podLength * Math.cos(ownAngle));

        coloredPodYCoords[0] = basePolygonYCoords[index] + (int) (portion1 * podLength * Math.sin(ownAngle));
        coloredPodYCoords[1] = basePolygonYCoords[index] + (int) (portion2 * podLength * Math.sin(ownAngle));
        coloredPodYCoords[2] = basePolygonYCoords[nextIndex] + (int) (portion2 * podLength * Math.sin(ownAngle));
        coloredPodYCoords[3] = basePolygonYCoords[nextIndex] + (int) (portion1 * podLength * Math.sin(ownAngle));

        g.fillPolygon(coloredPodXCoords, coloredPodYCoords, 4);
    }

    private void drawMiddleLine(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Indicator indicator, double ownAngle, int index, int nextIndex, double error1, double error2) {
        double min = indicator.getMin();
        double max = indicator.getMax();

        double portion1 = (error1 - min)/(max - min);
        double portion2 = (error2 - min)/(max - min);

        int xCommon = (int) (0.5 * (basePolygonXCoords[index] + basePolygonXCoords[nextIndex]));
        int xError1Line = xCommon + (int) (portion1 * podLength * Math.cos(ownAngle));
        int xError2Line = xCommon + (int) (portion2 * podLength * Math.cos(ownAngle));

        int yCommon = (int) (0.5 * (basePolygonYCoords[index] + basePolygonYCoords[nextIndex]));
        int yError1Line = yCommon + (int) (portion1 * podLength * Math.sin(ownAngle));
        int yError2Line = yCommon + (int) (portion2 * podLength * Math.sin(ownAngle));

        g.drawLine(xError1Line, yError1Line, xError2Line, yError2Line);
    }

    public void setScenario(Scenario scenario) {
        this.scenario = scenario;
    }

    public void showToolTip(int xMouse, int yMouse) {
        int podNum = Indicator.numOfVisibleIndicators;
        double distanceToCentre = Math.sqrt((xMouse-xCentre)*(xMouse-xCentre) +
                                            (yMouse-yCentre)*(yMouse-yCentre));
        double mouseAngle = Util.calculateAngle(xMouse, yMouse, xCentre, yCentre);

        String toolTipText = "";
        int index = 0;
        for (int i = 0; i < Session.getIndicatorNum(); ++i) {
            Indicator ithIndicator = Session.getIndicator(i);
            if (!ithIndicator.isVisible())
                continue;

            double ownAngle = index * 2 * Math.PI / podNum;

            if (distanceToCentre > podLength)
                break;

            if (ownAngle - angelDif <= mouseAngle && mouseAngle <= ownAngle + angelDif) {
                toolTipText += ithIndicator.getShortName();
                break;
            }
            ++index;
        }

        setToolTipText(toolTipText);
    }

    /*private double getAngle(Indicator indicator) {
        if (scenarioAngles.containsKey(indicator))
            return scenarioAngles.get(indicator);
        else {
            int indicatorNum = Session.getIndicatorNum();
            int indicatorRank = Session.getIndicatorRank(indicator);
            double ownAngle = indicatorRank * 2*Math.PI / indicatorNum;

            scenarioAngles.put(indicator, ownAngle);
            return ownAngle;
        }
    }*/
}
