/*
 * -------------------------------------------------------------------------
 *      $Id: CsPanel.java,v 1.3 2004/04/07 20:08:26 estewart Exp $
 * -------------------------------------------------------------------------
 *      Copyright (c) 1999 Visual Numerics Inc. All Rights Reserved.
 *
 *      This software is confidential information which is proprietary to
 *      and a trade secret of Visual Numerics, Inc.  Use, duplication or
 *      disclosure is subject to the terms of an appropriate license
 *      agreement.
 *
 *      VISUAL NUMERICS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
 *      SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
 *      BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 *      FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. VISUAL
 *      NUMERICS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 *      AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR
 *      ITS DERIVATIVES.
 *--------------------------------------------------------------------------
 */

/*
 * CsPanel.java
 *
 * Created on August 19, 2003, 2:15 PM
 */

package com.imsl.demo.fitting;

import com.imsl.chart.*;
import com.imsl.math.*;
import java.util.Vector;
import java.awt.event.*;
import javax.swing.JOptionPane;

public class CsPanel extends javax.swing.JPanel implements ActionListener, MouseListener {

    private Chart chart;
    private AxisXY axis;
    private Data point;
    private Vector dataX, dataY;
    private double[] xRange, yRange;
    private final int numMethod = 6;
    private Data line[] = new Data[numMethod];
    private javax.swing.JFrame parentFrame;
    private javax.swing.JRadioButtonMenuItem[] jRadioButtons;
    private final int numData = 5;
    private final String dataName[] = {"Blank", "Coffee Sales", "Titanium Data", "Ocean Depth", "Sin(x) Function"};


    /** Creates new form CsPanel */
    public CsPanel(javax.swing.JFrame parent) {
        this.parentFrame = parent;
        dataX = new Vector();
        dataY = new Vector();
        
        initComponents();
        setPreferredSize(new java.awt.Dimension(parent.getSize().width, (int)(0.85*parent.getSize().height)));
        jButtonGenerate.addActionListener(this);
        jButtonEnter.addActionListener(this);
        jButtonReset.addActionListener(this);
        checkAkima.addActionListener(this);
        checkInterp.addActionListener(this);
        checkPeriod.addActionListener(this);
        checkShape.addActionListener(this);
        checkSmooth.addActionListener(this);
        checkSmooth2.addActionListener(this);
        
        addDataButtons();
        setChart();
        jPanelChart.setPreferredSize(new java.awt.Dimension(parent.getSize().width, (int)(0.66*parent.getSize().height)));
        getData();
        update();
    }
    
    private void setChart() {
        chart = jPanelChart.getChart();
        chart.getLegend().setPaint(true);
        chart.getLegend().setViewport(0.8,0.95,0.05,0.15);
        axis = new AxisXY(chart);
        axis.setViewport(0.12,0.78,0.05,0.88);

        // Set axes ranges
        axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisX().setWindow(0.0, 50.0);
        axis.getAxisY().setWindow(0.0, 75.0);
        axis.getAxisX().setTextFormat(new java.text.DecimalFormat("###.#"));
        axis.getAxisY().setTextFormat(new java.text.DecimalFormat("###.#"));
       
        // Add MouseListeren to the chart.
        jPanelChart.addMouseListener(this);
    }
    
