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.*;

public class MultipodesIndicator extends JPanel {

    private final double podRadiusProportion = 0.5;
    private final double holeRadiusProportion = 0.33;

    private final double angelDif = Math.PI/20;

    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 Indicator indicator;

    private double min;
    private double max;

    //private Map<Scenario, Double> scenarioAngles;

    public MultipodesIndicator(Indicator indicator) {
        this.indicator = indicator;

        min = indicator.getMin();
        max = indicator.getMax();

        //scenarioAngles = new HashMap<Scenario, Double>();

        x = 0;
        y = 0;
    }

    @Override
    public void paint(Graphics g) {
        int scenarioNum = Session.getScenarios().size();
        if (scenarioNum == 0)
            return;

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

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

        drawPods((Graphics2D) g);
        drawBorder((Graphics2D) g);
    }

    private void drawPods(Graphics2D g) {
        int scenarioNum = Scenario.numOfVisibleScenarios;//Session.getScenarios().size();

        xCentre = x + (int) (0.5 * w);
        yCentre = y + (int) (0.5 * h);

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

        int[] basePolygonXCoords = new int[scenarioNum];
        int[] basePolygonYCoords = new int[scenarioNum];
        for (int m = 0; m < scenarioNum; ++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.getScenarios().size(); ++i) {
            Scenario ithScenario = Session.getSortedScenarios(TradeoffsPanel.sortByIndicator).get(i);
            if (!ithScenario.isVisible())
                continue;

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

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

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

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

    private void drawPodBorderLines(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, double ownAngle, int index, int nextIndex) {
        int outerXCoords0 = basePolygonXCoords[index] + (int) (podLength * Math.cos(ownAngle));
        int outerYCoords0 = basePolygonYCoords[index] + (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);
        g.drawLine(basePolygonXCoords[index], basePolygonYCoords[index], outerXCoords0, outerYCoords0);
        g.drawLine(basePolygonXCoords[nextIndex], basePolygonYCoords[nextIndex], outerXCoords1, outerYCoords1);
    }

    private void drawAveragePod(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, int i, Scenario scenario, double ownAngle, int nextIndex) {
        g.setColor(scenario.getColor());
        fillPod(g, basePolygonXCoords, basePolygonYCoords, ownAngle, i, nextIndex, 0, scenario.getAverageOrMedianValue(indicator, OptionsPanel.contoursBasedOn));
        g.drawLine(basePolygonXCoords[i], basePolygonYCoords[i], basePolygonXCoords[nextIndex], basePolygonYCoords[nextIndex]);
    }

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

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

    private void drawBoxPlot(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, Scenario scenario, 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 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, 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, 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, Scenario ithScenario, double ownAngle, int index, int nextIndex) {
        java.util.List<Double> pdfValues = ithScenario.getPdfValues().get(indicator);
        double maxValue = ithScenario.getPdfMax().get(indicator);
        int binNum = pdfValues.size();
        double[] scenarioMinMax = ithScenario.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(ithScenario.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, double ownAngle, int index, int nextIndex, double value1, double value2) {
        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.setColor(scenario.getColor());
        g.fillPolygon(coloredPodXCoords, coloredPodYCoords, 4);
    }

    private void drawMiddleLine(Graphics2D g, int[] basePolygonXCoords, int[] basePolygonYCoords, double ownAngle, int index, int nextIndex, double error1, double error2) {
        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);
    }

    private void drawBorder(Graphics2D g) {
        if (TradeoffsPanel.sortByIndicator == null)
            return;

        if (indicator.equals(TradeoffsPanel.sortByIndicator)) {
            g.setColor(Constants.MAIN_COLOR);
            g.setStroke(new BasicStroke(2.0f));
            g.drawRect(x+(int) (0.5*(w-plotLength)), y+(int) (0.5*(h-plotLength)), plotLength, plotLength);
            g.setStroke(new BasicStroke(1.0f));
        }
    }

    public void showToolTip(int xMouse, int yMouse) {
        int stickNum = Scenario.numOfVisibleScenarios;//Session.getScenarios().size();
        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.getScenarios().size(); ++i) {
            Scenario ithScenario = Session.getSortedScenarios(TradeoffsPanel.sortByIndicator).get(i);//Session.getScenario(i);
            if (!ithScenario.isVisible())
                continue;

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

            if (distanceToCentre > podLength)
                break;

            if (ownAngle - angelDif <= mouseAngle && mouseAngle <= ownAngle + angelDif) {
                toolTipText += ithScenario.getName() + ": " + ithScenario.getInfo(indicator);
                break;
            }
            ++index;
        }

        setToolTipText(toolTipText);
    }

    /*private double getAngle(Scenario scenario) {
        int scenarioNum = Session.getScenarios().size();

        int scenarioRank = Session.getScenarioRank(scenario);
        double ownAngle = scenarioRank * 2*Math.PI / scenarioNum;

        return ownAngle;
    }*/

}
