/*
 * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved.
 */
package ch.psi.plot;

import ch.psi.plot.MatrixPlotSeries.MatrixPlotSeriesListener;
import ch.psi.plot.utils.IO;
import ch.psi.plot.utils.SwingUtils;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.HashMap;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;

/**
 *
 */
abstract public class MatrixPlotBase extends PlotBase<MatrixPlotSeries> implements MatrixPlot {

    protected MatrixPlotBase() {
        this(null);
    }

    protected MatrixPlotBase(String title) {
        super(MatrixPlotSeries.class, title);
        createAxis(AxisId.X, "X");
        createAxis(AxisId.Y, "Y");
        createAxis(AxisId.Z);
    }

    //Only supporting one series for now so let's gain some speed    
    @Override
    public void addSeries(MatrixPlotSeries series) {
        if (getAllSeries().length > 0) {
            removeSeries(getAllSeries()[0]);
        }
        super.addSeries(series);

    }

    final MatrixPlotSeriesListener seriesListener = new MatrixPlotSeriesListener() {
        @Override
        public void onSeriesAppendData(MatrixPlotSeries series, double x, double y, double z) {

            if ((series == null) || (getNumberOfSeries() == 0)) {
                return;
            }
            final int indexX = ((int) ((x - series.getMinX()) / series.getBinWidthX()));
            final int indexY = ((int) ((y - series.getMinY()) / series.getBinWidthY()));
            if (!series.contains(indexX, indexY)) {
                return;
            }
            onAppendData(series, indexX, indexY, x, y, z);
            if (getRequireUpdateOnAppend() && isUpdatesEnabled()) {
                requestSeriesUpdate(series);
            }
        }

        @Override
        public void onSeriesSetData(MatrixPlotSeries series, double[][] data) {
            if ((series == null) || (getNumberOfSeries() == 0)) {
                return;
            }
            //TODO: Check consistency of z
            onSetData(series, data);
            if (isUpdatesEnabled()) {
                requestSeriesUpdate(series);
            }
        }
    };

    public void saveData(String filename) throws IOException {
        if (getAllSeries().length == 0) {
            return;
        }
        MatrixPlotSeries series = getAllSeries()[0];
        StringBuilder str = new StringBuilder(1024);

        str.append(getAxis(AxisId.X).getLabel().isEmpty() ? "X" : getAxis(AxisId.X).getLabel()).append(FIELD_SEPARATOR);
        str.append(getAxis(AxisId.Y).getLabel().isEmpty() ? "Y" : getAxis(AxisId.Y).getLabel()).append(FIELD_SEPARATOR);
        str.append(getAxis(AxisId.Z).getLabel().isEmpty() ? "Z" : getAxis(AxisId.Z).getLabel()).append(LINE_SEPARATOR);

        double[][] data = getSeriesData(getAllSeries()[0]);
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                double y = series.getMinY() + i * series.getBinWidthY();
                double x = series.getMinX() + j * series.getBinWidthX();
                double z = data[i][j];
                str.append(String.format("%1.6f", x)).append(FIELD_SEPARATOR);
                str.append(String.format("%1.6f", y)).append(FIELD_SEPARATOR);
                str.append(String.format("%1.6f", z)).append(LINE_SEPARATOR);
            }
        }
        IO.writeStringToFile(filename, str.toString());
    }

    //Injecting specific entries in popup menu
    @Override
    protected void onCreatedPanel() {
        addPopupMenuItem(null);//Separator
        // Detach plot into a separate frame
        JMenu detachPlotMenu = new JMenu("Detach");
        String[] implementations = getImplementations();
        JMenuItem[] detachPlotMenuItems = new JMenuItem[implementations.length];
        
        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    if (getAllSeries().length == 0) {
                        return;
                    }
                    MatrixPlotSeries s = getAllSeries()[0];
                    MatrixPlot p = newPlot(e.getActionCommand());
                    p.setTitle(getTitle());
                    p.getAxis(AxisId.X).setLabel(getAxis(AxisId.X).getLabel());
                    p.getAxis(AxisId.Y).setLabel(getAxis(AxisId.Y).getLabel());
                    p.getAxis(AxisId.Z).setLabel(getAxis(AxisId.Z).getLabel());
                    MatrixPlotSeries detachedSeries = new MatrixPlotSeries(null, s.getMinX(), s.getMaxX(), s.getNumberOfBinsX(), s.getMinY(), s.getMaxY(), s.getNumberOfBinsY());
                    p.addSeries(detachedSeries);
                    detachedSeries.setData(s.getData());

                    JFrame frame = new JFrame(getTitle());
                    frame.setContentPane(p.getChartPanel());
                    frame.pack();
                    SwingUtils.centerOnScreen(frame);
                    frame.setVisible(true);
                } catch (Exception ex) {
                    SwingUtils.showException(getChartPanel(), ex);
                }
            }
        };
        for (int i=0;i<implementations.length;i++){
            detachPlotMenuItems[i] = new JMenuItem(implementations[i]);
            detachPlotMenuItems[i].addActionListener(listener);
            detachPlotMenu.add(detachPlotMenuItems[i]);
        }        
        addPopupMenuItem(detachPlotMenu);
    }

    //abstract protected Object onSetSeries(MatrixPlotSeries series);
    abstract protected void onAppendData(MatrixPlotSeries series, int indexX, int indexY, double x, double y, double z);

    abstract protected void onSetData(MatrixPlotSeries series, double[][] data);
    
    
    // Known Implemenmtations
    //TODO: Should add reflection & dynamic including classes? Should factorize in PlotBase?

    public static MatrixPlot newPlot(String impl) throws ClassNotFoundException, InstantiationException, IllegalAccessException{    
        return (MatrixPlot) (Class.forName( "ch.psi.plot." + impl + ".MatrixPlot")).newInstance();
    }

    public static String[] getImplementations(){    
        return new String[]{"jfree", "jzy3d"};
    }
    
}
