/*
 * -------------------------------------------------------------------------
 *      $Id: KFilt.java,v 1.3 2004/05/26 18:22:42 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.
 *--------------------------------------------------------------------------
 */

package com.imsl.demo.Kalman;
import com.imsl.chart.*;
import com.imsl.demo.gallery.Describe;
import javax.swing.*;

/**
 *  Create a Chart from data in a file that is filtered with
 *  com.imsl.stat.KalmanFilter and shown with a moving average.
 *
 * @author     Ed Stewart
 * @created    September 25, 2002
 */
public class KFilt extends JFrameChart implements java.awt.event.MouseListener {

    private JComboBox jComboBoxMA;
    private JComboBox jComboBoxNY;
    private JComboBox jComboBoxQR;
    private JTextArea displayText;
    private Chart chart;
    private AxisXY axis, axis2;
    private Data lineData, lineKal, lineMov;
    private Data pointData, pointKal, pointMov;
    private double x[], y[], xall[], yall[], f[], m[];
    private int npoint, nyear;
    private int yearIndex[];
    static final private int CHARTPOINTS = 0;
    static final private int CHARTLINES = 1;
    static final private int CHARTAXES = 2;


    public KFilt(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]);
            }
        }

        Describe des = new Describe(this, "/com/imsl/demo/Kalman/Kalman.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("Kalman Filter");

        // the default data
        npoint = 6;
        nyear = 2;

        readFile("dj5.dat");
        createChart();
        drawGraph();
        initComponents();
    }


    /**
     *  main method
     *
     *@param  args  if "-noexit" then exitOnClose is set to false.
     */
    public static void main(String args[]) {
        boolean exitOnClose = true;
        if (args.length > 0 && args[0].equals("-noexit")) {
            exitOnClose = false;
        }
        new KFilt(exitOnClose).show();
    }


    /**
     *  Mouse button clicked in the graph clears previous points and draws
     *  points at the current location. The displayText area is updated with
     *  values.
     *
     *@param  evt  The moust event.
     */
    public void mouseClicked(java.awt.event.MouseEvent evt) {
        removeChart(CHARTPOINTS);
        double user[] = {0, 0};
        axis.mapDeviceToUser(evt.getX(), evt.getY(), user);
        double xRange[] = axis.getAxisX().getWindow();
        double yRange[] = axis.getAxisY().getWindow();

        // Return if click is outside axis
        if ((user[0] < xRange[0]) || (user[0] > xRange[1]) ||
                (user[1] < yRange[0]) || (user[1] > yRange[1])) {
            return;
        }

        // Add the points
        int idx = (int) Math.round((x.length - 1) * (user[0] - x[0]) /
                (x[x.length - 1] - x[0]));
        double[] px = {x[idx]};
        double[] py = {y[idx]};
        double[] pf = {f[idx]};
        pointData = new Data(axis, px, py);
        pointData.setDataType(Data.DATA_TYPE_MARKER);
        pointData.setMarkerType(Data.MARKER_TYPE_ASTERISK);
        pointData.setMarkerSize(0.8);
        pointData.setMarkerColor(java.awt.Color.blue);
        pointKal = new Data(axis, px, pf);
        pointKal.setDataType(Data.DATA_TYPE_MARKER);
        pointKal.setMarkerType(Data.MARKER_TYPE_ASTERISK);
        pointKal.setMarkerSize(0.8);
        pointKal.setMarkerColor(java.awt.Color.red);

        // Update the text region with values
        java.text.DecimalFormat fmt = new java.text.DecimalFormat("###.00");
        if (idx < npoint / 2 || idx > x.length - 1 - npoint / 2) {
            displayText.setText("Data Value = " + y[idx] +
                    "\nKalman Filtered Value = " + fmt.format(f[idx]));
        } else {
            double[] pm = {m[idx - npoint / 2]};
            pointMov = new Data(axis, px, pm);
            pointMov.setDataType(Data.DATA_TYPE_MARKER);
            pointMov.setMarkerType(Data.MARKER_TYPE_ASTERISK);
            pointMov.setMarkerSize(0.8);
            pointMov.setMarkerColor(java.awt.Color.magenta);
            displayText.setText("Data Value = " + y[idx] +
                    "\nKalman Filtered Value = " + fmt.format(f[idx]) +
                    "\nMoving Average Value = " + fmt.format(m[idx - npoint / 2]));
        }
        repaint();
    }


    public void mousePressed(java.awt.event.MouseEvent event) {
    }

    public void mouseReleased(java.awt.event.MouseEvent event) {
    }

    public void mouseEntered(java.awt.event.MouseEvent event) {
    }

    public void mouseExited(java.awt.event.MouseEvent event) {
    }


    /**
     *  Initializes the user interface built with Swing components.
     */
    private void initComponents() {
        // Set up the panel
        java.awt.Container cp = getContentPane();
        JPanel msgPanel = new JPanel();
        displayText = new JTextArea(" ", 3, 20);
        displayText.setEditable(false);
        msgPanel.add(displayText);
        cp.add(msgPanel, java.awt.BorderLayout.NORTH);

        // Create control panel to include comboboxes and button
        JPanel southPanel = new JPanel();

        // q/r ration selection ComboBox
        Label labelQR[] = {
                new Label("q/r = 0.001", 0),
                new Label("q/r = 0.005", 1),
                new Label("q/r = 0.01", 2),
                new Label("q/r = 0.05", 3),
                new Label("q/r = 0.1", 4),
                new Label("q/r = 0.5", 5),
                new Label("q/r = 1.0", 6),
                new Label("q/r = 5.0", 7)
                };
        jComboBoxQR = new JComboBox();
        for (int i = 0; i < labelQR.length; i++) {
            jComboBoxQR.addItem(labelQR[i]);
        }
        jComboBoxQR.setSelectedIndex(4);
        jComboBoxQR.addActionListener(
            new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jComboBoxQRActionPerformed(evt);
                }
            });

        // Moving average ComboBox
        Label labelMA[] = {
                new Label("2 pt. Moving Avg.", 0),
                new Label("6 pt. Moving Avg.", 1),
                new Label("10 pt. Moving Avg.", 2)
                };
        jComboBoxMA = new JComboBox();
        for (int i = 0; i < labelMA.length; i++) {
            jComboBoxMA.addItem(labelMA[i]);
        }
        jComboBoxMA.setSelectedIndex(1);
        jComboBoxMA.addActionListener(
            new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jComboBoxMAActionPerformed(evt);
                }
            });

        // Amount of Data ComboBox
        Label labelNY[] = {
                new Label("1 Year", 0),
                new Label("2 Years", 1),
                new Label("3 Years", 2),
                new Label("4 Years", 3),
                new Label("5 Years", 4)
                };
        jComboBoxNY = new JComboBox();
        for (int i = 0; i < labelNY.length; i++) {
            jComboBoxNY.addItem(labelNY[i]);
        }
        jComboBoxNY.setSelectedIndex(nyear-1);
        jComboBoxNY.addActionListener(
            new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jComboBoxNYActionPerformed(evt);
                }
            });

        // Add components to the chart
        southPanel.add(jComboBoxNY);
        southPanel.add(jComboBoxQR);
        southPanel.add(jComboBoxMA);
        cp.add(southPanel, java.awt.BorderLayout.SOUTH);
        displayText.setText("Click in the chart area for exact values.");
    }


    /**
     *  Read data from a file. Returns void because two arrays are filled.
     *
     *@param  fileName  The file name to read.
     */
    private void readFile(String fileName) {
        //java.io.File file = new java.io.File(fileName);
        java.util.Vector dateVect = new java.util.Vector();
        java.util.Vector valVect = new java.util.Vector();
        int count = 0;
        boolean done = false;
        try {
            java.io.InputStream is = getClass().getResourceAsStream(fileName);
            java.io.BufferedReader br = new java.io.BufferedReader(
                    new java.io.InputStreamReader(is));
            while (!done) {
                try {
                    String l = br.readLine();
                    if (l == null) {
                        done = true;
                        br.close();
                    } else {
                        String[] s = l.split(",");
                        valVect.addElement(Double.valueOf(s[1].trim()));
                        dateVect.addElement(s[0]);
                        count++;
                    }
                } catch (NumberFormatException nfe) {
                    System.out.println("NumberFormatException: " + nfe.getMessage());
                } catch (java.io.EOFException eof) {
                    done = true;
                    br.close();
                } catch (ArrayIndexOutOfBoundsException obe) {
                    done = true;
                    br.close();
                }
            }
        } catch (java.io.IOException ioe) {
            System.err.println("FileIO: " + ioe.getMessage());
        }
        xall = new double[dateVect.size()];
        yall = new double[valVect.size()];
        yearIndex = new int[5];
        int prevd = 1900;
        int indexloc = 0;
        int i;
        for (i = 0; i < xall.length; i++) {
            String[] s = ((String) dateVect.get(i)).split("/");
            int[] d = new int[3];
            try {
                d[0] = Integer.parseInt(s[0].trim());
                d[1] = Integer.parseInt(s[1].trim());
                d[2] = Integer.parseInt(s[2].trim());
            } catch (NumberFormatException nfe) {
                System.out.println("NumberFormatException: " + nfe.getMessage());
            }
            java.util.GregorianCalendar cal =
                    new java.util.GregorianCalendar(d[2], d[0], d[1]);
            if (d[2] != prevd) {
                yearIndex[indexloc] = i;
                indexloc++;
            }
            prevd = d[2];

            xall[i] = cal.getTime().getTime();
            yall[i] = ((Double) valVect.get(i)).doubleValue();
        }
        yearIndex[indexloc] = i;
        subset();
    }


    /**
     *  Create the Chart object with two axes
     */
    private void createChart() {
        chart = getChart();
        axis = new AxisXY(chart);
        axis.setViewport(0.15, 0.95, 0.05, 0.8);
        chart.getLegend().setPaint(true);
        chart.getLegend().setViewport(0.18, 0.38, 0.62, 0.72);
        axis.getAxisX().setTextFormat("Date(MEDIUM)");
        axis.getAxisX().setTextAngle(90);
        axis.getAxisX().setTickLength(-1);
        axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
        axis.getAxisX().setWindow(x[0], x[x.length - 1]);
        axis.getAxisY().setTickLength(-1);

        // Add another AxisXY for the boxed look
        axis2 = new AxisXY(chart);
        axis2.setViewport(axis.getViewport());
        axis2.getAxisX().setType(AxisXY.AXIS_X_TOP);
        axis2.getAxisX().setTickLength(-1);
        axis2.getAxisX().setTextColor(java.awt.Color.white);
        axis2.getAxisY().setType(AxisXY.AXIS_Y_RIGHT);
        axis2.getAxisY().setTickLength(-1);
        axis2.getAxisY().setTextColor(java.awt.Color.white);

        // Add MouseListener to the chart
        getPanel().addMouseListener(this);
    }


    /**
     *  Remove specific objects from the Chart.
     *
     *@param  what  One of CHARTLINES, CHARTPOINTS, or CHARTAXES.
     */
    private void removeChart(int what) {
        switch (what) {
            case CHARTLINES:
                lineData.remove();
                lineKal.remove();
                lineMov.remove();
                break;
            case CHARTPOINTS:
                if (pointData != null) {
                    pointData.remove();
                }
                if (pointKal != null) {
                    pointKal.remove();
                }
                if (pointMov != null) {
                    pointMov.remove();
                }
                break;
            case CHARTAXES:
                axis.remove();
                axis2.remove();
                break;
            default:
                System.out.println("Invalid option passed to removeChart(): " + what);
        }
    }


    /**
     *  Draw the graph in the chart. Three lines are drawn: Data, KalmanFilter,
     *  Moving Average.
     */
    private void drawGraph() {

        // Draw the data
        lineData = new Data(axis, x, y);
        lineData.setLineColor(java.awt.Color.blue);
        lineData.setTitle("Original Data");

        // Draw the filtered data
        f = computeKal(y);
        lineKal = new Data(axis, x, f);
        lineKal.setLineColor(java.awt.Color.red);
        lineKal.setTitle("Kalman Filtered");

        // Draw the moving average
        m = computeMov(y, npoint);
        double[] xx = new double[x.length - npoint];
        for (int i = npoint; i < x.length; i++) {
            xx[i - npoint] = x[i - npoint / 2];
        }
        lineMov = new Data(axis, xx, m);
        lineMov.setLineColor(java.awt.Color.magenta);
        lineMov.setTitle("Moving Average");
    }


    /**
     *  Compute the Kalman Filter
     *
     *@param  yy  The y values of the time series to be filtered.
     *@return     The values of the filtered time series.
     */
    private double[] computeKal(double[] yy) {
        int nobs = yy.length;
        double[] ff = new double[nobs];
        int rank = 0;
        double logDeterminant = 0.0;
        double ss = 0.0;
        double[] b = {4};
        double[] covb = {16};
        double[][] q = {{1}};
        double[][] r = {{1}};
        double[][] t = {{1}};
        double[][] z = {{1}};

        int sel = 4;
        if (jComboBoxQR != null) {
            sel = ((Label) jComboBoxQR.getSelectedItem()).index;
        }
        switch (sel) {
            case 0:
                q[0][0] = 0.001;
                break;
            case 1:
                q[0][0] = 0.005;
                break;
            case 2:
                q[0][0] = 0.01;
                break;
            case 3:
                q[0][0] = 0.05;
                break;
            case 4:
                q[0][0] = 0.1;
                break;
            case 5:
                q[0][0] = 0.5;
                break;
            case 6:
                q[0][0] = 1.0;
                break;
            case 7:
                q[0][0] = 5.0;
                break;
        }

        for (int i = 0; i < nobs; i++) {
            double yi[] = {yy[i]};
            com.imsl.stat.KalmanFilter kalman =
                    new com.imsl.stat.KalmanFilter(b, covb, rank, ss, logDeterminant);
            kalman.update(yi, z, r);
            kalman.filter();
            b = kalman.getStateVector();
            covb = kalman.getCovB();
            rank = kalman.getRank();
            ss = kalman.getSumOfSquares();
            logDeterminant = kalman.getLogDeterminant();
            double v[] = kalman.getPredictionError();
            double covv[][] = kalman.getCovV();

            kalman = new com.imsl.stat.KalmanFilter(b, covb, rank, ss, logDeterminant);
            kalman.setTransitionMatrix(t);
            kalman.setQ(q);
            kalman.filter();
            b = kalman.getStateVector();
            covb = kalman.getCovB();
            rank = kalman.getRank();
            ss = kalman.getSumOfSquares();
            logDeterminant = kalman.getLogDeterminant();
            ff[i] = b[0];
        }
        return ff;
    }


    /**
     *  Compute the Moving Average
     *
     *@param  yy  A double array, the y values of the time series to be
     *      averaged.
     *@param  np  An integer, the number of points to compute the average over.
     *@return     A double array, the values of the moving average.
     */
    private double[] computeMov(double[] yy, int np) {
        double[] mm = new double[yy.length - np];
        for (int i = 0; i < mm.length; i++) {
            double sum = 0.0;
            for (int j = 0; j < np; j++) {
                sum = sum + yy[i + j];
            }
            mm[i] = sum / np;
        }
        return mm;
    }


    /**
     *  Read File button press creates a JOtptionDialog to get the fileName
     *
     *@param  evt  The moust event.
     */
    /**
     * This code would be used with a ReadFile button to read other data files.
     *
    private void jButtonReadActionPerformed(java.awt.event.ActionEvent evt) {
        JPanel iPanel = new JPanel();
        iPanel.setLayout(new java.awt.BorderLayout());
        JTextArea infoArea = new JTextArea(" ", 5, 30);
        infoArea.setText("The file must be an ASCII file of 2 columns seperated by a" +
                "\ncomma. The first column is the date in the format:" +
                "\nmm/dd/yyyy. The second column is the value for that day." +
                "\nType a file name and click OK.");
        infoArea.setEditable(false);
        infoArea.setBackground(iPanel.getBackground());

        JPanel fPanel = new JPanel();
        JLabel inputLabel = new JLabel("\nFile name:");
        JTextArea inputArea = new JTextArea(0, 15);
        iPanel.add(infoArea, java.awt.BorderLayout.NORTH);

        fPanel.add(inputLabel);
        fPanel.add(inputArea);
        iPanel.add(fPanel, java.awt.BorderLayout.SOUTH);

        int option = 0;
        try {
            option = JOptionPane.showConfirmDialog(this, iPanel,
                    "Enter filename", JOptionPane.OK_CANCEL_OPTION);
            if (option == JOptionPane.CANCEL_OPTION) {
                return;
            }
        } catch (Exception e) {}

        fileName = inputArea.getText().trim();
        if (fileName != "") {
            readFile(fileName);
            removeChart("axes");
            createChart();
            update();
        }
    }
    */

    private void subset() {
        final int idx = nyear - 1;
        x = new double[yearIndex[idx]];
        y = new double[yearIndex[idx]];
        for (int i=0; i<yearIndex[idx]; i++) {
            x[i] = xall[i];
            y[i] = yall[i];
        }
    }

    /**
     *  Q/R ratio Combo Box selection, just update().
     *
     *@param  evt  The moust event.
     */
    private void jComboBoxQRActionPerformed(java.awt.event.ActionEvent evt) {
        update();
    }

    /**
     *  Number years ratio Combo Box selection, set subset then update().
     *
     *@param  evt  The moust event.
     */
    private void jComboBoxNYActionPerformed(java.awt.event.ActionEvent evt) {
        final int sel = ((Label) jComboBoxNY.getSelectedItem()).index;
        nyear = sel+1;
        subset();
        axis.getAxisX().setWindow(x[0], x[x.length - 1]);
        update();
    }

    /**
     *  Moving Average number of points selection, set npoint and then update().
     *
     *@param  evt  The moust event.
     */
    private void jComboBoxMAActionPerformed(java.awt.event.ActionEvent evt) {
        int sel = ((Label) jComboBoxMA.getSelectedItem()).index;
        switch (sel) {
            case 0:
                npoint = 2;
                break;
            case 1:
                npoint = 6;
                break;
            case 2:
                npoint = 10;
                break;
        }
        update();
    }


    /**
     *  Update the chart by removing all the Data objects, then calling
     *  drawGraph() and repaint()
     */
    private void update() {
        removeChart(CHARTLINES);
        removeChart(CHARTPOINTS);
        drawGraph();
        repaint();
        displayText.setText("Click in the chart area for exact values.");
    }


    /**
     *  Label objects are used in conjunction with the JComboBox.
     *
     *@author     John Brophy
     *@created    September 25, 2002
     */
    private static class Label {
        String label;
        int index;

        /**
         *  Constructor for the Label object.
         *
         *@param  label  The label String.
         *@param  index  The label index.
         */
        Label(String label, int index) {
            this.label = label;
            this.index = index;
        }


        /**
         *  Returns the label object.
         *
         *@return    The label.
         */
        public String toString() {
            return label;
        }
    }
}