/*
 * Apocalypse Laboratories
 * Open Source License
 *
 * Source code can be used for any purpose, as long as:
 *  - Compiled binaries are rebranded and trademarks are not
 *    visible by the end user at any time, except to give
 *    credit to Apocalypse Laboratories, such as by showing
 *    "Based on <product> by Apocalypse Laboratories" or a
 *    similar notice;
 *  - You do not use the code for evil;
 *  - Rebranded compiled applications have significant
 *    differences in functionality;
 *  - and you provide your modified source code for download,
 *    under the terms of the GNU LGPL v3 or a comparable
 *    license.
 *
 * Compiled binaries cannot be redistributed or mirrored,
 * unless:
 *  - You have written permission from Apocalypse Laboratories;
 *  - Downloads are not available from Apocalypse Laboratories,
 *    not even behind a paywall or other blocking mechanism;
 *  - or you have received a multi-computer license, in which
 *    case you should take measures to prevent unauthorized
 *    downloads, such as preventing download access from the
 *    Internet.
 */
package net.apocalypselabs.symat;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import static java.lang.Math.abs;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.math.plot.plotObjects.BaseLabel;
import org.matheclipse.core.eval.EvalUtilities;
import org.matheclipse.parser.client.math.MathException;

/**
 *
 * @author Skylar
 */
public class Graph extends javax.swing.JInternalFrame {

    private final JFileChooser fc = new JFileChooser();

    private boolean standalone = true;
    private boolean customName = false;

    private BaseLabel lbl = new BaseLabel("", Color.black, 0.5, 1.1);

    // History, used for redrawing when scale changed.
    private String history = "";

    // If a graph is being drawn, set to true, else false
    boolean graphing = false;

    // Graph min and max
    private double xmin = -10;
    private double xmax = 10;
//    private double ymin = -10;
//    private double ymax = 10;

    /**
     * Creates new form Graph
     */
    public Graph() {
        init();
    }

    public Graph(boolean isInternal) {
        init();
        standalone = !isInternal;
    }

    private void init() {
        initComponents();
        FileFilter filter = new FileNameExtensionFilter("PNG image (.png)", "png");
        fc.setFileFilter(filter);
        fc.addChoosableFileFilter(filter);
        plot.plotToolBar.remove(5);
        plot.plotToolBar.remove(4);
        plot.plotToolBar.remove(3);
        lbl.setFont(new Font("Courier", Font.BOLD, 18));
        plot.addPlotable(lbl);
    }

    @Override
    public void doDefaultCloseAction() {
        if (standalone) {
            dispose();
        } else {
            hide();
        }
    }

