package ISEmeter;

/**
 *
 * Open ISEmeter is published in 2015 by the UHU.ES/OSHW group The 3.2 version
 * of Open ISEmeter is released at 2016
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, version 3 or later of the License, and/or the Creative Commons
 * Attribution - Non Commertial - ShareAlike 4.0 International License (CC
 * BY-NC-SA 4.0) (at your option).
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>. To view a copy of
 * the Creative Commons license, visit
 * <http://www.creativecommons.org/licenses/by-nc-sa/4.0/>
 *
 *
 * See http://rxtx.qbang.org/wiki/index.php/Installation_for_Windows to install
 * DLL needed for appropriate function of Arduino.jar library in Windows
 *
 * Arduino.jar library was released by Antony Garcia Gonzalez (PanamaHitek)
 * under Creative Commons Attribution - Non Commertial 4.0 International License
 * (CC BY-NC 4.0)
 *
 * JChart2D.jar library was released by Achim Westermann under GNU Lesser
 * General Public License (GNU - LGPL)
 *
 *
 */

import Arduino.Arduino;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import info.monitorenter.gui.chart.Chart2D;
import info.monitorenter.gui.chart.IAxis;
import info.monitorenter.gui.chart.IAxis.AxisTitle;
import info.monitorenter.gui.chart.ITrace2D;
import info.monitorenter.gui.chart.axis.scalepolicy.AxisScalePolicyManualTicks;
import info.monitorenter.gui.chart.rangepolicies.RangePolicyFixedViewport;
import info.monitorenter.gui.chart.traces.Trace2DSimple;
import info.monitorenter.util.Range;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DecimalFormat;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 *
 * @author JDaniel Mozo for the Open Source Hardware Group of the University of
 * Huelva(UHU.ES/OSHW)
 *
 */

public abstract class ISEmeter implements ChangeListener {

    static Chart2D chart;
    static ITrace2D traceData;
    static Timer timer = new Timer(true);
    static TimerTask task1;
    static boolean copied = true;
    static Clipboard clipboard = (Clipboard) Toolkit.getDefaultToolkit().getSystemClipboard();
    static ArrayList<Double> dataX = new ArrayList<>();
    static ArrayList<Double> dataY = new ArrayList<>();
    static DecimalFormat formatter = new DecimalFormat("#####0.00");
    static Arduino arduino = new Arduino();
    static boolean demo = false;
    static double m_starttime;
    static int index = 0;
    static int gain;
    static int flt = 10;
    static JLabel lblTime = new JLabel("t (s): ");
    static JLabel lblPot = new JLabel("E (mV): ");
    static float LowXMax;
    static float HighXMax;
    static final int PointNr = 181;       // poits to be showed in chart
    static final int Interval = 333;      // time in msec between data acq

    @SuppressWarnings("static-access")

