/*
 * 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.FFT;

/**
 * FourierAnalysis adds gutter points to complex-number 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 FourierAnalysis implements Data {
  static final double PI2 = 2*Math.PI;
  FFT fft = new FFT();
  double[] fftData, omega, freqs;
  double[] cosVec, sinVec, gutterVec;
  ComplexDataset[] complexDatasets = new ComplexDataset[1];
  Dataset[] realDatasets = new Dataset[2];
  boolean radians = false;
  private String name = "Fourier Analysis Complex Data"; //$NON-NLS-1$
  protected int datasetID = hashCode();

  /**
   * Fourier analyzes the given complex data z[] after adding gutter points at the start and end of the z[] array.
   *
   * @param x double[]
   * @param z double[]
   * @param gutter int
   * @return double[] the Fourier spectrum
   */
  public double[] doAnalysis(double[] x, double[] z, int gutter) {
    fftData = new double[z.length+4*gutter];
    gutterVec = new double[2*gutter];
    System.arraycopy(z, 0, fftData, 2*gutter, z.length);
    fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts.
    fft.toNaturalOrder(fftData);
    double dx = x[1]-x[0];
    double xmin = x[0]-gutter*dx;
    double xmax = x[x.length-1]+(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 = fftData.length/(z.length);
    //double norm=1;
    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);
    }
    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;
  }

  /**
   * Repeats the Fourier analysis of the complex data z[] with the previously set scale and gutter.
   *
   * @param z double[]
   * @return double[] the Fourier spectrum
   */
  public double[] repeatAnalysis(double[] z) {
    if(fftData==null) {
      int n = z.length;
      double[] x = new double[n];
      double x0 = 0, dx = 1.0/n;
      for(int i = 0; i<n; i++) {
        x[i] = x0;
        x0 += dx;
      }
      return doAnalysis(x, z, 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(z, 0, fftData, gutterVec.length, z.length);
    fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts.
    fft.toNaturalOrder(fftData);
    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 a list that contains the complex dataset of the last Fourier analysis.
   * Complex dataset x-values are either frequencies (cycles) or angular frequencies (radians) depending
   * on the value of the radians flag.
   *
   * @return list of ComplexDatasets
   */
  public java.util.List<Data> getDataList() {
    java.util.ArrayList<Data> list = new java.util.ArrayList<Data>();
    if(fftData==null) {
      return list;
    }
    if(complexDatasets[0]==null) {
      complexDatasets[0] = new ComplexDataset();
      complexDatasets[0].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
        DisplayRes.getString("FourierAnalysis.Column.Real"),                                        //$NON-NLS-1$
          DisplayRes.getString("FourierAnalysis.Column.Imaginary"));                                //$NON-NLS-1$
    } else {
      complexDatasets[0].clear();
    }
    if(radians) {
      complexDatasets[0].append(omega, fftData);
    } else {
      complexDatasets[0].append(freqs, fftData);
    }
    list.add(complexDatasets[0]);
    return list;
  }

  /**
   * Gets the complex datasets that contain the result of the last Fourier analysis.
   * Real coefficients are contained in the first dataset.
   * Complex coefficients are in the second dataset.
   *
   * @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("FourierAnalysis.Column.Real"),                                     //$NON-NLS-1$
          DisplayRes.getString("FourierAnalysis.RealCoefficients"));                             //$NON-NLS-1$
      realDatasets[0].setLineColor(Color.RED);
      realDatasets[1] = new Dataset();
      realDatasets[1].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
        DisplayRes.getString("FourierAnalysis.Column.Imaginary"),                                //$NON-NLS-1$
          DisplayRes.getString("FourierAnalysis.ImaginaryCoefficients"));                        //$NON-NLS-1$
      realDatasets[1].setLineColor(Color.BLUE);
    } else {
      realDatasets[0].clear();
      realDatasets[1].clear();
    }
    if(radians) {
      for(int i = 0, nOmega = omega.length; i<nOmega; i++) {
        double re = fftData[2*i], im = fftData[2*i+1];
        realDatasets[0].append(omega[i], re);
        realDatasets[1].append(omega[i], im);
      }
    } else {
      for(int i = 0, nFreqs = freqs.length; i<nFreqs; i++) {
        double re = fftData[2*i], im = fftData[2*i+1];
        realDatasets[0].append(freqs[i], re);
        realDatasets[1].append(freqs[i], im);
      }
    }
    list.add(realDatasets[0]);
    list.add(realDatasets[1]);
    return list;
  }

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

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

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

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

  /**
   * The column names to be used in the data display tool
   * @return
   */
  public String[] getColumnNames() {
    return new String[] {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;
  }

  /**
   * 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
 */
