/*
 * @(#)XFourier.java    2.4.0   9 May 2005
 *
 * Copyright 2005
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package  edu.neu.ccs;

import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

import java.text.ParseException;
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
import java.io.Serializable;

/**
 * <p>Class <code>XFourier</code> provides an implementation of a
 * fourier series in one real variable with real coefficients.</p>
 *
 * <p><code>XFourier</code> fits into the family of functions
 * that implement <code>Function.OneArg</code>.</p>
 *
 * <p>For convenience of data entry, <code>XFourier</code> also
 * implements <code>Stringable</code>.</p>
 *
 * <p>Conceptually, a fourier series is a sum of three kinds of
 * entities:</p>
 *
 * <ul>
 *   <li>a constant term</li>
 *   <li>a series of <code>sin</code> terms</li>
 *   <li>a series of <code>cos</code> terms</li>
 * </ul>
 *
 * <p>The <code>sin</code> series is a sum of products where each
 * product has the form:</p>
 *
 * <p><code>s[i] * sin(i * x)</code></p>
 *
 * <p>where <code>s[i]</code> is the i-th fourier sin coefficient
 * and i &gt;= 1.</p>
 *
 * <p>The <code>cos</code> series is structured in a similar way.</p>
 *
 * <p>This class makes provisions for the input x to be either in
 * RADIANS or DEGREES.</p>
 *
 * <p>When the <code>sin</code> or <code>cos</code> series are given
 * internally using arrays of double, the 0-th entry in the array is
 * <i>ignored</i> in order to keep the array index and the term index
 * in the trigonometric series synchronized.</p>
 *
 * <p>On the other hand, when data input is given via the Stringable
 * interface, the user does NOT supply the useless 0-th degree entry
 * in the sin and cos series.  The parser makes the transition from
 * the external string format to the internal array conventions.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public class XFourier
    implements Function.OneArg, Stringable,
        JPTConstants, Serializable
{
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXFourier Error: Data format must be\n"
        + "{type|constant|[...;...]|[...;...]} or\n"
        + "{type|constant|[s1=...;s2=...;etc]|[c1=...;c2=...;etc]}\n"
        + "where type is the letter r or d for radians or degrees,\n"
        + "constant is the fourier constant term,\n"
        + "and the two groups [...;...] stand\n"
        + "for the fourier sin coefficients\n"
        + "and the fourier cos coefficients\n"
        + "in increasing order starting at index 1\n"
        + "from left to right\n";
    
    
    /**
     * <p>The abstract class to define the sin and cos functions
     * for the given input type.  Intended to be implemented
     * for either radians or degrees.</p>
     *
     * <p>The constructor has package access so only the two
     * instances defined here can be instantiated.  This is
     * done since otherwise it would be impossible to handle
     * variants in <code>XFourier.fromStringData</code>.</p>
     */
    public abstract static class Type {
        
        Type() {}
        
        public abstract double sin(double x);
        
        public abstract double cos(double x);
        
        public abstract String toString();
    }
    
    
    /**
     * <p>The type for radians input.</p>
     *
     * <p>The <code>toString</code> method returns "r".</p>
     */
    public static final Type RADIANS = new Type() {
    
        public double sin(double x) { return Math.sin(x); }
        
        public double cos(double x) { return Math.cos(x); }
        
        public String toString() { return "r"; }
    };
    
    
    /**
     * <p>The type for degrees input.</p>
     *
     * <p>The <code>toString</code> method returns "d".</p>
     */
    public static final Type DEGREES = new Type() {
    
        public double sin(double x) { return MathUtilities.sindeg(x); }
        
        public double cos(double x) { return MathUtilities.cosdeg(x); }
        
        public String toString() { return "d"; }
    };
    
    
    /** The fourier type. */
    protected Type type = RADIANS;
    
    /** The fourier constant term. */
    protected double constant = 0;
    
    /**
     * The fourier sin coefficients or <code>null</code> if no
     * storage has been allocated.
     */
    protected double[] sinCoefficients = null;
    
    /**
     * The fourier cos coefficients or <code>null</code> if no
     * storage has been allocated.
     */
    protected double[] cosCoefficients = null;
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    
    /**
     * <p>The constructor that sets the fourier to the zero with
     * type radians.</p>
     */
    public XFourier() { }
    
    
    /**
     * <p>The constructor that supplies the type, constant, and
     * array data for the sin and cos coefficients.<p>
     *
     * <p>The 0-th index entries in sinArray and cosArray are
     * ignored since no corresponding fourier terms are defined.</p>
     *
     * @param type the type: RADIANS or DEGREES
     * @param constant the fourier constant
     * @param sinArray the array of sin coefficients
     * @param cosArray the array of cos coefficients
     */
    public XFourier
        (XFourier.Type type, double constant, double[] sinArray, double[] cosArray)
    {
        setFourier(type, constant, sinArray, cosArray);
    }
    
    
    /**
     * <p>Constructs and initializes an <code>XFourier</code> from the
     * specified XFourier object.</p>
     *
     * @param fourier the <code>XFourier</code> object to copy
     */
    public XFourier(XFourier fourier) {
        copyFourier(fourier);
    }
    
    
    /**
     * The constructor that uses <code>fromStringData</code>
     * to set the fourier coefficients.
     */
    public XFourier(String data)
        throws ParseException
    {
        fromStringData(data);
    }
    
    
    /**
     * <p>Sets the fourier to the zero fourier, that is,
     * the fourier that evaluates to zero everywhere.</p>
     *
     * <p>Fires property change VALUE.</p>
     */
    public void setFourierToZero() {
        setFourier(this.type, 0, null, null);
    }
    
    
    /** <p>Returns the <code>XFourier.Type</code> parameter.</p> */
    public XFourier.Type getType() { return type; }
    
     
    /**
     * <p>Sets the <code>XFourier.Type</code> parameter to
     * determine the RADIANS or DEGREES setting.</p>
     *
     * <p>If the given type is <code>null</code>, it is set
     * to RADIANS.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param type the <code>XFourier.Type</code> parameter
     */
    public void setType(XFourier.Type type) {
        if (type == null)
            type = RADIANS;
        
        this.type = type;
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the <code>XFourier.Type</code> parameter to
     * determine the RADIANS or DEGREES setting using the
     * given string to signal the type.</p>
     *
     * <p>If the given string is <code>null</code> or has
     * length 0, the type is set to RADIANS.</p>
     *
     * <p>If the first letter of the given string is 'D'
     * or 'd', the type is set to DEGREES otherwise it is
     * set to RADIANS.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param type the <code>XFourier.Type</code> parameter
     */
    public void setTypeViaString(String string) {
        setType(getTypeViaString(string));
    }
    
    
    /** Helper method for getting type from String. */
    private XFourier.Type getTypeViaString(String string) {
        Type type = RADIANS;
        
        if (string != null)
            if (string.length() > 0) {
                char c = string.charAt(0);
                
                if ((c == 'D') || (c == 'd'))
                    type = DEGREES;
            }
        
        return type;
    }
    
    
    /** <p>Returns the fourier constant.</p> */
    public double getConstant() { return constant; }
    
    
    /**
     * <p>Sets the fourier constant to the given constant.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param constant the fourier constant
     */
    public void setConstant(double constant) {
        this.constant = constant;
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Returns the sin degree of the fourier which is defined
     * to be the highest index whose sin coefficient is non-zero;
     * by convention, the zero fourier has sin degree -1.</p>
     */
    public int getSinDegree() {
        if (sinCoefficients == null)
            return -1;
        
        int d = sinCoefficients.length - 1;
        
        while ((d >= 1) && (sinCoefficients[d] == 0))
            d--;
        
        if (d == 0)
            d = -1;
        
        return d;
    }
    
    
    /**
     * <p>Returns the cos degree of the fourier which is defined
     * to be the highest index whose cos coefficient is non-zero;
     * by convention, the zero fourier has cos degree -1.</p>
     */
    public int getCosDegree() {
        if (cosCoefficients == null)
            return -1;
        
        int d = cosCoefficients.length - 1;
        
        while ((d >= 1) && (cosCoefficients[d] == 0))
            d--;
        
        if (d == 0)
            d = -1;
        
        return d;
    }
    
    
    /**
     * <p>Returns the capacity of the current internal storage
     * for the sin terms in the fourier series,
     * that is, the largest index into which data may be stored
     * without reallocation.</p>
     *
     * <p>Returns -1 if no storage is currently allocated.</p>
     */
    public int getSinCapacity() {
        if (sinCoefficients == null)
            return -1;
        
        int d = sinCoefficients.length - 1;
        
        if (d == 0)
            d = -1;
        
        return d;
    }
    
    
    /**
     * <p>Returns the capacity of the current internal storage
     * for the cos terms in the fourier series,
     * that is, the largest index into which data may be stored
     * without reallocation.</p>
     *
     * <p>Returns -1 if no storage is currently allocated.</p>
     */
    public int getCosCapacity() {
        if (cosCoefficients == null)
            return -1;
        
        int d = cosCoefficients.length - 1;
        
        if (d == 0)
            d = -1;
        
        return d;
    }
    
    
    /**
     * <p>Sets the capacity for the sin terms of this fourier to
     * the given value except that any negative value will cause
     * the storage to be deallocated and the capacity will be -1.</p>
     *
     * <p>If the given capacity is 0, it will also be set to -1
     * since a fourier series has no degree 0 terms.</p>
     *
     * <p>If the capacity is set to a value less than the degree,
     * then information will be lost.</p>
     *
     * <p>Fires property change VALUE if information is lost
     * due to a call of this method.</p>
     *
     * @param capacity the desired storage capacity for sin terms
     */
    public void setSinCapacity(int capacity) {
        if (capacity <= 0)
            capacity = -1;
        
        if (getSinCapacity() == capacity)
            return;
        
        int d = getSinDegree();
        
        if (capacity < 0) {
            sinCoefficients = null;
        }
        else {
            int size = capacity + 1;
            
            double[] data = new double[size];
            
            if (sinCoefficients != null) {
                size = Math.min(size, sinCoefficients.length);
                
                // ignore index 0
                for (int i = 1; i < size; i++)
                    data[i] = sinCoefficients[i];
            }
            
            sinCoefficients = data;
        }
        
        // if information lost
        if (capacity < d) {
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets the capacity for the cos terms of this fourier to
     * the given value except that any negative value will cause
     * the storage to be deallocated and the capacity will be -1.</p>
     *
     * <p>If the given capacity is 0, it will also be set to -1
     * since a fourier series has no degree 0 terms.</p>
     *
     * <p>If the capacity is set to a value less than the degree,
     * then information will be lost.</p>
     *
     * <p>Fires property change VALUE if information is lost
     * due to a call of this method.</p>
     *
     * @param capacity the desired storage capacity for cos terms
     */
    public void setCosCapacity(int capacity) {
        if (capacity <= 0)
            capacity = -1;
        
        if (getCosCapacity() == capacity)
            return;
        
        int d = getCosDegree();
        
        if (capacity < 0) {
            cosCoefficients = null;
        }
        else {
            int size = capacity + 1;
            
            double[] data = new double[size];
            
            if (cosCoefficients != null) {
                size = Math.min(size, cosCoefficients.length);
                
                // ignore index 0
                for (int i = 1; i < size; i++)
                    data[i] = cosCoefficients[i];
            }
            
            cosCoefficients = data;
        }
        
        // if information lost
        if (capacity < d) {
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets the sin and cos capacity to the minimal possible
     * without loss of information.</p>
     */
    public void shrinkCapacity() {
        setSinCapacity(getSinDegree());
        setCosCapacity(getCosDegree());
    }
    
    
    /**
     * <p>Returns a copy of the fourier sin coefficients.</p>
     *
     * <p>Returns an array of size (d+1) where d is the sin degree.
     * If the sin degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     *
     * <p>The 0-th index value will be 0 since it is ignored.</p>
     */
    public double[] getSinCoefficients() {
        int d = getSinDegree();
        
        double[] values = new double[d+1];
        
        // ignore index 0
        for (int i = 1; i <= d; i++)
            values[i] = sinCoefficients[i];
        
        return values;
    }
    
    
    /**
     * <p>Returns a copy of the fourier cos coefficients.</p>
     *
     * <p>Returns an array of size (d+1) where d is the cos degree.
     * If the cos degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     *
     * <p>The 0-th index value will be 0 since it is ignored.</p>
     */
    public double[] getCosCoefficients() {
        int d = getCosDegree();
        
        double[] values = new double[d+1];
        
        // ignore index 0
        for (int i = 1; i <= d; i++)
            values[i] = cosCoefficients[i];
        
        return values;
    }
    
    
    /**
     * <p>Sets the sin coefficients of the fourier to a copy of
     * the data in the given array.</p>
     *
     * <p>The 0-th index array value will be ignored.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param sinArray the array of fourier sin coefficients to copy
     */
    public void setSinCoefficients(double[] sinArray) {
        if ((sinArray == null) || (sinArray.length <= 1)) {
            sinCoefficients = null;
        }
        else {
            int size = sinArray.length;
            
            sinCoefficients = new double[size];
            
            // ignore index 0
            for (int i = 1; i < size; i++)
                sinCoefficients[i] = sinArray[i];
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the cos coefficients of the fourier to a copy of
     * the data in the given array.</p>
     *
     * <p>The 0-th index array value will be ignored.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param cosArray the array of fourier cos coefficients to copy
     */
    public void setCosCoefficients(double[] cosArray) {
        if ((cosArray == null) || (cosArray.length <= 1)) {
            cosCoefficients = null;
        }
        else {
            int size = cosArray.length;
            
            cosCoefficients = new double[size];
            
            // ignore index 0
            for (int i = 1; i < size; i++)
                cosCoefficients[i] = cosArray[i];
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Returns the sin coefficient at the given index.</p>
     *
     * <p>Returns zero if the index is invalid.</p>
     */
    public double getSinCoefficient(int index) {
        if (index <= 0)
            return 0;
        
        int capacity = getSinCapacity();
        
        if (index > capacity)
            return 0;
        
        return sinCoefficients[index];
    }
    
    
    /**
     * <p>Returns the cos coefficient at the given index.</p>
     *
     * <p>Returns zero if the index is invalid.</p>
     */
    public double getCosCoefficient(int index) {
        if (index <= 0)
            return 0;
        
        int capacity = getCosCapacity();
        
        if (index > capacity)
            return 0;
        
        return cosCoefficients[index];
    }
    
    
    /**
     * <p>Sets the sin coefficient at the given index to the given value.</p>
     *
     * <p>Does nothing if the index is less than one.</p>
     *
     * <p>If the index is greater than the capacity, then the capacity
     * is increased to accomodate the index.</p>
     *
     * <p>Technical note: If several coefficients are to be set via this
     * method, it is most efficient to set the highest index first since
     * that will guarantee enough capacity for all other assignments.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param index the index of the coefficient
     * @param value the value of the coefficient
     */
    public void setSinCoefficient(int index, double value) {
        if (index <= 0)
            return;
        
        int capacity = getSinCapacity();
        
        if (index > capacity)
            setSinCapacity(index);
        
        sinCoefficients[index] = value;
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the cos coefficient at the given index to the given value.</p>
     *
     * <p>Does nothing if the index is less than one.</p>
     *
     * <p>If the index is greater than the capacity, then the capacity
     * is increased to accomodate the index.</p>
     *
     * <p>Technical note: If several coefficients are to be set via this
     * method, it is most efficient to set the highest index first since
     * that will guarantee enough capacity for all other assignments.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param index the index of the coefficient
     * @param value the value of the coefficient
     */
    public void setCosCoefficient(int index, double value) {
        if (index <= 0)
            return;
        
        int capacity = getCosCapacity();
        
        if (index > capacity)
            setCosCapacity(index);
        
        cosCoefficients[index] = value;
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Returns the maximum of the absolute value of the various
     * coefficients in this fourier.</p>
     */
    public double maxCoefficient() {
        double max = Math.abs(constant);
        
        int d = getSinDegree();
        
        if (d >= 1) {
            for (int i = 1; i <= d; i++)
                max = Math.max(max, Math.abs(sinCoefficients[i]));
        }
        
        d = getCosDegree();
        
        if (d >= 1) {
            for (int i = 1; i <= d; i++)
                max = Math.max(max, Math.abs(cosCoefficients[i]));
        }
        
        return max;
    }
    
    
    /**
     * <p>Sets the fourier to the given parameters.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param fourier the <code>XFourier</code> object to copy
     * @param type the type: RADIANS or DEGREES
     * @param constant the fourier constant
     * @param sinArray the array of sin coefficients
     * @param cosArray the array of cos coefficients
     */
    public void setFourier
        (XFourier.Type type, double constant, double[] sinArray, double[] cosArray)
    {
        if (type == null)
            type = RADIANS;
        
        this.type = type;
        
        this.constant = constant;
        
        if ((sinArray == null) || (sinArray.length <= 1)) {
            sinCoefficients = null;
        }
        else {
            int size = sinArray.length;
            
            sinCoefficients = new double[size];
            
            // ignore index 0
            for (int i = 1; i < size; i++)
                sinCoefficients[i] = sinArray[i];
        }
        
        if ((cosArray == null) || (cosArray.length <= 1)) {
            cosCoefficients = null;
        }
        else {
            int size = cosArray.length;
            
            cosCoefficients = new double[size];
            
            // ignore index 0
            for (int i = 1; i < size; i++)
                cosCoefficients[i] = cosArray[i];
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the data of this fourier to a copy of
     * the data in the given <code>XFourier</code>.</p>
     *
     * <p>If the given fourier is <code>null</code>, sets this
     * fourier to the zero fourier.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param fourier the <code>XFourier</code> object to copy
     */
    public void copyFourier(XFourier fourier) {
        if (fourier == null)
            setFourierToZero();
        else
            setFourier(
                fourier.getType(),
                fourier.getConstant(),
                fourier.getSinCoefficients(),
                fourier.getCosCoefficients());
    }
    
    
    /** Returns true if the fourier is the zero fourier. */
    public boolean isZero() {
        return ((constant == 0) && (getSinDegree() == -1) && (getCosDegree() == -1));
    }
    
    
    /**
     * <p>Returns true if the fourier is almost the zero fourier
     * relative to the measure epsilon, that is, if all of its
     * coefficients have absolute value less than or equal epsilon.</p>
     *
     * <p>Replaces epsilon with its absolute value before testing.</p>
     *
     * <p>Remark: If the fourier has sin degree d and cos degree e and
     * is almost zero to within epsilon, then for all values of x:</p>
     *
     * <p><code>evaluate(x) &lt;= (d + e + 1) * epsilon</code></p>
     *
     * <p>In other words, a fourier that is almost zero to within
     * epsilon has quite small values for all x.</p>
     *
     * @param epsilon the measure of closeness to zero
     */
    public boolean isAlmostZero(double epsilon) {
        epsilon = Math.abs(epsilon);
        
        return (maxCoefficient() <= epsilon);
    }
    
    
    /**
     * <p>Returns true if the given fourier is equal to this fourier
     * in the sense that all coefficients exactly agree.</p>
     *
     * <p>If the given fourier is <code>null</code> then returns false.</p>
     *
     * <p>If the type of the given fourier is not equal to the type of
     * this fourier then returns false.
     *
     * <p>This method is not called <code>equals</code> in order to avoid
     * redefinition of the default <code>hashCode</code> method.</p>
     *
     * @param fourier a fourier
     */
    public boolean isEqualTo(XFourier fourier) {
        if (fourier == null)
            return false;
        
        if (fourier.getType() != type)
            return false;
        
        if (fourier.getConstant() != constant)
            return false;
        
        int ds = getSinDegree();
        int dc = getCosDegree();
        
        int es = fourier.getSinDegree();
        int ec = fourier.getCosDegree();
        
        if ((es != ds) || (ec != dc))
            return false;
        
        // ignore index 0
        for (int i = 1; i <= ds; i++)
            if (fourier.getSinCoefficient(i) != getSinCoefficient(i))
                return false;
        
        // ignore index 0
        for (int i = 1; i <= es; i++)
            if (fourier.getCosCoefficient(i) != getCosCoefficient(i))
                return false;
        
        return true;
    }
    
    
    /**
     * <p>Returns true if the given fourier is almost equal to this fourier
     * in the sense that all coefficients agree to within the measure epsilon.</p>
     *
     * <p>Equivalently, returns true if the difference between this and the given
     * fourier is almost zero to within the measure epsilon.</p>
     *
     * <p>If the given fourier is <code>null</code> then returns false.</p>
     *
     * <p>If the type of the given fourier is not equal to the type of
     * this fourier then returns false.
     *
     * @param fourier a fourier
     * @param epsilon the measure of closeness
     */
    public boolean isAlmostEqualTo(XFourier fourier, double epsilon) {
        if (fourier == null)
            return false;
        
        if (fourier.getType() != type)
            return false;
        
        return subtract(this, fourier).isAlmostZero(epsilon);
    }
    
    
    /**
     * <p>Returns the value of the fourier at the given x.</p>
     *
     * <p>Implements <code>Function.OneArg</code>.</p>
     *
     * <p>If the current type is RADIANS, use x in radians.</p>
     *
     * <p>If the current type is DEGREES, use x in degrees.</p>
     *
     * @param x the position at which to evaluate the fourier
     */
    public double evaluate(double x) {
        double result = constant;
        
        int ds = getSinDegree();
        
        // ignore index 0
        for (int i = 1; i <= ds; i++) {
            double z = sinCoefficients[i];
            
            if (z != 0)
                result += (z * type.sin(i * x));
        }
        
        int dc = getCosDegree();
        
        // ignore index 0
        for (int i = 1; i <= dc; i++) {
            double z = cosCoefficients[i];
            
            if (z != 0)
                result += (z * type.cos(i * x));
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XFourier</CODE> as an annotated
     * string.</p>
     *
     * <p><code>
     * XFourier{type|constant|[s1=...;s2=...;etc]|[c1=...;c2=...;etc]}
     * </code></p>
     *
     * <p>where:</p>
     *
     * <ul>
     *   <li>type is the letter r or d for radians or degrees</li>
     *   <li>constant is the fourier constant term</li>
     *   <li>the next terms are the sin coefficients starting at index 1</li>
     *   <li>the next terms are the cos coefficients starting at index 1</li>
     * </ul>
     *
     * <p>The zero fourier is represented as:</p>
     *
     * <p><code>XFourier{type}</code></p>
     *
     * <p>since the type must be designated regardless.</p>
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("XFourier{");
        
        // type
        buffer.append(type);
        
        if (! isZero()) {
            // {} separator
            buffer.append('|');
            
            // constant
            buffer.append(constant);
            
            // {} separator
            buffer.append('|');
            
            // sin terms
            buffer.append('[');
            
            int ds = getSinDegree();
            
            // ignore index 0
            for (int i = 1; i <= ds; i++) {
                buffer.append('s');
                buffer.append(Integer.toString(i));
                buffer.append('=');
                buffer.append(Double.toString(sinCoefficients[i]));
                
                if (i < ds)
                    buffer.append(';');
            }
            
            buffer.append(']');
            
            // {} separator
            buffer.append('|');
            
            // cos terms
            buffer.append('[');
            
            int dc = getCosDegree();
            
            // ignore index 0
            for (int i = 1; i <= dc; i++) {
                buffer.append('c');
                buffer.append(Integer.toString(i));
                buffer.append('=');
                buffer.append(Double.toString(cosCoefficients[i]));
                
                if (i < ds)
                    buffer.append(';');
            }
            
            buffer.append(']');
        }
        
        buffer.append("}");
        
        return buffer.toString();
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XFourier</CODE> as a simple
     * string.</p>
     *
     * <p><code>
     * {type|constant|[...;...]|[...;...]}
     * </code></p>
     *
     * <p>where:</p>
     *
     * <ul>
     *   <li>type is the letter r or d for radians or degrees</li>
     *   <li>constant is the fourier constant term</li>
     *   <li>the next terms are the sin coefficients starting at index 1</li>
     *   <li>the next terms are the cos coefficients starting at index 1</li>
     * </ul>
     *
     * <p>The zero fourier is represented as:</p>
     *
     * <p><code>{type}</code></p>
     *
     * <p>since the type must be designated regardless.</p>
     */
    public String toStringData() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("{");
        
        // type
        buffer.append(type);
        
        if (! isZero()) {
            // {} separator
            buffer.append('|');
            
            // constant
            buffer.append(constant);
            
            // {} separator
            buffer.append('|');
            
            // sin terms
            buffer.append('[');
            
            int ds = getSinDegree();
            
            // ignore index 0
            for (int i = 1; i <= ds; i++) {
                buffer.append(Double.toString(sinCoefficients[i]));
                
                if (i < ds)
                    buffer.append(';');
            }
            
            buffer.append(']');
            
            // {} separator
            buffer.append('|');
            
            // cos terms
            buffer.append('[');
            
            int dc = getCosDegree();
            
            // ignore index 0
            for (int i = 1; i <= dc; i++) {
                buffer.append(Double.toString(cosCoefficients[i]));
                
                if (i < ds)
                    buffer.append(';');
            }
            
            buffer.append(']');
        }
        
        buffer.append("}");
        
        return buffer.toString();
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XFourier</CODE> object 
     * from a <CODE>String</CODE> representation of the data state.</p>
     * 
     * <p>Accepts string data in the format output by <code>toString</code>
     * or by <code>toStringData</code>.</p>
     *
     * <p>Technical note: The parser ignores any labels of the form
     * <code>s<i>i</i>=</code> or <code>c<i>i</i>=</code> and reads only
     * the fourier coefficient data from index 1 on up in each case.</p>
     *
     * <p>The labels if present simply help the user track
     * the index of each coefficient as data is entered.  In particular,
     * it is not valid to omit coefficients and depend on the labels to
     * make this clear.</p>
     * 
     * <p>Fires property change VALUE.</p>
     *
     * @param data <CODE>String</CODE> representation of the data state
     * @throws ParseException if the data is malformed
     */
    public void fromStringData(String data)
        throws ParseException
    {
        if (data == null)
            throw new ParseException(standardMessage, -1);
        
        data = data.trim();
        
        String[] split = Strings.splitIn2(data);
        
        String input = split[1].trim();
        
        if (! Strings.isStartEnd(input, Strings.BRACES, Strings.BRACES_END))
             throw new ParseException(standardMessage, -1);
        
        String[] items = Strings.trim(Strings.decode(input));
        
        int length = items.length;
        
        // items length must be 1 or 4
        if (length == 1) {
            setFourier(getTypeViaString(items[0]), 0, null, null);
            return;
        }
        else
        if (length == 4) {
            parse4(items);
            return;
        }
        else
            throw new ParseException(standardMessage, -1);
    }
    
    
    /**
     * <p>Helper method for parsing non-zero fourier.
     *
     * @param items the array of the 4 main fourier terms
     */
    private void parse4(String[] items)
        throws ParseException
    {
        Type type = getTypeViaString(items[0]);
        double constant = parseFourierConstant(items[1]);
        double[] sinArray = parseFourierCoefficients(items[2], "sin");
        double[] cosArray = parseFourierCoefficients(items[3], "cos");
        
        setFourier(type, constant, sinArray, cosArray);
    }
    
    
    /*
     * <p>Helper method to parse the fourier constant.</p>
     *
     * @param term the constant term
     */
    private double parseFourierConstant(String term)
        throws ParseException
    {
        double value = 0;
        
        try {
            XDouble xdouble = new XDouble();
            xdouble.fromStringData(term);
            value = xdouble.getValue();
        }
        catch(ParseException ex) {
            String message = ex.getMessage();
            int offset = ex.getErrorOffset();
            
            message =
                "\nXFourier constant error:\n"
                + message
                + "\nat offset: "
                + offset
                + "\n";
            
            throw new ParseException(message, -1);
        }
        
        return value;
    }
    
    
    /*
     * <p>Helper method to parse the fourier sin or cos coefficients.</p>
     *
     * @param terms the coefficient terms as [...]
     * @param name  either "sin" or "cos"
     */
    private double[] parseFourierCoefficients(String terms, String name)
        throws ParseException
    {
        double[] array = null;
        
        try {
            if (! Strings.isStartEnd(terms, Strings.BRACKETS, Strings.BRACKETS_END))
                 throw new ParseException(standardMessage, -1);
            
            // insert leading 0; to get external index to match internal index
            String expandedterms = "[0;" + terms.substring(1);
            
            String[] inner = Strings.trim(Strings.decode(expandedterms));
            inner = Strings.getValues(inner);
            
            array = Strings.stringsToDoubles(inner);
        }
        catch(ParseException ex) {
            String message = ex.getMessage();
            int offset = ex.getErrorOffset();
            
            message =
                "\nXFourier " + name + " term error:\n"
                + terms
                + "\nin term "
                + message
                + "\nat offset "
                + offset
                + "\n";
            
            throw new ParseException(message, -1);
        }
        
        return array;
    }
    
    
    /**
     * <p>Returns a new fourier equivalent mathematically to (f * p),
     * that is, the fourier obtained by multipying each coefficient
     * of p by the scale factor f.</p>
     *
     * <p>If the fourier p is <code>null</code> or the zero fourier
     * returns a zero fourier.</p>
     *
     * @param f a scale factor
     * @param fourier a fourier
     */
    public static XFourier scale(double f, XFourier fourier) {
        if (fourier == null)
            return new XFourier();
        
        Type type = fourier.getType();
        
        double constant = f * fourier.getConstant();
        
        int ds = fourier.getSinDegree();
        int dc = fourier.getCosDegree();
        
        double[] sinArray = null;
        double[] cosArray = null;
        
        if (ds > 0)
            sinArray = new double[ds + 1];
        
        if (dc > 0)
            cosArray = new double[dc + 1];
        
        for (int i = 1; i <= ds; i++)
            sinArray[i] = f * fourier.getSinCoefficient(i);
        
        for (int i = 1; i <= dc; i++)
            cosArray[i] = f * fourier.getCosCoefficient(i);
        
        return new XFourier(type, constant, sinArray, cosArray);
    }
    
    
    /**
     * <p>Returns a new fourier equivalent mathematically to (p + q).</p>
     *
     * <p>If either fourier is <code>null</code> or the zero fourier
     * returns a copy of the other fourier.</p>
     *
     * <p>If p and q do not have the same type, an exception is thrown.</p>
     *
     * @param p a fourier
     * @param q a fourier
     */
    public static XFourier add(XFourier p, XFourier q) {
        if (p == null)
            return new XFourier(q);
        
        if (q == null)
            return new XFourier(p);
        
        if (p.getType() != q.getType())
            throw new RuntimeException
                ("Input type mismatch in XFourier.add");
        
        Type type = p.getType();
        
        double constant = p.getConstant() + q.getConstant();
        
        int ds = Math.max(p.getSinDegree(), q.getSinDegree());
        int dc = Math.max(p.getCosDegree(), q.getCosDegree());
        
        double[] sinArray = null;
        double[] cosArray = null;
        
        if (ds > 0)
            sinArray = new double[ds + 1];
        
        if (dc > 0)
            cosArray = new double[dc + 1];
        
        for (int i = 1; i <= ds; i++)
            sinArray[i] = p.getSinCoefficient(i) + q.getSinCoefficient(i);
        
        for (int i = 1; i <= dc; i++)
            cosArray[i] = p.getCosCoefficient(i) + q.getCosCoefficient(i);
        
        return new XFourier(type, constant, sinArray, cosArray);
    }
    
    
    /**
     * <p>Returns a new fourier equivalent mathematically to (p - q).</p>
     *
     * <p>If fourier q is <code>null</code> or the zero fourier
     * returns a copy of fourier p.</p>
     *
     * <p>If fourier p is <code>null</code> or the zero fourier
     * returns a copy of the negative of fourier q.</p>
     *
     * <p>If p and q do not have the same type, an exception is thrown.</p>
     *
     * @param p a fourier
     * @param q a fourier
     */
    public static XFourier subtract(XFourier p, XFourier q) {
        if (p == null)
            return scale(-1, q);
        
        if (q == null)
            return new XFourier(p);
        
        if (p.getType() != q.getType())
            throw new RuntimeException
                ("Input type mismatch in XFourier.subtract");
        
        Type type = p.getType();
        
        double constant = p.getConstant() - q.getConstant();
        
        int ds = Math.max(p.getSinDegree(), q.getSinDegree());
        int dc = Math.max(p.getCosDegree(), q.getCosDegree());
        
        double[] sinArray = null;
        double[] cosArray = null;
        
        if (ds > 0)
            sinArray = new double[ds + 1];
        
        if (dc > 0)
            cosArray = new double[dc + 1];
        
        for (int i = 1; i <= ds; i++)
            sinArray[i] = p.getSinCoefficient(i) - q.getSinCoefficient(i);
        
        for (int i = 1; i <= dc; i++)
            cosArray[i] = p.getCosCoefficient(i) - q.getCosCoefficient(i);
        
        return new XFourier(type, constant, sinArray, cosArray);
    }
    
    
    /////////////////////////////
    // Property Change Support //
    /////////////////////////////
    
    /**
     * Registers the given object 
     * to listen for property change events 
     * generated by this object.
     *
     * @param listener the listener to be registered
     */
    public void addPropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(listener);
    }
    
    
    /**
     * Registers the given object 
     * to listen for property change events
     * generated by this object
     * with the given property name.
     *
     * @param propertyName the name of the desired property 
     * @param listener the listener to be registered
     */
    public void addPropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(
            propertyName, 
            listener);
    }
    
    
    /**
     * Deregisters the given object 
     * from listening for property change events 
     * generated by this object.
     *
     * @param listener the listener to be deregistered
     */
    public void removePropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(listener);
    }
    
    
    /**
     * Deregisters the given object 
     * from listening for property change events
     * generated by this object
     * with the given property name.
     *
     * @param propertyName the name of the desired property 
     * @param listener the listener to be deregistered
     */
    public void removePropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(
            propertyName, 
            listener);
    }
    
}