    // add the available data sets to the dataMenu
    private void addDataButtons() {
        jRadioButtons = new javax.swing.JRadioButtonMenuItem[numData];
        javax.swing.ButtonGroup group = new javax.swing.ButtonGroup();
        
        for (int i=0; i<numData; i++) {
            jRadioButtons[i] = new javax.swing.JRadioButtonMenuItem();
            jRadioButtons[i].setText(dataName[i]);
            
            jRadioButtons[i].addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    getData();
                    update();
                }
            });
            
            group.add(jRadioButtons[i]);
            ((FittingMain)parentFrame).getDataMenu().add(jRadioButtons[i]);
        }
        jRadioButtons[1].setSelected(true);
    }        
   
    // clear and load data sets determined by the menu selection
    public void getData() {
        dataX.clear();
        dataY.clear();
        xRange = new double[2];
        yRange = new double[2];
        String xTitle = "X Value";
        String yTitle = "Y Value";
        String xFormat = "###.#";
        String yFormat = "###.#";
        int selected = -1;
        for (int i=0; i<numData; i++) {
            if (jRadioButtons[i].isSelected()) {
                selected = i;
                break;
            }
        }
        switch (selected) {
            case 0: // blank
                xRange[0] = 0.0;
                xRange[1] = 50.0;
                yRange[0] = 0.0;
                yRange[1] = 50.0;
                break;
            case 3: /* These data are the depth of the Atlantic Ocean from
                     * Cape Hatteras, North America to Cape Blanc, Africa
                     */
                double x3[] = {0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60};
                double y3[] = {0,46,52,47,52,56,55,42,25,49,55,60,58,51,42,0};
                for (int i=0; i<x3.length; i++) {
                    dataX.add(new Double(x3[i]*100));
                    dataY.add(new Double(y3[i]*-100));
                }
                xTitle = "Distance, km";
                yTitle = "Depth, m";
                xFormat = "#,###";
                yFormat = "#,###";
                xRange[0] = 0.0;
                xRange[1] = 6000.0;
                yRange[0] = -6500.0;
                yRange[1] = 0.0;
                break;
            case 1: /* These data 
                     * data discussed by Neter and Wasserman (1974,
                     *  pp. 279–285). The data set contains the response variable y measuring coffee
                     *  sales (in hundred gallons) and the number of self-service coffee dispensers.
                     */
                //double x2[] = {53,61,69,77,85,93,101,109,117,125,133,141,149,157,165,173,181,189,197,205,213,221,229,237};
                //double y2[] = {8103,6044,4694,3827,3086,3540,3941,2033,1703,1242,1310,1332,1486,2066,2888,2059,2153,1874,1919,1575,1434,1153,942,756};
                //xRange[1] = 250.0;
                //yRange[1] = 8500.0;
                
                double x1[] = {0,1,2,4,5,6,7};
                double y1[] = {508.1,568.2,651.7,755.3,787.6,841.4,854.7,};
                xRange[1] = 8;
                yRange[1] = 1000.0;

                for (int i=0; i<x1.length; i++) {
                    dataX.add(new Double(x1[i]));
                    dataY.add(new Double(y1[i]));
                }
                xTitle = "Number of Self-Service Dispensers";
                yTitle = "Coffee Sales, Hundreds of Gallons";
                xFormat = "###";
                yFormat = "###";
                xRange[0] = 0.0;
                yRange[0] = 0.0;
                break;
            case 2: /* These data are from  A Practical Guide to Splines by C. de Boor    
                     * these data represent a property of titanium as a function of
                     * temperature. They have been used extensively as an example in spline
                     * approximation with variable knots.
                     * The peak near 885 degrees C is the point of phase change to the beta phase
                 */
                double y2[] = {.644,.622,.638,.649,.652,.639,.646,.657,.652,.655,.644,.663,.663,.668,.676,.676,.686,.679,.678,.683,.694,.699,.710,.730,.763,.812,.907,1.044,1.336,1.881,2.169,2.075,1.598,1.211,.916,.746,.672,.627,.615,.607,.606,.609,.603,.601,.603,.601,.611,.601,.608};

                for (int i=0; i<y2.length; i++) {
                    dataX.add(new Double(585 + 10.0*(i+1)));
                    dataY.add(new Double(y2[i]));
                }
                xTitle = "Temperature, "+(char)186+"C";
                yTitle = "Specific Heat of Titanium, J/gK";
                xFormat = "#,###";
                yFormat = "#.#";
                xRange[0] = 550.0;
                xRange[1] = 1100.0;
                yRange[0] = 0.5;
                yRange[1] = 2.5;
                break;
            case 4: /* This is a sin() function
                     */
                int num = 14;
                for (int i=0; i<num; i++) {
                    double x = (double)i;
                    dataX.add(new Double(x));
                    dataY.add(new Double(Math.sin(x)));
                }
                xTitle = "X Value";
                yTitle = "Sin(X)";
                xFormat = "##.#";
                yFormat = "##.#";
                xRange[0] = 0.0;
                xRange[1] = num;
                yRange[0] = -2.0;
                yRange[1] = 2.0;
                break;
        }
        axis.getAxisX().getAxisTitle().setTitle(xTitle);
        axis.getAxisY().getAxisTitle().setTitle(yTitle);
        axis.getAxisX().setWindow(xRange);
        axis.getAxisY().setWindow(yRange);
        axis.getAxisX().setTextFormat(new java.text.DecimalFormat(xFormat));
        axis.getAxisY().setTextFormat(new java.text.DecimalFormat(yFormat));
    }        

    // Draw the graph.
    private void drawGraph() {
        if ((dataX.size() != 0)) {
            double[] x = new double[dataX.size()];
            double[] y = new double[dataY.size()];

            for (int i = 0; i < x.length; i++) {
                x[i] = ((Double) dataX.get(i)).doubleValue();
                y[i] = ((Double) dataY.get(i)).doubleValue();
            }

            // Draw the points.
            point = new Data(axis, x, y);
            point.setDataType(Data.DATA_TYPE_MARKER);
            point.setMarkerType(Data.MARKER_TYPE_OCTAGON_X);
            point.setMarkerSize(0.8);
            point.setMarkerColor(java.awt.Color.blue);

            // Draw the spline using CsAkima
            if (checkAkima.isSelected() && (dataX.size() > 3)) {
                final CsAkima cs0 = new CsAkima(x, y);

                ChartFunction fcn0 = new ChartFunction() {
                    public double f(double x) {
                        return cs0.value(x);
                    }
                };
                line[0] = new Data(axis, fcn0, xRange[0], xRange[1]);
                line[0].setLineColor(java.awt.Color.magenta);
                line[0].setTitle("CsAkima");
                line[0].setLineWidth(1.5);
            }

            // Draw the spline using CsInterpolate
            if (checkInterp.isSelected() && (dataX.size() > 2)) {
                final CsInterpolate cs1 = new CsInterpolate(x, y);

                ChartFunction fcn1 = new ChartFunction() {
                    public double f(double x) {
                        return cs1.value(x);
                    }
                };
                line[1] = new Data(axis, fcn1, xRange[0], xRange[1]);
                line[1].setLineColor(java.awt.Color.green);
                line[1].setTitle("CsInterpolate");
                line[1].setLineWidth(1.5);
            }

            // Draw the spline using CsPeriodic
            if (checkPeriod.isSelected() && (dataX.size() > 3)) {
                final CsPeriodic cs2 = new CsPeriodic(x, y);
                ChartFunction fcn2 = new ChartFunction() {
                    public double f(double x) {
                        return cs2.value(x);
                    }
                };
                line[2] = new Data(axis, fcn2, xRange[0], xRange[1]);
                line[2].setLineColor(java.awt.Color.orange);
                line[2].setTitle("CsPeriodic");
                line[2].setLineWidth(1.5);
            }

            // Draw the spline using CsShape
            if (checkShape.isSelected() && (dataX.size() > 2)) {
                try {
                    final CsShape cs3 = new CsShape(x, y);
                    ChartFunction fcn3 = new ChartFunction() {
                        public double f(double x) {
                            return cs3.value(x);
                        }
                    };
                    line[3] = new Data(axis, fcn3, xRange[0], xRange[1]);
                    line[3].setLineColor(java.awt.Color.blue);
                    line[3].setTitle("CsShape");
                    line[3].setLineWidth(1.5);
                } catch (CsShape.TooManyIterationsException e) {
                    System.out.println("Too many iterations");
                } catch (SingularMatrixException e) {
                    System.out.println("Singular matrix");
                }
            }

            // Draw the spline using CsSmooth
            if (checkSmooth.isSelected() && (dataX.size() > 2)) {
                final CsSmooth cs4 = new CsSmooth(x, y);
                ChartFunction fcn4 = new ChartFunction() {
                    public double f(double x) {
                        return cs4.value(x);
                    }
                };
                line[4] = new Data(axis, fcn4, xRange[0], xRange[1]);
                line[4].setLineColor(java.awt.Color.cyan);
                line[4].setTitle("CsSmooth");
                line[4].setLineWidth(1.5);
            }

            // Draw the spline using CsSmooth2
            if (checkSmooth2.isSelected()) {
                // Set the weights
                final double sdev = 1./Math.sqrt(3.);
                double weights[]   = new double[dataX.size()];
                for (int i = 0; i < dataX.size(); i++) {
                    weights[i] = sdev;
                }
                //  Set the smoothing parameter
                double smpar = (double)dataX.size();
                
                final CsSmoothC2 cs5 = new CsSmoothC2(x, y, weights, smpar);
                ChartFunction fcn5 = new ChartFunction() {
                    public double f(double x) {
                        return cs5.value(x);
                    }
                };
                line[5] = new Data(axis, fcn5, xRange[0], xRange[1]);
                line[5].setLineColor(java.awt.Color.red);
                line[5].setTitle("CsSmoothC2");
                line[5].setLineWidth(1.5);
            }
        }
    }

    // Redraw the chart.
    private void update() {
        for (int i = 0; i<numMethod; i++) {
            if (line[i] != null) line[i].remove();
        }
        if (point != null) point.remove();
        drawGraph();
        repaint();
    }

    public void setVisible(boolean show) {
        super.setVisible(show);
        if (show) {
            addDataButtons();
        }
    }
    
    /** 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.
     */
    private void initComponents() {//GEN-BEGIN:initComponents
        jPanel = new javax.swing.JPanel();
        jButtonEnter = new javax.swing.JButton();
        jButtonGenerate = new javax.swing.JButton();
        jLabelPoints = new javax.swing.JLabel();
        numField = new javax.swing.JTextField("6", 4);
        jButtonReset = new javax.swing.JButton();
        jPanel1 = new javax.swing.JPanel();
        jLabel2 = new javax.swing.JLabel();
        jPanelChecks = new javax.swing.JPanel();
        checkAkima = new javax.swing.JCheckBox();
        checkInterp = new javax.swing.JCheckBox();
        checkPeriod = new javax.swing.JCheckBox();
        checkShape = new javax.swing.JCheckBox();
        checkSmooth = new javax.swing.JCheckBox();
        checkSmooth2 = new javax.swing.JCheckBox();
        jPanelChart = new com.imsl.chart.JPanelChart();

        setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));

        setPreferredSize(new java.awt.Dimension(500, 600));
        jPanel.setMaximumSize(new java.awt.Dimension(32767, 36));
        jButtonEnter.setText("Enter Points");
        jPanel.add(jButtonEnter);

        jButtonGenerate.setText("Generate");
        jPanel.add(jButtonGenerate);

        jLabelPoints.setText("# of Points: ");
        jPanel.add(jLabelPoints);

        numField.setColumns(4);
        numField.setText("6");
        jPanel.add(numField);

        jButtonReset.setText("Reset");
        jPanel.add(jButtonReset);

        add(jPanel);

        jPanel1.setMaximumSize(new java.awt.Dimension(32767, 20));
        jPanel1.setMinimumSize(new java.awt.Dimension(93, 20));
        jPanel1.setPreferredSize(new java.awt.Dimension(93, 20));
        jLabel2.setText("Select Cubic Spline Models:");
        jPanel1.add(jLabel2);

        add(jPanel1);

        jPanelChecks.setMaximumSize(new java.awt.Dimension(32767, 28));
        jPanelChecks.setMinimumSize(new java.awt.Dimension(468, 28));
        jPanelChecks.setPreferredSize(new java.awt.Dimension(468, 28));
        checkAkima.setText("Akima");
        jPanelChecks.add(checkAkima);

        checkInterp.setSelected(true);
        checkInterp.setText("Interpolate");
        jPanelChecks.add(checkInterp);

        checkPeriod.setText("Periodic");
        jPanelChecks.add(checkPeriod);

        checkShape.setText("Shape");
        jPanelChecks.add(checkShape);

        checkSmooth.setText("Smooth");
        jPanelChecks.add(checkSmooth);

        checkSmooth2.setText("SmoothC2");
        jPanelChecks.add(checkSmooth2);

        add(jPanelChecks);

        jPanelChart.setMinimumSize(new java.awt.Dimension(500, 500));
        jPanelChart.setPreferredSize(new java.awt.Dimension(500, 500));
        add(jPanelChart);

    }//GEN-END:initComponents
    
    // Implement ActionListener
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("Enter Points")) {
            // Enter points using a text area.
            javax.swing.JTextArea textArea = new javax.swing.JTextArea(10, 5);
            javax.swing.JScrollPane scroll = new javax.swing.JScrollPane(textArea);

            int option = 0;
            try {
                option = JOptionPane.showConfirmDialog(this, scroll,
                    "Enter Points", JOptionPane.OK_CANCEL_OPTION);
                if (option == JOptionPane.CANCEL_OPTION) return;
            } catch (Exception ex) {
            }

            java.util.StringTokenizer lineToken = new java.util.StringTokenizer(textArea.getText(), "\n");
            boolean dup = false;
            while (lineToken.hasMoreElements()) {
                java.util.StringTokenizer token = new java.util.StringTokenizer(lineToken.nextToken(), "(){}[] ,\t\n");
                // If the line does not contain 2 numbers, then ignore.
                if (token.countTokens() == 2) {
                    try {
                        double x = Double.parseDouble(token.nextToken());
                        double y = Double.parseDouble(token.nextToken());

                        // Ignore any points that are not in the range.
                        if ((x >= xRange[0]) && (x <= xRange[1]) &&
                            (y >= yRange[0]) && (y <= yRange[1])) {
                            // Only add if the x value is distinct or else
                            // we'll throw a nasty java.lang.IllegalArgumentException
                            for (int i = 0; i < dataX.size(); i++) {
                                if (((Double) dataX.get(i)).doubleValue() == x) {
                                    dup = true;
                                }
                            }
                            if (!dup) {
                                dataX.add(new Double(x));
                                dataY.add(new Double(y));
                            }
                        }
                    } catch (Exception ex) {
                    }
                }
            }
            if (dup) {
                JOptionPane.showMessageDialog(this, "X values must be distinct.\n" +
                        "Duplicates have not been added.",
                        "Data Error", JOptionPane.ERROR_MESSAGE);
            }
            update();
        } else if (e.getActionCommand().equals("Generate")) {
            int i = 0;
            try {
                i = Integer.parseInt(numField.getText());
            } catch (Exception exception) {
                i = 6;
                numField.setText("6");
            } finally {
                if (i <= 0) {
                    i = 6;
                    numField.setText("6");
                }
                for (int j = 0; j < i; j++) {
                    dataX.add(new Double(xRange[1] * Math.random() + xRange[0]));
                    dataY.add(new Double(yRange[1] * Math.random() + yRange[0]));
                }
                update();
            }
        } else if (e.getActionCommand().equals("Reset")) {
            numField.setText("6");
            dataX = new Vector();
            dataY = new Vector();
            update();
            getData();
            update();
        } else {    // this means a checkbox is changed
            update();
        }
    }


    // Implement MouseListener
    public void mouseClicked(MouseEvent e) {
        double user[] = {0,0};
        axis.mapDeviceToUser(e.getX(), e.getY(), user);

        // Do not add the point if it is not in the range.
        if ((user[0] < xRange[0]) || (user[0] > xRange[1]) ||
            (user[1] < yRange[0]) || (user[1] > yRange[1])) return;

        // Button is to add points.  Otherwise, remove points.
        if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {
            // But only add if the x value is distinct or else
            // Splines throw a java.lang.IllegalArgumentException
            boolean dup = false;
            for (int i = 0; i < dataX.size(); i++) {
                if (((Double) dataX.get(i)).doubleValue() == (double) user[0]) {
                    dup = true;
                }
            }
            if (dup) {
                JOptionPane.showMessageDialog(this, "X values must be distinct.",
                        "Data Error", JOptionPane.ERROR_MESSAGE);
            } else {
                dataX.add(new Double(user[0]));
                dataY.add(new Double(user[1]));
                update();
            }
        } else {
            int idx = -1;
            double min = 0.01;
            final double ux = (user[0]-xRange[0])/(double)(xRange[1]-xRange[0]);
            final double uy = (user[1]-yRange[0])/(double)(yRange[1]-yRange[0]);
            
            for (int i = 0; i < dataX.size(); i++) {
                double x = (((Double)dataX.get(i)).doubleValue()-xRange[0])/
                            (double)(xRange[1]-xRange[0]);
                double y = (((Double)dataY.get(i)).doubleValue()-yRange[0])/
                            (double)(yRange[1]-yRange[0]);
                double dist = Math.sqrt((ux - x)*(ux - x) + (uy - y)*(uy - y));

                if (dist < min) {
                    min = dist;
                    idx = i;
                }
            }

            if (idx != -1) {
                dataX.remove(idx);
                dataY.remove(idx);
                update();
            }
        }
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox checkSmooth;
    private javax.swing.JLabel jLabelPoints;
    private javax.swing.JButton jButtonGenerate;
    private javax.swing.JCheckBox checkShape;
    private javax.swing.JButton jButtonReset;
    private javax.swing.JTextField numField;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JCheckBox checkInterp;
    private javax.swing.JCheckBox checkPeriod;
    private com.imsl.chart.JPanelChart jPanelChart;
    private javax.swing.JCheckBox checkSmooth2;
    private javax.swing.JCheckBox checkAkima;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JButton jButtonEnter;
    private javax.swing.JPanel jPanel;
    private javax.swing.JPanel jPanelChecks;
    // End of variables declaration//GEN-END:variables
}