    public static void main(String[] args) {
    // Create a chart:
        chart = new Chart2D();
    // Create an ITrace:
        // Note that dynamic charts need limited amount of values!!!
        traceData = new Trace2DSimple();    //Ltd(PointNr);
        traceData.setColor(Color.RED);
        // Add the traceData to the chart. This has to be done before adding points (deadlock prevention):
        chart.addTrace(traceData);
    // Configure Chart:
        chart.setPaintLabels(false);
        chart.setUseAntialiasing(true);
        IAxis axisX = chart.getAxisX();
        IAxis axisY = chart.getAxisY();
        // Set titles
        axisX.setAxisTitle(new AxisTitle("Time (s)"));
        axisY.setAxisTitle(new AxisTitle("Potential (mV)"));

    // Make it visible:
        // Create a frame.
        JFrame frame = new JFrame("Open ESImeter");
        frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        // Place frame in the middle of desktop
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        int ancho = dim.width / 2;
        int alto = dim.height / 2;
        frame.setBounds(ancho / 2, alto / 2, ancho, alto);

        // Sets personalized icon frame
        Image icon = Toolkit.getDefaultToolkit().getImage("resources\\UHU-OSHW.jpg");
        frame.setIconImage(icon);

        // Create layout on frame to distribute panels
        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.X_AXIS));
        // Create panels
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        JPanel panelR = new JPanel();
        panelR.setLayout(new BoxLayout(panelR, BoxLayout.Y_AXIS));

        // Add panels left to right
        frame.getContentPane().add(panel);
        frame.getContentPane().add(panelR);

        // Enable the termination button [cross on the upper right edge]:
        frame.addWindowListener(
            new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    // check if data was copied before exit
                    if (copied) {
                        System.exit(1);         // Terminates
                    } else {
                        int n = JOptionPane.showConfirmDialog(frame, "Data was not copied, do you continue?", "Exit confirmation", JOptionPane.YES_NO_OPTION);
                        // if answer is YES:
                        if (n == JOptionPane.YES_OPTION) {
                            copied = true;  // set data as copied and continue
                            System.exit(0);     // Abort exit
                        }
                    }
                }
            }
        );
        frame.setVisible(true);

    // Configure control panel:
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    // Create controls and add them to panel:
        JButton btnStart = new JButton("Start");
        btnStart.setPreferredSize(new Dimension(100, 40));
        btnStart.setMaximumSize(new Dimension(150, 40));
        btnStart.setAlignmentX(panel.LEFT_ALIGNMENT);

        JButton btnCopy = new JButton("Copy");
        btnCopy.setPreferredSize(new Dimension(100, 20));
        btnCopy.setMaximumSize(new Dimension(150, 20));
        btnCopy.setAlignmentX(panel.LEFT_ALIGNMENT);

        JLabel label1 = new JLabel("Range(FE) (mV):");
        label1.setMinimumSize(new Dimension(100, 0));
        label1.setAlignmentX(panel.LEFT_ALIGNMENT);
        JComboBox cmbSens = new JComboBox();
        cmbSens.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"5000", "500", "50", "5"}));
        cmbSens.setPreferredSize(new Dimension(100, 20));
        cmbSens.setMaximumSize(new Dimension(100, 20));
        cmbSens.setAlignmentX(panel.LEFT_ALIGNMENT);

        lblTime.setBorder(BorderFactory.createEtchedBorder());
        lblTime.setPreferredSize(new Dimension(100, 20));
        lblTime.setMaximumSize(new Dimension(100, 20));
        lblTime.setAlignmentX(panel.LEFT_ALIGNMENT);
        lblPot.setBorder(BorderFactory.createEtchedBorder());
        lblPot.setPreferredSize(new Dimension(100, 20));
        lblPot.setMaximumSize(new Dimension(100, 20));
        lblPot.setAlignmentX(panel.LEFT_ALIGNMENT);

        JLabel label3 = new JLabel("Comm Port:");
        label3.setMinimumSize(new Dimension(100, 0));
        label3.setAlignmentX(panel.LEFT_ALIGNMENT);
        JComboBox cmbPortComm = new javax.swing.JComboBox();
        cmbPortComm.setModel(new javax.swing.DefaultComboBoxModel(new String[]{}));
        cmbPortComm.setPreferredSize(new Dimension(100, 20));
        cmbPortComm.setMaximumSize(new Dimension(100, 20));
        cmbPortComm.setAlignmentX(panel.LEFT_ALIGNMENT);
        arduino.ShowMessageDialogs(false);
        for (int i = 1; i <= (arduino.SerialPortsAvailable()); i++) {
            // read available comm-port names and add to combo
            String PortName = arduino.NameSerialPortAt(i);
            // if there are 2 ports or plus NameSerialPortAt gives all ports 
            // concatenated to isolate each port name:
            if (i > 1) {
                PortName = (String) PortName.subSequence(arduino.NameSerialPortAt(i - 1).length() + 1, arduino.NameSerialPortAt(i).length());
            }
            cmbPortComm.addItem(PortName);
        }
        // Add a demo mode comm-port
        cmbPortComm.addItem("DEMO");
        cmbPortComm.setSelectedItem("DEMO");
        demo = true;

        // Set graphical scales
        ScaleGraf(axisY, -5000, 5000, true);
        ScaleGraf(axisX, 0, 60, false);

        // Load panel controls
        panel.add(btnStart);
        panel.add(Box.createRigidArea(new Dimension(0, 5)));
        panel.add(btnCopy);
        panel.add(label1);
        panel.add(cmbSens);
        panel.add(Box.createRigidArea(new Dimension(0, 5)));
        panel.add(lblTime);
        panel.add(lblPot);
        panel.add(Box.createVerticalGlue());
        panel.add(Box.createRigidArea(new Dimension(0, 5)));
        panel.add(label3);
        panel.add(cmbPortComm);

    //Load panelR content (graph and slider):
        // add chart to panelR:
        panelR.add(chart);
        // create and set an horizontal slider
        LowXMax = 0;
        HighXMax = PointNr * Interval / 1000;
        JSlider slider = new JSlider((int) LowXMax, (int) HighXMax, (int) HighXMax);

        ChangeListener ch = new ChangeListener() {
            // Define listener action for slider change value
            @Override
            public void stateChanged(ChangeEvent e) {
                // set axisX range from slider value
                if (slider.getValueIsAdjusting()) {      // while slider knob drags
                    int XMax = slider.getValue();
                    double XMin = XMax - PointNr * Interval / 1000;
                    ScaleGraf(axisX, XMin, (double) XMax, false);
                }
                //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
            }
        };

        slider.addChangeListener(ch);
        panelR.add(slider);
        slider.setEnabled(false);

        frame.setVisible(true);

        btnStart.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                String Text = btnStart.getText();
                // check if data was copied before acquire
                if (!copied) {
                    int n = JOptionPane.showConfirmDialog(frame, "Data was not copied, do you continue?", "Warning", JOptionPane.YES_NO_OPTION);
                    // if answer is YES:
                    if (n == JOptionPane.YES_OPTION) {
                        copied = true;  // set data as copied and continue
                    }
                }
                if ("Start".equals(Text) & copied) {     // start acquisition
                    btnStart.setText("Stop");
                    // clear chart
                    traceData.removeAllPoints();
                    // clear data arraylist
                    dataX.clear();
                    dataY.clear();
                    //initialize rangepolicy for Xaxis
                    ScaleGraf(axisX, 0, Interval * PointNr / 1000, false);
                    //disable slider
                    slider.setEnabled(false);

                    if (!demo) {
                        index = 0;
                        m_starttime = System.currentTimeMillis();

                    // Send start acquisition order to Serial comm
                        // (Data acquisition, store data and graph is in SerialPortEventListener)
                        // (Pacer is Arduino, not Java)
                        try {
                            arduino.SendData("r");
                        } catch (Exception ex) {
                            Logger.getLogger(ISEmeter.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    } else {
                        // Now the dynamic adding of points. This is just a demo!
                        task1 = new TimerTask() {

                            private double m_y = 100;
                            private double m_starttime = System.currentTimeMillis();
                            private int index = 0;

                            @Override
                            public void run() {
                                // This is just computation of some nice looking value.
                                double rand = Math.random();
                                boolean add = (rand >= 0.5);
                                this.m_y = (add) ? this.m_y + Math.random() * 10 : this.m_y - Math.random() * 10;

                                // This is the important thing: Point is added from separate Thread.
                                dataX.add((double) (System.currentTimeMillis() - this.m_starttime) / 1000);
                                dataY.add(this.m_y);

                                // Displays new data on labels and graph
                                traceData.addPoint(dataX.get(index), dataY.get(index));
                                lblTime.setText("t (s): " + formatter.format(dataX.get(index)));
                                lblPot.setText("E (mV): " + formatter.format(dataY.get(index)));

                                // check scale X and drag if necessary
                                if (dataX.get(index) > Interval * PointNr / 1000) {
                                    ScaleGraf(axisX, dataX.get(index) - PointNr * Interval / 1000, dataX.get(index), false);
                                }

                                index++;
                            }
                        };

                        // Every "Interval" milliseconds a new value is collected.
                        timer.schedule(task1, 0, Interval);
                    }
                } else {        //stop adquisition
                    btnStart.setText("Start");
                    copied = false;         // set flag not copied data

                    //setup the slider and enable
                    HighXMax = (float) axisX.getMax();
                    LowXMax = (PointNr * Interval / 1000);
                    if (HighXMax < LowXMax) {
                        LowXMax = 0;
                    }
                    slider.setMinimum((int) LowXMax);
                    slider.setMaximum((int) HighXMax);
                    slider.setValue((int) HighXMax);
                    slider.setEnabled(HighXMax > PointNr * Interval / 1000);

                    // Halts the data acquisition
                    if (!demo) {
                        try {
                            // Send stop acquisition order to Serial comm
                            arduino.SendData("s");
                        } catch (Exception ex) {
                            Logger.getLogger(ISEmeter.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    } else { // if demo mode
                        // stop new value collection.
                        task1.cancel();         //demo mode
                    }
                }
            }
        });

        btnCopy.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            // copy data buffer to clipboard
                // build text string to be copied
                int i = 0;
                String textBuild = new String();
                textBuild += "Time (s)" + "\t" + "emf (mV)" + "\n";
                String dataXString, dataYString;
                do {
                    dataXString = formatter.format(dataX.get(i));
                    dataYString = formatter.format(dataY.get(i));
                    textBuild += dataXString + "\t" + dataYString + "\n";
                    i++;
                } while (i < dataX.size());
                // put text string to clipboard
                StringSelection data2copy = new StringSelection(textBuild);
                clipboard.setContents(data2copy, data2copy);

                copied = true;      //set copied flag
            }
        });

        cmbSens.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            // Send to Arduino the Gain setting and set graph

                // Set YGraphScale depending combo selection
                switch (cmbSens.getSelectedIndex()) {
                    case 0: // 5000 mV (x1)
                        ScaleGraf(axisY, -5000, 5000, true);
                        break;
                    case 1: // 500 mV (x10)
                        ScaleGraf(axisY, -500, 500, true);
                        break;
                    case 2: // 50 mV (x100)
                        ScaleGraf(axisY, -50, 50, true);
                        break;
                    case 3: // 5 mV (x1000)
                        ScaleGraf(axisY, -5, 5, true);
                        break;
                }
            }
        });

        cmbPortComm.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // Set DEMO flag
                demo = cmbPortComm.getSelectedItem().equals("DEMO");

                if (!demo) {
                    // Initialize Serial Comm Port for Arduino
                    try {
                        arduino.ShowMessageDialogs(false);
                        arduino.KillArduinoConnection();
                    } catch (Exception ex) {
                        Logger.getLogger(ISEmeter.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    try {
                        arduino.ShowMessageDialogs(true);
                        arduino.ArduinoRXTX((String) cmbPortComm.getSelectedItem(), 2000, 9600, evento);
                    } catch (Exception ex) {
                        Logger.getLogger(ISEmeter.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        });

        chart.addMouseMotionListener(new MouseMotionListener() {

            @Override
            public void mouseMoved(MouseEvent e) {
                if (!traceData.isEmpty()) {
                    double scaledX = axisX.translatePxToValue(e.getX());
                    double scaledY = axisY.translatePxToValue(e.getY());

                    lblTime.setText("t (s): " + formatter.format(scaledX));
                    lblPot.setText("E (mV): " + formatter.format(scaledY));
                }
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
            }
        });

    }

    public static void ScaleGraf(IAxis axis, double sMin, double sMax, boolean g) {
        // Set scale on axis from sMin to sMax
        axis.setRangePolicy(new RangePolicyFixedViewport(new Range(sMin, sMax)));
        axis.setAxisScalePolicy(new AxisScalePolicyManualTicks());
        axis.setMinorTickSpacing((sMax - sMin) / 10);

        // Calculate gain from selected FE
        if (g) {
            gain = 5000 / (int) sMax;

            if (!demo) {         // If not DEMO send gain to Arduino
                String msg = new String();
                switch ((int) sMax) {
                    case 5000: {
                        msg = "g0";
                        break;
                    }
                    case 500: {
                        msg = "g1";
                        break;
                    }
                    case 50: {
                        msg = "g2";
                        break;
                    }
                    case 5: {
                        msg = "g3";
                        break;
                    }
                }
                try {
                    // Send gain setting to Serial comm
                    arduino.SendData(msg);
                } catch (Exception ex) {
                    Logger.getLogger(ISEmeter.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    static SerialPortEventListener evento = new SerialPortEventListener() {

        @Override
        public void serialEvent(SerialPortEvent spe) {
            if (arduino.MessageAvailable() == true) {
                String msg = arduino.PrintMessage();    // read data
                if (msg.startsWith("v") == true) {       // If message is a data:
                    // stores time in X
                    // (by setting the reception time)
                    // dataX.add((double)(System.currentTimeMillis() - m_starttime)/1000);
                    // (by using the pacer interval)
                    dataX.add((double) Interval * index / 1000);

                    // extract data from msg
                    int semicolon = msg.indexOf(";");
                    int comma = msg.indexOf(",");
                    String dato = msg.substring(semicolon + 1, comma);
                    String datom = msg.substring(comma + 1);
                    // calculate voltage from data and gain
                    dataY.add((Double.parseDouble(dato) - Double.parseDouble(datom)) * 5000 / 1024 / gain);
                    // Add point to traceData
                    traceData.addPoint(dataX.get(index), dataY.get(index));
                    // Print point to labels
                    lblTime.setText("t (s): " + formatter.format(dataX.get(index)));
                    lblPot.setText("E (mV): " + formatter.format(dataY.get(index)));

                    // check scale X
                    if (dataX.get(index) > Interval * PointNr / 1000) {
                        ScaleGraf(chart.getAxisX(), dataX.get(index) - PointNr * Interval / 1000, dataX.get(index), false);
                    }

                    index++;
                } else {
                    // If message isn't a data:
                    System.out.println(msg);
                }
            }
        }
    };

}
