/*
 * -------------------------------------------------------------------------
 * $Id: Basis.java,v 1.3 2004/05/26 18:00:47 estewart Exp $
 * -------------------------------------------------------------------------
 * Copyright (c) 2002 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.
 * --------------------------------------------------------------------------
 */

package com.imsl.demo.Basis;
import com.imsl.stat.*;
import com.imsl.math.*;
import com.imsl.chart.*;
import javax.swing.*;

/**
 *  Basis creates a Chart where the user can select data to fit with several
 *  different user basis functions.
 *
 *@author     Ed Stewart
 *@created    September 25, 2002
 */
public class Basis extends JFrameChart {
    
    private JComboBox jComboBoxUser;
    private JComboBox jComboBoxFunc;
    private JTextArea displayText;
    private JLabel jLabelSNC;
    private JScrollBar jScrollCoef;
    private Chart chart;
    private AxisXY axis, axis2;
    private Data lineUser, lineFunc;
    
    private UserBasisRegression ubr;
    private final int npoints = 100;
    private final int defCoef = 4;
    private final double[] xrange = {0.0, 10.0};
    private final double[] yrange = {-15.0, 15.0};
    private String textFunc, textUser;
    private double x[], y[], f[], rand[];
    
    
    /**
     *  Constructor for the Basis object sets default values and calls methods
     *  to create the chart, draw the graph and build the user interface.
     *
     *@param  exitOnClose  If false, remove the WindowListener installed by
     *      JFrameChart.
     */
    private Basis(boolean exitOnClose) {
        if (!exitOnClose) {
            Object l[] = getListeners(java.awt.event.WindowListener.class);
            for (int k = 0; k < l.length; k++) {
                removeWindowListener((java.awt.event.WindowListener) l[k]);
            }
        }
        
        com.imsl.demo.gallery.Describe des = new com.imsl.demo.gallery.Describe(this, "/com/imsl/demo/Basis/Basis.html");
        des.show();
        java.awt.Dimension dess = des.getSize();
        
        java.awt.Dimension ss = getToolkit().getScreenSize();
        int h = Math.min(ss.width/2, ss.height-dess.height-32);
        int w = (int)(h/0.8);
        setSize(w, h);
        setLocation(ss.width-dess.width, dess.height);
        setTitle("User Basis Regression");
        
        // Set default values
        final double pi = Physical.constant("PI").doubleValue();
        int ncoef = defCoef;
        double[] coef = new double[ncoef];
        x = new double[npoints];
        rand = new double[npoints];
        Random r = new Random();
        for (int i = 0; i < npoints; i++) {
            x[i] = 3 * pi * (double) i / npoints;
            rand[i] = r.nextDouble();
        }
        
        createChart();
        drawGraph();
        initComponents();
    }
    
    
    /** Spawns the Basis class by calling show() */
    public static void main(String args[]) {
        boolean exitOnClose = true;
        if (args.length > 0 && args[0].equals("-noexit")) {
            exitOnClose = false;
        }
        new Basis(exitOnClose).show();
    }
    
    
    /** Pop up a dialog with the coefficients. */
    public void detailButtonActionPerformed(java.awt.event.ActionEvent event) {
        int width = 35;
        java.text.MessageFormat mf = new java.text.MessageFormat(textUser);
        JPanel cPanel = new JPanel(new java.awt.BorderLayout());
        JTextArea tArea = new JTextArea(" ", 2, width);
        
        Object kargs[] = {new String("k")};
        String kusr = mf.format(kargs);
        tArea.setText("Leading coefficients of " + kusr + " to fit " + textFunc);
        
        int ncoef = jScrollCoef.getValue();
        JTextArea cArea = new JTextArea(" ", ncoef+1, width);
        StringBuffer sb = new StringBuffer("\n");
        double[] coef = new double[ncoef];
        coef = ubr.getCoefficients();
        java.text.DecimalFormat fmt = new java.text.DecimalFormat("##0.0000");
        
        for (int i = 0; i < ncoef; i++) {
            sb.append("Coefficient #" + (i+1) + " = " + fmt.format(coef[i]) + "\n");
        }
        cArea.setText(sb.toString());
        cArea.setEditable(false);
        cArea.setBackground(cPanel.getBackground());
        
        JTextArea eArea = new JTextArea(" ", 2, width);
        sb = new StringBuffer("Analytical equation of the estimate:\ny = ");
        boolean addText = false;
        int addLine = 0;
        java.text.DecimalFormat shortfmt = new java.text.DecimalFormat("##0.####");
        
        for (int i = 0; i < ncoef; i++) {
            Object args[] = {new Integer(i+1)};            
            String usr = mf.format(args);
            String z = fmt.format(JMath.abs(coef[i]));
            if (z.compareTo("0.0000") > 0) {
                if (addText) {
                    sb.append(" + ");
                }
                if (sb.length() > (width*(addLine+1))*2) {
                    sb.append("\n    ");
                    addLine++;
                }
                if (textUser.indexOf(" ") == -1) {
                    sb.append(shortfmt.format(coef[i]) + "*" + usr);
                } else {
                    sb.append(shortfmt.format(coef[i]) + "*[" + usr +"]");
                }
                addText = true;
            }
        }
        if (!addText) {
            if (textUser.indexOf(" ") == -1) {
                sb.append("0 * " + kusr);
            } else {
                sb.append("0 * [" + kusr +"]");
            }
        }
        eArea.setText(sb.toString());
        
        cPanel.add(tArea, java.awt.BorderLayout.NORTH);
        cPanel.add(cArea, java.awt.BorderLayout.CENTER);
        cPanel.add(eArea, java.awt.BorderLayout.SOUTH);
        JOptionPane.showMessageDialog(this, cPanel, "Coefficients", JOptionPane.INFORMATION_MESSAGE);
    }
    
