/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.analysis;
import java.awt.Color;
import org.opensourcephysics.display.ComplexDataset;
import org.opensourcephysics.display.Data;
import org.opensourcephysics.display.Dataset;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.numerics.FFTReal;

/**
 * FourierAnalysis adds gutter points to real data before performing a fast Fourier transform.
 * Gutter points increase the number points in order to approximate a nonperiodic function.
 *
 * The FFT output is phase shifted to account for the fact that the FFT basis functions are
 * defined on [0, 2*pi].
 *
 * @author W. Christian
 * @version 1.0
 */
public class FourierSinCosAnalysis implements Data {
  static final double PI2 = 2*Math.PI;
  FFTReal fft = new FFTReal();
  double[] fftData, omega, freqs;
  private double[] cosVec, sinVec, gutterVec;
  ComplexDataset[] complexDatasets = new ComplexDataset[1];
  Dataset[] realDatasets = new Dataset[3];
  boolean radians = false;
  private String name = "Fourier Analysis Sin/Cos Data"; //$NON-NLS-1$
  protected int datasetID = hashCode();

  /**
   * Fourier analyzes the given data y[] after adding gutter points at the start and end of the z[] array.
   *
   * @param x double[]
   * @param y double[]
   * @param gutter int
   * @return double[] the Fourier spectrum
   */
  public double[] doAnalysis(double[] x, double[] y, int gutter) {
    int offset = y.length%2; // zero if even number of points; one if odd number of points
    fftData = new double[y.length+2*gutter-offset];
    gutterVec = new double[gutter];
    System.arraycopy(y, 0, fftData, gutter, y.length-offset);
    fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts.
    double dx = x[1]-x[0];
    double xmin = x[0]-gutter*dx;
    double xmax = x[x.length-1-offset]+(gutter+1)*dx;
    omega = fft.getNaturalOmega(xmin, xmax);
    freqs = fft.getNaturalFreq(xmin, xmax);
    cosVec = new double[omega.length];
    sinVec = new double[omega.length];
    double norm = 2.0/y.length;
    //double norm=2.0/fftData.length;
    for(int i = 0, nOmega = omega.length; i<nOmega; i++) {
      cosVec[i] = norm*Math.cos(omega[i]*xmin);
      sinVec[i] = norm*Math.sin(omega[i]*xmin);
    }
    cosVec[0] *= 0.5; // constant coefficient has factor of 1/2.
    sinVec[0] *= 0.5;
    for(int i = 0, nOmega = omega.length; i<nOmega; i++) {
      double re = fftData[2*i];
      double im = fftData[2*i+1];
      fftData[2*i] = re*cosVec[i]+im*sinVec[i];    // cos coefficient
      fftData[2*i+1] = -im*cosVec[i]+re*sinVec[i]; // sin coefficient
    }
    return fftData;
  }

  /**
   * Some elements (a Group, for instance) do not contain data, but a list of subelements which do.
   * This method is used by Data displaying tools to create as many pages as needed.
   * @return A list of DataInformation elements, null if the element itself is a DataInformation
   */
  public java.util.List<Data> getDataList() {
    return null;
  }

  /**
   * Repeats the Fourier analysis of the real data y[] with the previously set scale and gutter.
   *
   * @param y double[]
   * @return double[] the Fourier sin/cos coefficients
   */
  public double[] repeatAnalysis(double[] y) {
    int offset = y.length%2; // zero if even number of points; one if odd number of points
    if(fftData==null) {
      int n = y.length-offset;
      double[] x = new double[n];
      double x0 = 0, dx = 1.0/n;
      for(int i = 0; i<n; i++) {
        x[i] = x0;
        x0 += dx;
      }
      doAnalysis(x, y, 0);
    }
    System.arraycopy(gutterVec, 0, fftData, 0, gutterVec.length);                                 // zero the left gutter
    System.arraycopy(gutterVec, 0, fftData, fftData.length-1-gutterVec.length, gutterVec.length); // zero the right gutter
    System.arraycopy(y, 0, fftData, gutterVec.length, y.length-offset);
    fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts.
    for(int i = 0, nOmega = omega.length; i<nOmega; i++) {
      double re = fftData[2*i];
      double im = fftData[2*i+1];
      fftData[2*i] = re*cosVec[i]+im*sinVec[i];
      fftData[2*i+1] = im*cosVec[i]-re*sinVec[i];
    }
    return fftData;
  }

  /**
   * Gets the angular frequencies of the Fourier spectrum.
   * @return double[]
   */
  public double[] getNaturalOmega() {
    return omega;
  }

  /**
   * Gets the frequencies of the Fourier spectrum.
   * @return double[]
   */
  public double[] getNaturalFreq() {
    return freqs;
  }

  /**
   * Sets the radians flag for the frequency values of datasets.
   * Dataset x-values are either frequencies (cycles) or angular frequencies (radians) depending
   * on the value of the radians flag.
   *
   * @param radians boolean
   */
  public void useRadians(boolean radians) {
    this.radians = radians;
  }

