/*
 * -------------------------------------------------------------------------
 *      $Id: SunSpots.java,v 1.2 2004/05/26 19:16:36 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.SunSpots;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.text.*;
import com.imsl.stat.*;
import com.imsl.chart.*;
import com.imsl.demo.gallery.Describe;


public class SunSpots extends JFrameChart
implements ActionListener, ChangeListener, MouseListener, MouseMotionListener {
    private Chart chart;
    private AxisXY axis;
    private Data data, forecastData, upperLimit, lowerLimit;
    private JSlider slider;
    private JTextField predField, probField, displayField;
    private boolean toForecast, zoom, iszoom;
    private double[] from, to, current;
    private ARMA arma;
    private SimpleDateFormat dateFormat = new SimpleDateFormat("M/yyyy");



    public SunSpots(boolean exitOnClose) {
        if (!exitOnClose) {
            // remove the WindowListener,  installed by JFrameChart, that
            // exits the application when the window is closed.
            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/SunSpots/SunSpots.html");
        des.show();
        Dimension ds = des.getSize();

        Dimension ss = getToolkit().getScreenSize();
        int h = Math.min(ss.width/2, ss.height-ds.height-32);
        int w = (int)(h/0.8);
        setSize(w, h);
        setLocation(ss.width-ds.width, ds.height);

        // Set default values.
        toForecast = zoom = false;
        chart = getChart();
        axis = new AxisXY(chart);
        axis.getAxisX().getAxisLabel().setTextFormat(dateFormat);
        axis.getAxisX().getAxisLabel().setTextAngle(90);
        axis.getAxisX().getAxisTitle().setTitle("Year");

        from = new double[2];
        to = new double[2];
        current = new double[2];

        // Add MouseListener and MouseMotionListener to chart panel.
        chart.getLegend().setPaint(true);
        getPanel().addMouseListener(this);
        getPanel().addMouseMotionListener(this);

        Container cp = getContentPane();

        // Create components in control panel.
        JButton draw = new JButton("Forecast");
        draw.addActionListener(this);

        JButton reset = new JButton("Reset");
        reset.addActionListener(this);

        JLabel prob = new JLabel("Confidence", JLabel.CENTER);
        probField = new JTextField("0.95", 4);

        slider = new JSlider(1, 31, 11);
        slider.setPaintTicks(true);
        slider.setPaintLabels(true);
        slider.setMajorTickSpacing(10);
        slider.setMinorTickSpacing(1);
        slider.setSnapToTicks(true);
        slider.addChangeListener(this);

        JLabel pred = new JLabel("Predicts", JLabel.CENTER);
        predField = new JTextField("11", 3);
        predField.setEditable(false);

        JPanel buttonPanel = new JPanel();

        buttonPanel.add(draw);
        buttonPanel.add(prob);
        buttonPanel.add(probField);
        buttonPanel.add(slider);
        buttonPanel.add(pred);
        buttonPanel.add(predField);
        buttonPanel.add(reset);

        // Create the display panel
        JPanel displayPanel = new JPanel();

        displayField = new JTextField("Current position:  X = ??  Y = ??", 53);
        displayField.setBorder(null);
        displayField.setCaretPosition(0);
        displayField.setEditable(false);
        displayPanel.add(displayField);

        cp.add(buttonPanel, BorderLayout.NORTH);
        cp.add(displayPanel, BorderLayout.SOUTH);

        drawGraph();
        setResizable(false);
    }



    //  Get the description line.
    private String getDescription() {
        DecimalFormat nf = new DecimalFormat("0.0000");
        StringBuffer sb = new StringBuffer();
        if (iszoom) {
            sb.append("Zooming In from:  ");
            sb.append("X = " + dateFormat.format(new Date((long) from[0])) + "  Y = " + nf.format(from[1]));
            sb.append("    To:    ");
            sb.append("X = " + dateFormat.format(new Date((long) to[0])) + "  Y = " + nf.format(to[1]));
        } else {
            sb.append("Cursor At:  X = " + dateFormat.format(new Date((long) current[0])) +
            "  Y = " + nf.format(current[1]));
        }
        return sb.toString();
    }



    // Draw the graph.
    private void drawGraph() {
        double[] y = {100.8, 81.6, 66.5, 34.8, 30.6, 7, 19.8, 92.5,
        154.4, 125.9, 84.8, 68.1, 38.5, 22.8, 10.2, 24.1, 82.9,
        132, 130.9, 118.1, 89.9, 66.6, 60, 46.9, 41, 21.3, 16,
        6.4, 4.1, 6.8, 14.5, 34, 45, 43.1, 47.5, 42.2, 28.1, 10.1,
        8.1, 2.5, 0, 1.4, 5, 12.2, 13.9, 35.4, 45.8, 41.1, 30.4,
        23.9, 15.7, 6.6, 4, 1.8, 8.5, 16.6, 36.3, 49.7, 62.5, 67,
        71, 47.8, 27.5, 8.5, 13.2, 56.9, 121.5, 138.3, 103.2,
        85.8, 63.2, 36.8, 24.2, 10.7, 15, 40.1, 61.5, 98.5, 124.3,
        95.9, 66.5, 64.5, 54.2, 39, 20.6, 6.7, 4.3, 22.8, 54.8,
        93.8, 95.7, 77.2, 59.1, 44, 47, 30.5, 16.3, 7.3, 37.3,
        73.9};
        double[] x = new double[y.length];

        // If in zooming mode, then zoom the graph.  Otherwise, use default ranges.
        if (zoom) {
            axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
            axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);

            axis.getAxisX().setWindow(Math.min(from[0], to[0]), Math.max(from[0], to[0]));
            axis.getAxisY().setWindow(Math.min(from[1], to[1]), Math.max(from[1], to[1]));
        } else {
            axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
            axis.getAxisX().setWindow((new GregorianCalendar(1770, 0, 1)).getTimeInMillis(),
            (new GregorianCalendar(1910, 0, 1)).getTimeInMillis());
            axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
            //axis.getAxisY().setWindow(-145.0, 235.0);
            axis.getAxisY().setWindow(-50.0, 200.0);
        }

        // Draw the actual data.
        for (int i = 0; i < y.length; i++) {
            x[i] = (new GregorianCalendar(1770 + i, 0, 1)).getTimeInMillis();
        }

        data = new Data(axis, x, y);
        data.setTitle("Actual data");
        data.setDataType(Data.DATA_TYPE_LINE | Data.DATA_TYPE_MARKER);
        data.setMarkerType(Data.MARKER_TYPE_FILLED_CIRCLE);
        data.setMarkerSize(0.7);
        data.setMarkerColor(Color.blue);
        data.setLineColor(Color.blue);

        // Use ARMA class (ARMA(2,1)) to get the forecast using backorigin = 3.
        // Number of predicts and confidence level are from the interface.
        if (toForecast) {
            try {
                int num = slider.getValue();

                // Do this if Forecast is clicked.
                if (arma == null) {
                    arma = new ARMA(2, 1, y);
                    arma.setRelativeError(0.0);
                    arma.setMaxIterations(0);
                    arma.compute();
                    arma.setConfidence(Double.parseDouble(probField.getText()));
                    arma.setBackwardOrigin(3);
                }

                double[][] forecast = arma.forecast(num);

                double[] tmp = new double[num];
                double[] x1 = new double[num];

                // Draw the forecast data.
                for (int i = 0; i < num; i++) {
                    tmp[i] = forecast[i][3];
                    x1[i] = (new GregorianCalendar(1870+i, 0, 1)).getTimeInMillis();
                }

                // Draw upper and lower limits.
                double[] dev = arma.getDeviations();
                double[] ul = new double[dev.length];
                double[] ll = new double[dev.length];
                double min = 0.0;

                for (int i = 0; i < dev.length; i++) {
                    ul[i] = tmp[i] + dev[i];
                    ll[i] = tmp[i] - dev[i];
                    if (min > ll[i]) min = ll[i];
                }
                min = min - 0.01;

                upperLimit = new Data(axis, x1, ul);
                upperLimit.setTitle("Upper Limit");
                upperLimit.setDataType(Data.DATA_TYPE_LINE | Data.DATA_TYPE_FILL);
                upperLimit.setReference(min);
                upperLimit.setFillColor(Color.yellow);
                upperLimit.setFillOutlineColor(Color.white);

                lowerLimit = new Data(axis, x1, ll);
                lowerLimit.setTitle("Lower Limit");
                lowerLimit.setDataType(Data.DATA_TYPE_LINE | Data.DATA_TYPE_FILL);
                lowerLimit.setReference(min);
                lowerLimit.setFillColor(Color.white);
                lowerLimit.setFillOutlineColor(Color.white);

                forecastData = new Data(axis, x1, tmp);
                forecastData.setTitle("Forecast");
                forecastData.setDataType(Data.DATA_TYPE_LINE);
                forecastData.setLineColor(Color.red);
            } catch (Exception e) {
            }
        }
    }



    // Get the information from the interface and redraw the chart.
    private void update() {
        double tmp = 0.0;

        // Check the range for the confidence level.  Use 0.95 if there is
        // an error in the input.
        try {
            tmp = Double.parseDouble(probField.getText());
        } catch (Exception exception) {
            tmp = 0.95;
            probField.setText("0.95");
        } finally {
            if (data != null) data.remove();
            if (forecastData != null) forecastData.remove();
            if (upperLimit != null) upperLimit.remove();
            if (lowerLimit != null) lowerLimit.remove();
            if ((tmp <= 0.0) || (tmp >= 1.0)) probField.setText("0.95");
            drawGraph();
            repaint();
        }
    }



    // Implement ActionListener
    public void actionPerformed(ActionEvent e) {
        // If Reset is clicked, re-size the chart using autoscale.
        // If Draw is clicked, redraw the chart.
        if (e.getActionCommand().equals("Reset")) {
            zoom = false;
        } else {
            toForecast = true;
            arma = null;
        }
        update();
    }



    // Implement ChangeListener
    public void stateChanged(ChangeEvent e) {
        // Update text field for number of predicts.
        predField.setText(Integer.toString(slider.getValue()));
    }



    // Implement MouseListener
    public void mouseClicked(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
        // Get the starting position for zooming.
        axis.mapDeviceToUser(e.getX(), e.getY(), from);
        axis.mapDeviceToUser(e.getX(), e.getY(), to);
        zoom = true;
        iszoom = true;
    }

    public void mouseReleased(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();

        // The selection must be from 2 different points.
        int[] point = new int[2];
        axis.mapUserToDevice(from[0], from[1], point);
        if (point[0] == x) return;
        if (point[1] == y) return;

        // Get the ending position for zooming.
        axis.mapDeviceToUser(x, y, to);

        iszoom = false;
        update();
    }

    public void mouseEntered(MouseEvent e) {
        setCursor(new java.awt.Cursor(java.awt.Cursor.CROSSHAIR_CURSOR));
    }

    public void mouseExited(MouseEvent e) {
        setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
    }



    // Implement MouseMotionListener
    public void mouseDragged(MouseEvent e) {
        // Draw the selection square and update display information.
        paint(e, true);
        displayField.setText(getDescription());
    }

    public void mouseMoved(MouseEvent e) {
        // Update display information.
        axis.mapDeviceToUser(e.getX(), e.getY(), current);
        displayField.setText(getDescription());
    }

    // Draw the selection in different color.
    protected void paint(MouseEvent mouseEvent, boolean erase) {
        Graphics2D g = (Graphics2D) SunSpots.this.getPanel().getGraphics();
        if (erase)  paint(g);
        axis.mapDeviceToUser(mouseEvent.getX(), mouseEvent.getY(), to);
        paint(g);
        g.dispose();
    }

    protected void paint(Graphics2D g) {
        Color colorFill = new Color(255,0,255,128);
        Color colorOutline = new Color(0,255,0);

        int[] pointA = new int[2];
        int[] pointB = new int[2];

        axis.mapUserToDevice(from[0], from[1], pointA);
        axis.mapUserToDevice(to[0], to[1], pointB);

        Shape shape = new Rectangle(Math.min(pointA[0], pointB[0]),
        Math.min(pointA[1], pointB[1]), Math.abs(pointA[0]-pointB[0]),
        Math.abs(pointA[1]-pointB[1]));
        g.setXORMode(colorOutline);
        g.draw(shape);
        g.setXORMode(colorFill);
        g.fill(shape);
    }



    public static void main(String args[]) {
        boolean exitOnClose = true;
        if (args.length > 0  && args[0].equals("-noexit"))  exitOnClose = false;
        new SunSpots(exitOnClose).show();
    }
}