    private String getDescription() {
        StringBuffer sb = new StringBuffer();
        sb.append("Analytical Function: " + textFunc);
        java.text.MessageFormat mf = new java.text.MessageFormat(textUser);
        Object kargs[] = {new String("k")};
        String kusr = mf.format(kargs);
        sb.append("\nUser Basis Function: " + kusr);
        return sb.toString();
    }
    
    
    private void createChart() {
        // Chart
        chart = getChart();
        axis = new AxisXY(chart);
        axis.setViewport(0.05, 0.98, 0.05, 0.95);
        chart.getLegend().setPaint(true);
        chart.getLegend().setViewport(0.70, 0.90, 0.10, 0.05);
        
        // Set x-axis propteries
        axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisX().setWindow(xrange);
        axis.getAxisX().setDensity(2);
        axis.getAxisX().setNumber(11);
        axis.getAxisX().setTickLength(-1);
        axis.getAxisX().setTextFormat(new java.text.DecimalFormat("#0"));
        
        // Set y-axis properties
        axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisY().setWindow(yrange);
        axis.getAxisY().setDensity(2);
        axis.getAxisY().setNumber(7);
        axis.getAxisY().setTextFormat(new java.text.DecimalFormat("##0"));
        axis.getAxisY().setTickLength(-1);
        
        // Add another AxisXY for the boxed look
        axis2 = new AxisXY(chart);
        axis2.setViewport(0.05, 0.98, 0.05, 0.95);
        axis2.getAxisX().setType(AxisXY.AXIS_X_TOP);
        axis2.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis2.getAxisX().setWindow(xrange);
        axis2.getAxisX().setDensity(2);
        axis2.getAxisX().setNumber(11);
        axis2.getAxisX().setTickLength(-1);
        axis2.getAxisX().setTextColor(java.awt.Color.white);
        axis2.getAxisY().setType(AxisXY.AXIS_Y_RIGHT);
        axis2.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis2.getAxisY().setWindow(yrange);
        axis2.getAxisY().setDensity(2);
        axis2.getAxisY().setNumber(11);
        axis2.setCross(xrange[1], yrange[1]);
        axis2.getAxisY().setTickLength(-1);
        axis2.getAxisY().setTextColor(java.awt.Color.white);
    }
    
    
    private void initComponents() {
        java.awt.Container cp = getContentPane();
        JPanel msgPanel = new JPanel();
        displayText = new JTextArea(getDescription(), 2, 20);
        displayText.setEditable(false);
        JButton detailButton = new JButton("Show Coefficients");        
        detailButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                detailButtonActionPerformed(evt);
            }
        });
        msgPanel.add(displayText);
        msgPanel.add(detailButton);
        cp.add(msgPanel, java.awt.BorderLayout.NORTH);
        
        JPanel southPanel = new JPanel();
        
        // Function selection ComboBox
        Label labelFn[] = {
            new Label("Function 1 (sin)", 0),
            new Label("Function 2 (cos)", 1),
            new Label("Function 3 (sin*cos)", 2),
            new Label("Function 4 (sin*Random)", 3)
        };
        jComboBoxFunc = new JComboBox();
        for (int i = 0; i < labelFn.length; i++) {
            jComboBoxFunc.addItem(labelFn[i]);
        }
        jComboBoxFunc.setSelectedIndex(0);
        jComboBoxFunc.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jComboBoxFuncActionPerformed(evt);
            }
        });
        
        // User Basis Function selection ComboBox
        Label labelUs[] = {
            new Label("sin(kx)", 0),
            new Label("cos(kx)", 1),
            new Label("sin(kx) * cos(kx)", 2),
            new Label("sin(kx) + cos(kx)", 3)
            //new Label("sinh(kx)", 4),
            //new Label("cosh(kx)", 5)
        };
        jComboBoxUser = new JComboBox();
        for (int i = 0; i < labelUs.length; i++) {
            jComboBoxUser.addItem(labelUs[i]);
        }
        jComboBoxUser.setSelectedIndex(0);
        jComboBoxUser.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jComboBoxUserActionPerformed(evt);
            }
        });
        
        jLabelSNC = new JLabel();
        jLabelSNC.setText("# Coefficients = " + defCoef);
        jScrollCoef = new JScrollBar(java.awt.Scrollbar.HORIZONTAL);
        jScrollCoef.setValues(defCoef, 1, 1, 9);
        jScrollCoef.setBlockIncrement(1);
        jScrollCoef.setPreferredSize(new java.awt.Dimension(100,15));
        jScrollCoef.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
            public void adjustmentValueChanged(java.awt.event.AdjustmentEvent avt) {
                jScrollCoefValueChanged(avt);
            }
        });
        
        // Add components to the panel
        southPanel.add(jComboBoxFunc);
        southPanel.add(jComboBoxUser);
        southPanel.add(jLabelSNC);
        southPanel.add(jScrollCoef);
        cp.add(southPanel, java.awt.BorderLayout.SOUTH);
    }
    
    
    /** Draw the graph in the chart. The input data and the Basis Regression
     *  computed from the coefficients are plotted.
     */
    private void drawGraph() {
        // Get number of coefficients to use
        int ncoef = defCoef;
        if (jScrollCoef != null) {
            ncoef = jScrollCoef.getValue();
        }
        
        // Defined the UserBasis here
        int val = 0;
        if (jComboBoxUser != null) {
            val = ((Label) jComboBoxUser.getSelectedItem()).index;
        }
        
        switch (val) {
            case 0:     /* Sin */
                ubr = new UserBasisRegression(new BasisSin(), ncoef, false);
                textUser = "sin({0}x)";
                break;
            case 1:     /* Cos */
                ubr = new UserBasisRegression(new BasisCos(), ncoef, false);
                textUser = "cos({0}x)";
                break;
            case 2:     /* Prod */
                ubr = new UserBasisRegression(new BasisProd(), ncoef, false);
                textUser = "sin({0}x) * cos({0}x)";
                break;
            case 3:     /* Sum */
                ubr = new UserBasisRegression(new BasisSum(), ncoef, false);
                textUser = "sin({0}x) + cos({0}x)";
                break;
            case 4:      /* Sinh */
                ubr = new UserBasisRegression(new BasisSinh(), ncoef, false);
                textUser = "sinh({0}x)";
                break;
            case 5:     /* Cosh */
                ubr = new UserBasisRegression(new BasisCosh(), ncoef, false);
                textUser = "cosh({0}x)";
                break;
        }
        
        // Draw the function
        f = computeFunc(x);     // computeFunc updates the ubr
        lineFunc = new Data(axis, x, f);
        lineFunc.setLineColor(java.awt.Color.blue);
        lineFunc.setLineWidth(2.0);
        lineFunc.setTitle("Analytical Function");
        
        // Draw the estime
        y = computeUser(x);     // computeUser retrives coefs from ubr
        lineUser = new Data(axis, x, y);
        lineUser.setLineColor(java.awt.Color.red);
        lineUser.setLineWidth(0.5);
        lineUser.setLineDashPattern(Data.DASH_PATTERN_DOT);
        lineUser.setTitle("User Basis Regression");
    }
    
    
    /** Compute the line for the function to plot
     *
     *@param  xx  The independent values
     *@return     The dependent values for the input function f(x)
     */
    private double[] computeFunc(double[] xx) {
        double[] ff = new double[xx.length];
        int val = 0;
        if (jComboBoxFunc != null) {
            val = ((Label) jComboBoxFunc.getSelectedItem()).index;
        }
        switch (val) {
            case 0:     /* function 1 */
                for (int i = 0; i < npoints; i++) {
                    ff[i] = JMath.sin(xx[i]) + 7.0 * JMath.sin(3.0 * xx[i]);
                    ubr.update(xx[i], ff[i], 1.0);
                }
                textFunc = "sin(x) + 7sin(3x)";
                break;
            case 1:     /* function 2 */
                for (int i = 0; i < npoints; i++) {
                    ff[i] = JMath.cos(xx[i]) + 7.0 * JMath.cos(3.0 * xx[i]);
                    ubr.update(xx[i], ff[i], 1.0);
                }
                textFunc = "cos(x) + 7cos(3x)";
                break;
            case 2:     /* function 3 */
                for (int i = 0; i < npoints; i++) {
                    ff[i] = 10.0 * JMath.cos(xx[i]) * JMath.sin(xx[i]);
                    ubr.update(xx[i], ff[i], 1.0);
                }
                textFunc = "10 * cos(x) * sin(x)";
                break;
            case 3:     /* function 4 */
                for (int i = 0; i < npoints; i++) {
                    ff[i] = 8.0 * JMath.sin(2.0 * xx[i]) * rand[i];
                    ubr.update(xx[i], ff[i], 1.0);
                }
                textFunc = "8sin(2x) * Uniform Random [0,1]";
                break;
            //case 4:       /* file */
                /* An original part of the demo. It would be easy to read
                 * in your own file to extend the functionality of this
                 * demonstration 
                textFunc = "file y.dat";
                java.io.File file = new java.io.File("y.dat");
                try {
                    java.io.BufferedReader br = new java.io.BufferedReader(
                    new java.io.InputStreamReader(
                    new java.io.BufferedInputStream(
                    new java.io.FileInputStream(file))));
                    for (int i = 0; i < npoints; i++) {
                        String s = br.readLine();
                        try {
                            ff[i] = Double.valueOf(s.trim()).doubleValue();
                        } catch (NumberFormatException nfe) {
                            System.out.println("NumberFormatException: " + nfe.getMessage());
                        }
                    }
                    br.close();
                } catch (java.io.IOException e) {
                    System.err.println("FileIO: " + e);
                }
                break;
                 **/
        }
        return ff;
    }
    
    
    /** Compute the user basis function estimate to plot
     *
     *@param  xx  The independent values
     *@return     The dependent values for the user basis function y(x)
     */
    private double[] computeUser(double[] xx) {
        double[] yy = new double[xx.length];
        int ncoef = defCoef;
        if (jScrollCoef != null) {
            ncoef = jScrollCoef.getValue();
        }
        double[] coef = new double[ncoef];
        coef = ubr.getCoefficients();
        int val = 0;
        if (jComboBoxUser != null) {
            val = ((Label) jComboBoxUser.getSelectedItem()).index;
        }
        
        for (int i = 0; i < npoints; i++) {
            double sum = 0.0;
            double tmp = 0.0;
            for (int j = 0; j < ncoef; j++) {
                switch (val) {
                    case 0:     /* Sin */
                        tmp = JMath.sin((double) (j + 1) * x[i]);
                        break;
                    case 1:     /* Cos */
                        tmp = JMath.cos((double) (j + 1) * x[i]);
                        break;
                    case 2:     /* Prod */
                        tmp = JMath.sin((double) (j + 1) * x[i]) * JMath.cos((double) (j + 1) * x[i]);
                        break;
                    case 3:     /* Sum */
                        tmp = JMath.sin((double) (j + 1) * x[i]) * JMath.sin((double) (j + 1) * x[i]);
                        break;
                    case 4:     /* Sinh */
                        tmp = Hyperbolic.sinh((double) (j + 1) * x[i]);
                        break;
                    case 5:     /* Cosh */
                        tmp = Hyperbolic.sinh((double) (j + 1) * x[i]);
                        break;
                }
                sum = sum + coef[j] * tmp;
            }
            yy[i] = sum;
        }
        return yy;
    }
    
    
    /** Update the chart by removing all the Data objects, then calling
     *  drawGraph() and repaint(). Finally update the displayText.
     */
    private void update() {
        if (lineFunc != null) {
            lineFunc.remove();
        }
        if (lineUser != null) {
            lineUser.remove();
        }
        drawGraph();
        repaint();
        displayText.setText(getDescription());
    }
    
    
    /** Update when a new function is selected */
    private void jComboBoxFuncActionPerformed(java.awt.event.ActionEvent evt) {
        update();
    }
    
    
    /** Update when a new user basis is selected */
    private void jComboBoxUserActionPerformed(java.awt.event.ActionEvent evt) {
        update();
    }
    
    
    /** Adjusting scrollbar changes the number of coefficients, so update() */
    private void jScrollCoefValueChanged(java.awt.event.AdjustmentEvent event) {
        jLabelSNC.setText("# Coefficients = " + jScrollCoef.getValue());
        update();
    }
    
    /** Label objects are used in conjunction with the JComboBox. */
    private static class Label {
        String label;
        int index;
        
        Label(String label, int index) {
            this.label = label;
            this.index = index;
        }
        
        public String toString() {
            return label;
        }
    }

    /** Basis functions are below this point
     *  Each function is best summarized by these docs where
     *  f is the basis function to use (e.g., JMath.sin())
     *
     *  Compute f(kx)
     *
     *@param  index  value of k-1
     *@param  x      independent value
     *@return        f(k*x)
     */
    
    /** Sine basis function. */
    class BasisSin implements RegressionBasis {
        public double basis(int index, double x) {
            return JMath.sin((index + 1) * x);
        }
    }
    
    /** Cosine basis function. */
    class BasisCos implements RegressionBasis {
        public double basis(int index, double x) {
            return JMath.cos((index + 1) * x);
        }
    }
    
    /** Product of sine and cosine basis function. */
    class BasisProd implements RegressionBasis {
        public double basis(int index, double x) {
            return JMath.cos((index + 1) * x) * JMath.sin((index + 1) * x);
        }
    }
    
    /** Sum of sine and cosine basis function*/
    class BasisSum implements RegressionBasis {
        public double basis(int index, double x) {
            return JMath.cos((index + 1) * x) + JMath.sin((index + 1) * x);
        }
    }
    
    /** Exponential basis function. */
    class BasisExp implements RegressionBasis {
        public double basis(int index, double x) {
            return JMath.exp((index + 1) * x);
        }
    }
    
    /** Natural log basis function. */
    class BasisLog implements RegressionBasis {
        public double basis(int index, double x) {
            return JMath.log((index + 1) * x);
        }
    }
    
    /** Hyperbolic sine basis function. */
    class BasisSinh implements RegressionBasis {
        public double basis(int index, double x) {
            return Hyperbolic.sinh((index + 1) * x);
        }
    }
    
    /** Hyperbolic cosine basis function. */
    class BasisCosh implements RegressionBasis {
        public double basis(int index, double x) {
            return Hyperbolic.cosh((index + 1) * x);
        }
    }
}