    public void forceClose() {
        dispose();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        inBox = new javax.swing.JTextField();
        jLabel1 = new javax.swing.JLabel();
        plotBtn = new javax.swing.JButton();
        plot = new org.math.plot.Plot2DPanel();
        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu1 = new javax.swing.JMenu();
        exportBtn = new javax.swing.JMenuItem();
        jMenu2 = new javax.swing.JMenu();
        clrGraphBtn = new javax.swing.JMenuItem();
        setTitleBtn = new javax.swing.JMenuItem();

        setClosable(true);
        setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
        setIconifiable(true);
        setMaximizable(true);
        setResizable(true);
        setTitle("Graph");
        setToolTipText("");
        setFrameIcon(new javax.swing.ImageIcon(getClass().getResource("/net/apocalypselabs/symat/icons/graph.png"))); // NOI18N
        setPreferredSize(new java.awt.Dimension(326, 402));
        addComponentListener(new java.awt.event.ComponentAdapter() {
            public void componentShown(java.awt.event.ComponentEvent evt) {
                formComponentShown(evt);
            }
        });

        inBox.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                inBoxKeyTyped(evt);
            }
        });

        jLabel1.setText("f(x)=");

        plotBtn.setText(">>");
        plotBtn.setToolTipText("");
        plotBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                plotBtnActionPerformed(evt);
            }
        });

        plot.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                plotMouseClicked(evt);
            }
        });

        jMenu1.setText("File");

        exportBtn.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        exportBtn.setText("Export graph...");
        exportBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exportBtnActionPerformed(evt);
            }
        });
        jMenu1.add(exportBtn);

        jMenuBar1.add(jMenu1);

        jMenu2.setText("Edit");

        clrGraphBtn.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, java.awt.event.InputEvent.CTRL_MASK));
        clrGraphBtn.setText("Clear Graph");
        clrGraphBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                clrGraphBtnActionPerformed(evt);
            }
        });
        jMenu2.add(clrGraphBtn);

        setTitleBtn.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_T, java.awt.event.InputEvent.CTRL_MASK));
        setTitleBtn.setText("Set Title...");
        setTitleBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                setTitleBtnActionPerformed(evt);
            }
        });
        jMenu2.add(setTitleBtn);

        jMenuBar1.add(jMenu2);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(inBox, javax.swing.GroupLayout.DEFAULT_SIZE, 215, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(plotBtn)
                .addGap(10, 10, 10))
            .addComponent(plot, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(plot, javax.swing.GroupLayout.DEFAULT_SIZE, 311, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(inBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel1)
                    .addComponent(plotBtn))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void plotBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_plotBtnActionPerformed
        new GraphThread(inBox.getText()).start();
    }//GEN-LAST:event_plotBtnActionPerformed

    /**
     * Graph the graphs without holding up everything else.
     */
    private class GraphThread extends Thread {

        String[] frmlas;

        public GraphThread(String frmla) {
            frmlas = new String[1];
            frmlas[0] = frmla;
        }

        public GraphThread(String[] frmla) {
            frmlas = frmla;
        }

        @Override
        public void run() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    inBox.setEnabled(false);
                    plotBtn.setEnabled(false);
                    for (Component mu : jMenuBar1.getComponents()) {
                        mu.setEnabled(false);
                    }
                }
            });
            for (String formula : frmlas) {
                String niceformula = formula;
                CodeRunner cr = new CodeRunner();
                formula = formula.replaceAll("x", "\\$x");
                EvalUtilities solver = new EvalUtilities();
                String xx = "";
                String yy = "";
                double x;
                for (x = xmin; x <= xmax; x += ((xmax - xmin) / 40.0)) {
                    try {
                        cr.setVar("x", x);
                        String res = solver.evaluate("$x=" + x + ";N[" + formula + "]").toString();
                        //if (Double.parseDouble(res) >= ymin && Double.parseDouble(res) <= ymax) {
                        yy += res + " ";
                        xx += String.valueOf(x) + " ";
                        //}
                    } catch (MathException | NumberFormatException ex) {

                    }
                }
                Debug.println(xx);
                Debug.println(yy);
                String[] xs = xx.trim().split(" ");
                String[] ys = yy.trim().split(" ");
                double[] xd = new double[xs.length];
                double[] yd = new double[ys.length];
                for (int i = 0; i < xs.length; i++) {
                    xd[i] = Double.parseDouble(xs[i]);
                }
                for (int i = 0; i < ys.length; i++) {
                    yd[i] = Double.parseDouble(ys[i]);
                }
                SwingUtilities.invokeLater(new Updater(niceformula, xd, yd));
            }
            SwingUtilities.invokeLater(new Finisher());
        }

        private class Updater implements Runnable {

            final double[] xd;
            final double[] yd;
            final String formula;

            public Updater(String frmla, double[] x, double[] y) {
                xd = x;
                yd = y;
                formula = frmla;
            }

            @Override
            public void run() {
                plot.addLinePlot(formula, xd, yd);
                history += formula + "\n";
            }

        }

        private class Finisher implements Runnable {

            @Override
            public void run() {
                inBox.setEnabled(true);
                plotBtn.setEnabled(true);
                for (Component mu : jMenuBar1.getComponents()) {
                    mu.setEnabled(true);
                }
            }

        }
    }

    private void formComponentShown(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentShown
    }//GEN-LAST:event_formComponentShown

    public void plotPoints(double[] x, double[] y) {
        plotPoints(x, y, "Points");
    }

    public void plotPoints(double[] x, double[] y, String name) {
        plot.addLinePlot(name, x, y);
    }

    /**
     * Get the zoom ratio.
     *
     * @param zoomLevel The zoom level to calculate from.
     * @return The ratio.
     */
    @Deprecated
    public static double getScale(int zoomLevel) {
        return 15.0;
    }

    public void drawDot(double x, double y) {
        double[] xpt = {x};
        double[] ypt = {y};

        plot.addScatterPlot("", xpt, ypt);
    }

    private void inBoxKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_inBoxKeyTyped
        if (evt.getKeyChar() == '\n') {
            plotBtnActionPerformed(null);
        }
    }//GEN-LAST:event_inBoxKeyTyped

    private void clrGraphBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clrGraphBtnActionPerformed
        clearDraw();
    }//GEN-LAST:event_clrGraphBtnActionPerformed

    private void exportBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportBtnActionPerformed
        int result = fc.showSaveDialog(MainGUI.mainPane);
        if (result == JFileChooser.APPROVE_OPTION) {
            File file = new File(addSaveExt(fc.getSelectedFile().toString()));
            try {
                plot.toGraphicFile(file);
            } catch (IOException ex) {
                JOptionPane.showInternalMessageDialog(this,
                        "Image export failed!",
                        "Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }//GEN-LAST:event_exportBtnActionPerformed

    private void setTitleBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_setTitleBtnActionPerformed
        String wintitle = JOptionPane.showInternalInputDialog(this,
                "New window title:",
                "Rename",
                JOptionPane.QUESTION_MESSAGE);
        if (wintitle != null && !wintitle.equals("")) {
            setWindowTitle(wintitle);
            setLabel(wintitle);
        }
    }//GEN-LAST:event_setTitleBtnActionPerformed

    private void plotMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_plotMouseClicked

    }//GEN-LAST:event_plotMouseClicked

    /**
     * Get the range of the graph.
     *
     * @return {xmin, xmax}
     */
    public double[] getRange() {
        double[] range = {xmin, xmax};
        return range;
    }

    public void setRange(double min, double max) {
        xmin = min;
        xmax = max;

        clearDraw(false);
        plot.setFixedBounds(0, min, max);
        if (!history.trim().equals("")) {
            String temp = "";
            for (String cmd : history.trim().split("\n")) {
                cmd = cmd.trim();
                if (!cmd.equals("")) {
                    temp += cmd + "\n";
                }
            }
            history = temp.trim();
            new GraphThread(history.split("\n")).start();
            inBox.setText("");
        }
    }

    /**
     * Graph the given function. Same as typing into input box and pressing
     * Enter.
     *
     * @param f f(x) = f
     */
    public void graphFunction(String f) {
        inBox.setText(f);
        plotBtnActionPerformed(null);
    }

    public void setLabel(String label) {
        lbl.setText(label);
    }

    private String addSaveExt(String path) {
        if (!path.matches(".*\\.(png)")) {
            path += ".png";
        }
        return path;
    }

    /**
     * Set the wintitle of this graph window.
     *
     * @param t The wintitle.
     */
    public void setWindowTitle(String t) {
        setTitle(t);
        customName = true;
    }

    /**
     * Get the wintitle of the window.
     *
     * @return the wintitle, stupid!
     */
    public String getWindowTitle() {
        return getTitle();
    }

    /**
     * Erase the graph.
     */
    public void clearDraw() {
        clearDraw(true);
    }

    /**
     * Erase the graph.
     *
     * @param alsoHistory True if history should be removed
     */
    public void clearDraw(boolean alsoHistory) {
        if (alsoHistory) {
            history = "";
        }
        plot.removeAllPlots();
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JMenuItem clrGraphBtn;
    private javax.swing.JMenuItem exportBtn;
    private javax.swing.JTextField inBox;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JMenu jMenu1;
    private javax.swing.JMenu jMenu2;
    private javax.swing.JMenuBar jMenuBar1;
    private org.math.plot.Plot2DPanel plot;
    private javax.swing.JButton plotBtn;
    private javax.swing.JMenuItem setTitleBtn;
    // End of variables declaration//GEN-END:variables
}
