/*
 * -------------------------------------------------------------------------
 *      $Id: Harmonic.java,v 1.4 2006/01/31 20:02:09 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.Harmonic;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.text.DecimalFormat;
import com.imsl.math.FFT;
import com.imsl.chart.*;
import com.imsl.demo.gallery.Describe;


/**
 *  Reads from a set of .wav files, plots the waveform, computes
 *  and plots an FFT on request.
 *
 * @author     R.B.E.Taylor
 * @created    October 23, 2002
 */
public class Harmonic extends JFrameChart
implements ActionListener, ChangeListener, MouseListener, MouseMotionListener {

    private Chart chart;
    private AxisXY axis;
    private Data data;
    private JSlider slider;
    private JTextField displayField;
    private ButtonGroup selectCelloString;
    private boolean zoom, iszoom;
    private double[] from, to, current;
    private double[] t, y, t_sub, y_sub;
    private String plotColor, plotLabel;

    public Harmonic(boolean exitOnClose) {
        setTitle("Harmonic Analysis");
        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]);
            }
        }

        // Set default values.
        zoom = true;
        getWavData("Astring.wav");
        plotColor = "orange";
        plotLabel = "A String";

        DecimalFormat tFormat = new DecimalFormat("0.000");
        DecimalFormat yFormat = new DecimalFormat("###");

        chart = getChart();
        axis = new AxisXY(chart);
        axis.getAxisX().getAxisLabel().setTextFormat(tFormat);
        axis.getAxisX().getAxisLabel().setTextAngle(90);
        axis.getAxisX().getAxisTitle().setTitle("Time, sec");

        axis.getAxisY().getAxisLabel().setTextFormat(yFormat);
        axis.getAxisY().getAxisTitle().setTitle("Relative Amplitude");

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

        from[0] = 0.0; from[1] = 128.0;
        to[0] = 0.8; to[1] = -128;

        // Add MouseListener and MouseMotionListener to chart panel.
        chart.getLegend().setPaint(true);
        chart.getLegend().setViewport(0.65, 0.85, 0.05, 0.15);
        getPanel().addMouseListener(this);
        getPanel().addMouseMotionListener(this);
        Container cp = getContentPane();

        // Create components in control panel.
        ButtonGroup selectCelloString = new ButtonGroup();
        JRadioButton Astringbutton = new JRadioButton("A String", true);
        Astringbutton.addActionListener(this);
        JRadioButton Dstringbutton = new JRadioButton("D String", false);
        Dstringbutton.addActionListener(this);
        JRadioButton Gstringbutton = new JRadioButton("G String", false);
        Gstringbutton.addActionListener(this);
        JRadioButton Cstringbutton = new JRadioButton("C String", false);
        Cstringbutton.addActionListener(this);

        selectCelloString.add(Astringbutton);
        selectCelloString.add(Dstringbutton);
        selectCelloString.add(Gstringbutton);
        selectCelloString.add(Cstringbutton);

        JPanel stringPanel = new JPanel();
        stringPanel.setLayout(new GridLayout(2,2));
        stringPanel.add(Astringbutton);
        stringPanel.add(Dstringbutton);
        stringPanel.add(Gstringbutton);
        stringPanel.add(Cstringbutton);

        JButton draw = new JButton("Show Full Sample");
        draw.addActionListener(this);

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

        JButton transform = new JButton("Calculate FFT");
        transform.addActionListener(this);

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(stringPanel);
        buttonPanel.add(transform);
        buttonPanel.add(draw);
        buttonPanel.add(reset);

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

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

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

        drawGraph();
        Describe des = new Describe(this, "/com/imsl/demo/Harmonic/Harmonic.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);
        setResizable(false);
    }


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



    // Get data from .wav files:
    private void getWavData(String wavefilename) {

        int numWavBytes;
        byte[] byteStream;
        float bytesPerSec;

        try {
            AccessWaveFile fileobject = new AccessWaveFile(wavefilename);
            numWavBytes = fileobject.getAvailableBytes();
            // workaround compression of jar file issue by hardcoding values.
            switch (wavefilename.charAt(0)) {
                case 'A': numWavBytes = 86272; break;
                case 'D': numWavBytes = 75904; break;
                case 'G': numWavBytes = 81664; break;
                case 'C': numWavBytes = 92672; break;
                default: numWavBytes = fileobject.getAvailableBytes(); break;
            }
            byteStream = fileobject.getByteStream(numWavBytes);
            bytesPerSec = fileobject.getBytesPerSec();
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this, e.getMessage(), "Exception", JOptionPane.ERROR_MESSAGE);
            e.printStackTrace();
            System.exit(1);
            return;
        }

        // Convert data and construct t-values:
        t = new double[numWavBytes];
        y = new double[numWavBytes];

        for (int i = 0; i < y.length; i++) {
            y[i] = (double) byteStream[i];
            t[i] = (double) i/bytesPerSec;  //time in seconds
        }
    }


    // Draw the graph.
    private void drawGraph() {

        // Subset it before plotting:
        if (zoom) {
            int i = 0;
            double min = Math.min(from[0], to[0]);
            while (t[i] < min) i++;
            int i_init = i;
            double max = Math.max(from[0], to[0]);
            while (t[i] <= max) {
                i++;
                if (i == t.length-1) break;
            }
            int i_final = i;

            t_sub = new double[i_final - i_init + 1];
            y_sub = new double[i_final - i_init + 1];

            for (i = 0; i <= i_final - i_init; i++) {
                t_sub[i] = t[i + i_init];
                y_sub[i] = y[i + i_init];
            }
        } else {
            t_sub = new double[t.length];
            y_sub = new double[t.length];
            for (int i = 0; i < y_sub.length; i++) {
                t_sub[i] = t[i];
                y_sub[i] = y[i];
            }
        }

        // 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(-128.0, 128.0);
        } else {
            axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
            axis.getAxisX().setWindow(0.0, t[t.length-1]);
            axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
            axis.getAxisY().setWindow(-128.0, 128.0);
        }

        data = new Data(axis, t_sub, y_sub);

        data.setTitle(plotLabel);
        data.setDataType(Data.DATA_TYPE_LINE);
        data.setLineColor(plotColor);
    }


    private void plotNewFFT() {
        // Calculate the FFT :
        double[] t_lim = new double[2];
        t_lim[0] = t_sub[0];
        t_lim[1] = t_sub[t_sub.length - 1];

        double[] a;
        FFT transform = new FFT(y_sub.length);
        a = transform.forward(y_sub);

        // Calculate the power spectrum, as per documentation,
        // including a factor normalising to the length of the input:
        double[] power = new double[y_sub.length/2]; //should take care of n odd/even
        double[] f = new double[power.length];

        f[0] = (double) 0.0;
        power[0] = Math.abs(a[0])/y_sub.length;

        for (int i = 1; i < power.length; i++) {
            f[i] = i/(t_lim[1] - t_lim[0]);
            power[i] = Math.sqrt(a[2*i-1]*a[2*i-1] + a[2*i]*a[2*i])/y_sub.length;
        }

        PlotFFT pf = new PlotFFT(this);
        pf.draw(f, power, t_lim, plotLabel, plotColor);
        pf.show();
    }


    // Implement ActionListener
    public void actionPerformed(ActionEvent e) {

        data.remove(); // clear current trace

        if (e.getActionCommand().equals("Show Full Sample")) {
            zoom = false;
        } else if (e.getActionCommand().equals("Reset View")) {
            zoom = true;
            from[0] = 0.0; from[1] = 128.0;
            to[0] = 0.8; to[1] = -128;
        } else if (e.getActionCommand().equals("A String")) {
            plotColor = "orange";
            plotLabel = "A String";
            getWavData("Astring.wav");
        } else if (e.getActionCommand().equals("C String")) {
            plotColor = "red";
            plotLabel = "C String";
            getWavData("Cstring.wav");
        } else if (e.getActionCommand().equals("D String")) {
            plotColor = "magenta";
            plotLabel = "D String";
            getWavData("Dstring.wav");
        } else if (e.getActionCommand().equals("G String")) {
            plotColor = "blue";
            plotLabel = "G String";
            getWavData("Gstring.wav");
        } else if (e.getActionCommand().equals("Calculate FFT")) {
            plotNewFFT();
        }
        drawGraph();
        repaint();
    }



    // Implement ChangeListener
    public void stateChanged(ChangeEvent e) {
    }


    // 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;
        data.remove();
        drawGraph();
        repaint();
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }



    // 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) Harmonic.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 Harmonic(exitOnClose).show();
    }
}