/*
 * JTraceChart.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.app.tracer.traces;

import dr.app.gui.chart.Axis;
import dr.app.gui.chart.DiscreteJChart;
import dr.app.gui.chart.Plot;
import dr.stats.Variate;

import java.awt.*;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;
import java.util.List;

public class JTraceChart extends DiscreteJChart {

    private static final int SAMPLE_POINTS = 1000;

    private boolean useSample = false;
    private boolean isLinePlot = true;
    private static final int BURNIN_TRANSLUCENCY = 72;

    private class Trace {
        int stateStart;
        int stateStep;

        int sampleCount;
        int sampleStep;

        List<Double> states = new ArrayList<Double>();
        List<Double> values;

        Trace(int stateStart, int stateStep, List<Double> values) {

            this.stateStart = stateStart;
            this.stateStep = stateStep;

            this.values = values;

            sampleCount = values.size();
            sampleStep = 1;
            while (sampleCount > SAMPLE_POINTS) {
                sampleStep *= 2;
                sampleCount = values.size() / sampleStep;
            }

            double ix = stateStart;
            for (int j = 0; j < values.size(); j++) {
                this.states.add(j, ix);
                ix += stateStep;

            }
        }
    }

    private final ArrayList<Trace> traces = new ArrayList<Trace>();
    private final ArrayList<Trace> burninTraces = new ArrayList<Trace>();

    public JTraceChart(Axis xAxis, Axis yAxis) {
        super(xAxis, yAxis);
    }

    public void setUseSample(boolean useSample) {
        this.useSample = useSample;
    }

    public void setIsLinePlot(boolean isLinePlot) {
        this.isLinePlot = isLinePlot;
    }

    public void addTrace(String name, int stateStart, int stateStep, List<Double> values, List<Double> burninValues, Paint paint) {

        Variate.D yd = new Variate.D(values);

        xAxis.addRange(0, stateStart + (values.size() * stateStep) - stateStep);
        yAxis.addRange(yd.getMin(), yd.getMax());

        traces.add(new Trace(stateStart, stateStep, values));
        if (burninValues != null) {
            burninTraces.add(new Trace(0, stateStep, burninValues));
        }

        Plot plot = new Plot.AbstractPlot() { // create a dummy plot to store paint styles
            protected void paintData(Graphics2D g2, Variate.N xData, Variate.N yData) {
            }
        };

        plot.setLineColor(paint);
        plot.setName(name);
        addPlot(plot);

        recalibrate();
        repaint();
    }

    public List<Double> getTraceStates(int index) {
        Trace trace = traces.get(index);
        return trace.states;
    }

    public List<Double> getTraceValues(int index) {
        Trace trace = traces.get(index);
        return trace.values;
    }

    public void removeAllTraces() {
        traces.clear();
        burninTraces.clear();
        xAxis.setRange(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
        yAxis.setRange(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
        removeAllPlots();
        recalibrate();
        repaint();
    }

    protected void calibrate(Graphics2D g2, Dimension size) {
    }

    protected boolean hasContents() {
        return traces.size() > 0;
    }

    protected void paintContents(Graphics2D g2) {

        if (!isLinePlot) {
            g2.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
        } else {
            g2.setStroke(new BasicStroke(0.5f));
        }


        for (int i = 0; i < traces.size(); i++) {
            Trace trace = traces.get(i);

            int sampleFrequency = 1;

            if (useSample) {
                sampleFrequency = trace.states.size() / trace.sampleCount;
            }

            paintTrace(g2, trace, getPlot(i).getLineColor(), sampleFrequency);

            if (i < burninTraces.size()) {
                Trace burninTrace = burninTraces.get(i);
                Color colour = (Color)getPlot(i).getLineColor();
                Color burninColor = new Color(colour.getRed(), colour.getGreen(), colour.getBlue(), BURNIN_TRANSLUCENCY);
                paintTrace(g2, burninTrace, burninColor, sampleFrequency);
            }
        }

    }

    private void paintTrace(Graphics2D g2, Trace trace, Paint paint, int sampleFrequency) {
        float x = (float) transformX(trace.states.get(0));
        float y = (float) transformY(trace.values.get(0));

        GeneralPath path = new GeneralPath();
        path.moveTo(x, y);
        if (!isLinePlot) {
            path.lineTo(x, y);
        }

        int k = sampleFrequency;

        for (int j = 1; j < trace.states.size(); j++) {

            x = (float) transformX(trace.states.get(k));
            y = (float) transformY(trace.values.get(k));

            if (!isLinePlot) {
                path.moveTo(x, y);
            }
            path.lineTo(x, y);

            k += sampleFrequency;
            if (k >= trace.states.size()) {
                break;
            }
        }

        g2.setPaint(paint);
        g2.draw(path);
    }

}