  /**
   * Gets the radians flag.
   * Radians is true if the dataset uses angular frequency as the x-coordinate.
   *
   * @return boolean
   */
  public boolean isRadians() {
    return radians;
  }

  /**
   * Gets the datasets that contain the result of the last Fourier analysis.
   * The power spectrum is contained in the first dataset.
   * Sine coefficients are contained in the second dataset.
   * Cosine coefficients are in the third dataset.
   *
   * Dataset x-values are either frequencies (cycles) or angular frequencies (radians) depending
   * on the value of the radians flag.
   *
   * @return list of Datasets
   */
  public java.util.ArrayList<Dataset> getDatasets() {
    java.util.ArrayList<Dataset> list = new java.util.ArrayList<Dataset>();
    if(fftData==null) {
      return list;
    }
    if(realDatasets[0]==null) {
      realDatasets[0] = new Dataset();
      realDatasets[0].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
        DisplayRes.getString("FourierSinCosAnalysis.Column.Power"),                              //$NON-NLS-1$
          DisplayRes.getString("FourierSinCosAnalysis.PowerSpectrum"));                          //$NON-NLS-1$
      realDatasets[0].setLineColor(Color.GREEN.darker());
      realDatasets[0].setMarkerColor(Color.GREEN.darker());
      realDatasets[0].setMarkerShape(Dataset.BAR);
      realDatasets[0].setMarkerSize(4);
      realDatasets[1] = new Dataset();
      realDatasets[1].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
        DisplayRes.getString("FourierSinCosAnalysis.Column.Cosine"),                             //$NON-NLS-1$
          DisplayRes.getString("FourierSinCosAnalysis.CosineCoefficients"));                     //$NON-NLS-1$
      realDatasets[1].setLineColor(Color.CYAN.darker());
      realDatasets[1].setMarkerColor(Color.CYAN.darker());
      realDatasets[1].setMarkerShape(Dataset.BAR);
      realDatasets[1].setMarkerSize(4);
      realDatasets[2] = new Dataset();
      realDatasets[2].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
        DisplayRes.getString("FourierSinCosAnalysis.Column.Sine"),                               //$NON-NLS-1$
          DisplayRes.getString("FourierSinCosAnalysis.SineCoefficients"));                       //$NON-NLS-1$
      realDatasets[2].setLineColor(Color.BLUE.darker());
      realDatasets[2].setMarkerColor(Color.BLUE.darker());
      realDatasets[2].setMarkerShape(Dataset.BAR);
      realDatasets[2].setMarkerSize(4);
    } else {
      realDatasets[0].clear();
      realDatasets[1].clear();
      realDatasets[2].clear();
    }
    if(radians) {
      for(int i = 0, nOmega = omega.length; i<nOmega; i++) {
        double cos = fftData[2*i], sin = fftData[2*i+1];
        realDatasets[0].append(omega[i], sin*sin+cos*cos);
        realDatasets[1].append(omega[i], sin);
        realDatasets[2].append(omega[i], cos);
      }
    } else {
      for(int i = 0, nFreqs = freqs.length; i<nFreqs; i++) {
        double sin = fftData[2*i], cos = fftData[2*i+1];
        realDatasets[0].append(freqs[i], sin*sin+cos*cos);
        realDatasets[1].append(freqs[i], sin);
        realDatasets[2].append(freqs[i], cos);
      }
    }
    list.add(realDatasets[0]);
    list.add(realDatasets[1]);
    list.add(realDatasets[2]);
    return list;
  }

  /**
   * Sets a name that can be used to identify the dataset.
   *
   * @param name String
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * The column names to be used in the data display tool
   * @return
   */
  public String[] getColumnNames() {
    return new String[] {name};
  }

  /**
   * Gets the dataset name.
   *
   * @return String
   */
  public String getName() {
    return name;
  }

  /**
   * Line colors for Data interface.
   * @return
   */
  public java.awt.Color[] getLineColors() {
    return null;
  }

  /**
   * Fill colors for Data interface.
   * @return
   */
  public java.awt.Color[] getFillColors() {
    return null;
  }

  /**
   * Gets the frequencies, power, cos, and sin coefficients.
   * @return double[][]
   */
  public double[][] getData2D() {
    if(fftData==null) {
      return null;
    }
    double[][] data = new double[4][];
    int n = fftData.length/2;
    data[1] = new double[n];
    data[2] = new double[n];
    data[3] = new double[n];
    for(int i = 0; i<n; i++) {
      double cos = fftData[2*i], sin = fftData[2*i+1];
      data[1][i] = sin*sin+cos*cos;
      data[2][i] = cos;
      data[3][i] = sin;
    }
    if(radians) {
      data[0] = omega;
    } else {
      data[0] = freqs;
    }
    return data;
  }

  /**
   * 3D data is not available.
   *
   * @return double[][][]
   */
  public double[][][] getData3D() {
    return null;
  }

  /**
   * Sets the ID number of this Data.
   *
   * @param id the ID number
   */
  public void setID(int id) {
    datasetID = id;
  }

  /**
   * Returns a unique identifier for this Data.
   *
   * @return the ID number
   */
  public int getID() {
    return datasetID;
  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
