/*
 * @(#)XPolynomialComplex.java    2.4.0   26 July 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>XPolynomialComplex</code> provides an implementation
 * of a polynomial in one complex variable with complex coefficients.</p>
 *
 * <p><code>XPolynomialComplex</code> fits into the family of functions
 * that implement <code>FunctionComplex.OneArg</code>.</p>
 *
 * <p>For convenience of data entry, <code>XPolynomialComplex</code> also
 * implements <code>Stringable</code>.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public class XPolynomialComplex
    implements FunctionComplex.OneArg, ParameterComplex.ArrayParam,
        Stringable, JPTConstants, Serializable
{
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXPolynomialComplex Error: Data format must be\n"
        + "{[...;...]|[...;...]|[...;...]|etc} or\n"
        + "{c0=[...;...]|c1=[...;...]|c2=[...;...]|etc}\n"
        + "where [...;...] stands for the complex numbers\n"
        + "that constitute the polynomial coefficients\n"
        + "in increasing order starting at index 0\n"
        + "from left to right\n";
    
    
    /** Division by zero error message. */
    private static String divisionMessage =
        "Division by zero in class XPolynomialComplex";
    
    
    /**
     * <p>The polynomial coefficients or <code>null</code> if no
     * storage has been allocated.</p>
     *
     * <p>If the coefficients array is non-<code>null</code>, it
     * may nevertheless have <code>null</code> entries which are
     * treated as equal to zero.  This avoid excess allocation
     * of complex number storage that may not be used.</p>
     */
    protected XComplex[] coefficients = null;
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    
    /**
     * <p>The constructor that sets the polynomial to the zero
     * polynomial but allocates no storage for future coefficients.</p>
     */
    public XPolynomialComplex() { }
    
    
    /**
     * <p>The constructor that sets the polynomial to the zero
     * polynomial and allocates storage with the given capacity.</p>
     *
     * @param capacity the desired storage capacity
     */
    public XPolynomialComplex(int capacity) {
        setCapacity(capacity);
    }
    
    
    /**
     * <p>The constructor that sets the polynomial coefficients to a
     * copy of the given params array.</p>
     *
     * <p>If <code>params</code> is <code>null</code> or of length 0,
     * sets the polynomial to the zero polynomial
     * but allocates no storage for future coefficients.</p>
     *
     * @param params the array of polynomial coefficients to copy
     */
    public XPolynomialComplex(XComplex[] params) {
        setCoefficients(params);
    }
    
    
    /**
     * <p>The constructor that sets the polynomial coefficients to a
     * copy of the given params array.</p>
     *
     * <p>If <code>params</code> is <code>null</code> or of length 0,
     * sets the polynomial to the zero polynomial
     * but allocates no storage for future coefficients.</p>
     *
     * @param params the double[][] array of data to copy
     */
    public XPolynomialComplex(double[][] params) {
        setCoefficients(params);
    }
    
    
    /**
     * <p>The constructor that sets the polynomial coefficients to a
     * copy of the given params array.</p>
     *
     * <p>If <code>params</code> is <code>null</code> or of length 0,
     * sets the polynomial to the zero polynomial
     * but allocates no storage for future coefficients.</p>
     *
     * @param params the float[][] array of data to copy
     */
    public XPolynomialComplex(float[][] params) {
        setCoefficients(params);
    }
    
    
    /**
     * <p>Constructs and initializes an <code>XPolynomialComplex</code> from
     * the specified XPolynomialComplex object.</p>
     *
     * <p>If <code>polynomial</code> is <code>null</code>,
     * sets the polynomial to the zero polynomial
     * but allocates no storage for future coefficients.</p>
     *
     * @param polynomial the <code>XPolynomialComplex</code> object to copy
     */
    public XPolynomialComplex(XPolynomialComplex polynomial) {
        if (polynomial != null)
            setCoefficients(polynomial.coefficients);
    }
    
    
    /**
     * The constructor that uses <code>fromStringData</code>
     * to set the polynomial coefficients.
     */
    public XPolynomialComplex(String data)
        throws ParseException
    {
        fromStringData(data);
    }
    
    
    /**
     * The constructor that uses <code>fromStringArrayData</code>
     * to set the polynomial coefficients.
     */
    public XPolynomialComplex(String[] strings)
        throws ParseException
    {
        fromStringArrayData(strings);
    }
    
    
    /**
     * <p>Sets the polynomial to the zero polynomial, that is,
     * the polynomial that evaluates to zero everywhere.</p>
     *
     * <p>Fires property change VALUE.</p>
     */
    public void setPolynomialToZero() {
        coefficients = null;
        
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Returns the degree of the polynomial which is defined
     * to be the highest index whose coefficient is non-zero;
     * by convention, the zero polynomial has degree -1.</p>
     */
    public int getDegree() {
        if (coefficients == null)
            return -1;
        
        int d = coefficients.length - 1;
        
        while ((d >= 0) && XComplex.isZero(coefficients[d]))
            d--;
        
        return d;
    }
    
    
    /**
     * <p>Returns the capacity of the current internal storage,
     * that is, the largest index into which data may be stored
     * without reallocation of the coefficients array.</p>
     *
     * <p>Returns -1 if no storage is currently allocated.</p>
     */
    public int getCapacity() {
        if (coefficients == null)
            return -1;
        
        return coefficients.length - 1;
    }
    
    
    /**
     * <p>Sets the capacity of this polynomial to the given value
     * except that any negative value will cause the polynomial
     * storage to be deallocated and the capacity will be -1.</p>
     *
     * <p>This method does <i>not</i> allocate new storage for
     * individual complex coefficients since by convention this
     * class treats a <code>null</code> coefficient as equal to
     * zero.</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
     */
    public void setCapacity(int capacity) {
        if (capacity < 0)
            capacity = -1;
        
        if (getCapacity() == capacity)
            return;
        
        int d = getDegree();
        
        if (capacity < 0) {
            coefficients = null;
        }
        else {
            int size = capacity + 1;
            
            XComplex[] data = new XComplex[size];
            
            if (coefficients != null) {
                size = Math.min(size, coefficients.length);
                
                for (int i = 0; i < size; i++)
                    data[i] = coefficients[i];
            }
            
            coefficients = data;
        }
        
        // if information lost
        if (capacity < d) {
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets the capacity to the degree.</p>
     *
     * <p>Equivalent to <code>setCapacity(getDegree())</code>.</p>
     */
    public void shrinkCapacity() {
        setCapacity(getDegree());
    }
    
    
    /**
     * <p>Returns a copy of the polynomial coefficients.</p>
     *
     * <p>Returns an array of size (d+1) where d is the degree.
     * If the degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     *
     * <p>Uses <code>XComplex.copy</code> so it is possible that
     * some items in the array returned are <code>null</code>.</p>
     *
     * <p>Note that the internal coefficient capacity is ignored
     * by this method.</p>
     */
    public XComplex[] getCoefficients() {
        int d = getDegree();
        
        XComplex[] values = new XComplex[d+1];
        
        for (int i = 0; i <= d; i++)
            values[i] = XComplex.copy(coefficients[i]);
        
        return values;
    }
    
    
    /**
     * <p>Returns a copy of the polynomial coefficient data as
     * an array of type double[][].</p>
     *
     * <p>Returns an array of size (d+1) where d is the degree.
     * If the degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     *
     * <p>Uses <code>XComplex.toData</code> so it is possible that
     * some items in the array returned are <code>null</code>.</p>
     *
     * <p>Note that the internal coefficient capacity is ignored
     * by this method.</p>
     */
    public double[][] toData() {
        int d = getDegree();
        
        double[][] values = new double[d+1][0];
        
        for (int i = 0; i <= d; i++)
            values[i] = XComplex.toData(coefficients[i]);
        
        return values;
    }
    
    
    /**
     * <p>Sets the coefficients of the polynomial to a copy of
     * the data in the given params array.</p>
     *
     * <p>If the given params is <code>null</code> or of length
     * 0, sets this polynomial to the zero polynomial.</p>
     *
     * <p>Uses <code>XComplex.copy</code> to copy the data from
     * the given params array.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param params the array of polynomial coefficients to copy
     */
    public void setCoefficients(XComplex[] params) {
        if ((params == null) || (params.length == 0)) {
            coefficients = null;
        }
        else {
            coefficients = XComplex.copy(params);
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the coefficients of the polynomial to a copy of
     * the data in the given params array.</p>
     *
     * <p>If the given params is <code>null</code> or of length
     * 0, sets this polynomial to the zero polynomial.</p>
     *
     * <p>Uses <code>XComplex.copyData</code> to copy the data
     * from the given params array.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param params the double[][] array of data to copy
     */
    public void setCoefficients(double[][] params) {
        if ((params == null) || (params.length == 0)) {
            coefficients = null;
        }
        else {
            coefficients = XComplex.copyData(params);
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the coefficients of the polynomial to a copy of
     * the data in the given params array.</p>
     *
     * <p>If the given params is <code>null</code> or of length
     * 0, sets this polynomial to the zero polynomial.</p>
     *
     * <p>Uses <code>XComplex.copyData</code> to copy the data
     * from the given params array.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param params the float[][] array of data to copy
     */
    public void setCoefficients(float[][] params) {
        if ((params == null) || (params.length == 0)) {
            coefficients = null;
        }
        else {
            coefficients = XComplex.copyData(params);
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the coefficients of this polynomial to a copy of
     * the data in the given <code>XPolynomialComplex</code>.</p>
     *
     * <p>If the given polynomial is <code>null</code>, sets this
     * polynomial to the zero polynomial.</p>
     *
     * <p>Uses <code>XComplex.copy</code> to copy the data from
     * the given polynomial.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param polynomial the <code>XPolynomialComplex</code> object to copy
     */
    public void setCoefficients(XPolynomialComplex polynomial) {
        if (polynomial == null)
            setPolynomialToZero();
        else
            setCoefficients(polynomial.coefficients);
    }
    
    
    /**
     * <p>Returns a copy of the coefficient at the given index.</p>
     *
     * <p>Uses <code>XComplex.copy</code>.</p>
     *
     * <p>Returns <code>null</code> if the index is invalid or the
     * internal coefficient at the index is <code>null</code>.</p>
     *
     * @param index the index of the coefficient
     */
    public XComplex getCoefficient(int index) {
        if (index < 0)
            return null;
        
        int capacity = getCapacity();
        
        if (index > capacity)
            return null;
        
        return XComplex.copy(coefficients[index]);
    }
    
    
    /**
     * <p>Sets the coefficient at the given index to a copy of the given
     * value.</p>
     *
     * <p>Does nothing if the index is less than zero.</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>Uses <code>XComplex.copy</code>.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param index the index of the coefficient
     * @param value the value of the coefficient
     */
    public void setCoefficient(int index, XComplex value) {
        if (index < 0)
            return;
        
        int capacity = getCapacity();
        
        if (index > capacity)
            setCapacity(index);
        
        coefficients[index] = XComplex.copy(value);
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Returns the maximum of the method <code>maxabs</code> applied to
     * the coefficients in this polynomial.</p>
     *
     * <p>We use <code>maxabs</code> rather than <code>abs</code> to avoid
     * the computation of numerous square roots.</p>
     *
     * <p>Returns 0 if the polynomial has no non-trivial coefficients.</p>
     */
    public double maxCoefficient() {
        int d = getDegree();
        
        if (d == -1)
            return 0;
        
        double max = XComplex.maxabs(coefficients[d]);
        
        for (int i = 0; i < d; i++)
            max = Math.max(max, XComplex.maxabs(coefficients[i]));
        
        return max;
    }
    
    
    /** Returns true if the polynomial is the zero polynomial. */
    public boolean isZero() {
        return (getDegree() == -1);
    }
    
    
    /**
     * <p>Returns true if the polynomial is almost the zero polynomial
     * relative to the measure epsilon, that is, if all of its
     * coefficients have a <code>maxabs</code> value that is less than
     * or equal to epsilon.</p>
     *
     * <p>Replaces epsilon with its absolute value before testing.</p>
     *
     * <p>Remark: If the polynomial has degree d and is almost zero to
     * within epsilon, then for x with absolute value at most one:</p>
     *
     * <p><pre>  abs(evaluate(x)) &lt;= (d + 1) * sqrt(2) * epsilon</pre></p>
     *
     * <p>In other words, a polynomial that is almost zero to within
     * epsilon has quite small values for arguments x at most one.</p>
     *
     * <p>The reason for <code>sqrt(2)</code> in the above estimate is
     * that we use <code>maxabs</code> to bound the size of the
     * coefficients rather than the more expensive <code>abs</code>;
     * we also use the relation: <code>abs&lt;=sqrt(2)*maxabs</code>.</p>
     *
     * @param epsilon the measure of closeness to zero
     */
    public boolean isAlmostZero(double epsilon) {
        int d = getDegree();
        
        if (d == -1)
            return true;
        
        epsilon = Math.abs(epsilon);
        
        for (int i = 0; i <= d; i++)
            if (XComplex.maxabs(coefficients[i]) > epsilon)
                return false;
        
        return true;
    }
    
    
    /**
     * <p>Returns true if the given polynomial is equal to <code>this</code>
     * in the sense that the degrees are the same and all coefficients agree
     * using <code>XComplex.isEqualTo</code> to test equality.</p>
     *
     * <p>If the given polynomial is <code>null</code> then returns true
     * if and only if <code>this</code> equals the zero polynomial.</p>
     *
     * <p>Ignores polynomial capacity since this is irrelevant as far as
     * testing equality.</p>
     *
     * @param p a polynomial
     */
    public boolean isEqualTo(XPolynomialComplex p) {
        if (p == null)
            return isZero();
        
        int d = getDegree();
        int e = p.getDegree();
        
        if (e != d)
            return false;
        
        for (int i = 0; i <= d; i++)
            if(! XComplex.isEqualTo(coefficients[i], p.coefficients[i]))
                return false;
        
        return true;
    }
    
    
    /**
     * <p>Returns true if the given polynomial is almost equal to this polynomial
     * 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
     * polynomial is almost zero to within the measure epsilon.</p>
     *
     * <p>If the given polynomial is <code>null</code> then returns true
     * if and only if <code>this</code> is almost zero to within epsilon.</p>
     *
     * @param p a polynomial
     * @param epsilon the measure of closeness
     */
    public boolean isAlmostEqualTo(XPolynomialComplex p, double epsilon) {
        if (p == null)
            return isAlmostZero(epsilon);
        
        int d = getDegree();
        int e = p.getDegree();
        
        int n = Math.max(d, e);
        
        epsilon = Math.abs(epsilon);
        
        for (int i = 0; i <= n; i++) {
            double x1 = 0;
            double y1 = 0;
            
            double x2 = 0;
            double y2 = 0;
            
            if ((i <= d) && (coefficients[i] != null)) {
                x1 = coefficients[i].Re();
                y1 = coefficients[i].Im();
            }
            
            
            if ((i <= e) && (p.coefficients[i] != null)) {
                x2 = p.coefficients[i].Re();
                y2 = p.coefficients[i].Im();
            }
            
            double max = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
            
            if (max > epsilon)
                return false;
        }
        
        return true;
    }
    
    
    /**
     * <p>Returns the value of the polynomial at the given x.</p>
     *
     * <p>Implements <code>FunctionComplex.OneArg</code>.</p>
     *
     * @param x the position at which to evaluate the polynomial
     */
    public XComplex evaluate(XComplex x) {
        int d = getDegree();
        
        if (d < 0)
            return new XComplex();
        
        if (XComplex.isZero(x))
            return new XComplex(coefficients[0]);
        
        XComplex result = new XComplex(coefficients[d]);
        
        for (int i = d - 1; i >= 0; i--) {
            result.multiply(x);
            result.add(coefficients[i]);
        }
        
        return result;
    }
    
    
    /**
     * <p>Sets the coefficients of the polynomial to a copy of
     * the data in the given params array.</p>
     *
     * <p>Implements <code>ParameterComplex.ArrayParam</code>.</p>
     *
     * <p>Equivalent to <code>setCoefficients</code>.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param params the array of polynomial coefficients to copy
     */
    public void setParam(XComplex[] params) {
        setCoefficients(params);
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XPolynomialComplex</CODE> as an
     * annotated string.</p>
     *
     * <p><code>XPolynomialComplex{c0=[...;...]|c1=[...;...]|c2=[...;...]|etc}</code></p>
     *
     * <p>where [...;...] stands for the complex numbers that form the
     * polynomial coefficients in increasing order from left to right.</p>
     *
     * <p>The zero polynomial is represented as:</p>
     *
     * <p><code>XPolynomialComplex{}</code></p>
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("XPolynomialComplex{");
        
        int d = getDegree();
        
        for (int i = 0; i <= d; i++) {
            buffer.append('c');
            buffer.append(Integer.toString(i));
            buffer.append('=');
            buffer.append(XComplex.toStringData(coefficients[i]));
            
            if (i < d)
                buffer.append('|');
        }
        
        buffer.append("}");
        
        return buffer.toString();
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XPolynomialComplex</CODE> as a
     * simple string.</p>
     *
     * <p><code>{[...;...]|[...;...]|[...;...]|etc}</code></p>
     *
     * <p>where [...;...] stands for the complex numbers that form the
     * polynomial coefficients in increasing order from left to right.</p>
     *
     * <p>The zero polynomial is represented as:</p>
     *
     * <p><code>{}</code></p>
     */
    public String toStringData() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("{");
        
        int d = getDegree();
        
        for (int i = 0; i <= d; i++) {
            buffer.append(XComplex.toStringData(coefficients[i]));
            
            if (i < d)
                buffer.append('|');
        }
        
        buffer.append("}");
        
        return buffer.toString();
    }
    
    
    /**
     * <p>Returns the data state of this <CODE>XPolynomialComplex</CODE>
     * as an array of strings.</p>
     *
     * <p>This is a convenience method that allows to caller to use the
     * string representations of the individual coefficients in any way
     * desired.</p>
     *
     * <p>Returns an array of size (d+1) where d is the degree.
     * If the degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     */
    public String[] toStringArrayData() {
        int d = getDegree();
        
        String[] strings = new String[d+1];
        
        for (int i = 0; i <= d; i++)
            strings[i] = XComplex.toStringData(coefficients[i]);
        
        return strings;
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XPolynomialComplex</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>c<i>i</i>=</code> and reads only the coefficient data from
     * index 0 on up.</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));
        
        fromStringArrayData(items);
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XPolynomialComplex</CODE> object 
     * from a <CODE>String</CODE> array representation of the data state.</p>
     * 
     * <p>Each string in the array is used to defining the correspoding
     * coefficient in the polynomial.</p>
     *
     * <p>Technical note: The parser ignores any labels of the form
     * <code>c<i>i</i>=</code> and reads only the coefficient data from
     * index 0 on up.</p>
     * 
     * <p>Fires property change VALUE.</p>
     *
     * @param strings <CODE>String</CODE> array representation of the data state
     * @throws ParseException if the data is malformed
     */
    public void fromStringArrayData(String[] strings)
        throws ParseException
    {
        if (strings == null)
            throw new ParseException(standardMessage, -1);
        
        String[] values = Strings.getValues(strings);
        
        try {
            setCoefficients(Strings.stringsToXComplexValues(values));
        }
        catch (ParseException ex) {
            String message = ex.getMessage();
            int offset = ex.getErrorOffset();
            
            message =
                "\nXPolynomial error in term "
                + message
                + "\nat offset "
                + offset
                + "\n";
            
            throw new ParseException(message, -1); 
        }
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (f * p),
     * that is, the polynomial obtained by multipying each coefficient
     * of p by the complex scale factor f.</p>
     *
     * <p>If the polynomial p is <code>null</code> or the zero polynomial
     * or if f is <code>null</code> returns a zero polynomial.</p>
     *
     * @param f a scale factor
     * @param p a polynomial
     */
    public static XPolynomialComplex scale(XComplex f, XPolynomialComplex p) {
        if ((p == null) || XComplex.isZero(f))
            return new XPolynomialComplex();
        
        int d = p.getDegree();
        
        if (d == -1)
            return new XPolynomialComplex();
        
        XPolynomialComplex result = new XPolynomialComplex(d);
        
        for (int i = 0; i <= d; i++)
            result.coefficients[i] = XComplex.multiply(f, p.coefficients[i]);
        
        return result;
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (p + q).</p>
     *
     * <p>If either polynomial is <code>null</code> or the zero polynomial
     * returns a copy of the other polynomial.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomialComplex add
        (XPolynomialComplex p, XPolynomialComplex q)
    {
        if (p == null)
            return new XPolynomialComplex(q);
        
        if (q == null)
            return new XPolynomialComplex(p);
        
        int d = p.getDegree();
        int e = q.getDegree();
        
        if (d == -1)
            return new XPolynomialComplex(q);
        
        if (e == -1)
            return new XPolynomialComplex(p);
        
        int n = Math.max(d, e);
        
        XPolynomialComplex result = new XPolynomialComplex(n);
        
        for (int i = 0; i <= n; i++) {
            double x1 = 0;
            double y1 = 0;
            
            double x2 = 0;
            double y2 = 0;
            
            if ((i <= d) && (p.coefficients[i] != null)) {
                x1 = p.coefficients[i].Re();
                y1 = p.coefficients[i].Im();
            }
            
            
            if ((i <= e) && (q.coefficients[i] != null)) {
                x2 = q.coefficients[i].Re();
                y2 = q.coefficients[i].Im();
            }
            
            result.coefficients[i] = XComplex.add(x1, y1, x2, y2);
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (p - q).</p>
     *
     * <p>If polynomial q is <code>null</code> or the zero polynomial
     * returns a copy of polynomial p.</p>
     *
     * <p>If polynomial p is <code>null</code> or the zero polynomial
     * returns a copy of the negative of polynomial q.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomialComplex subtract
        (XPolynomialComplex p, XPolynomialComplex q)
    {
        if (p == null)
            return new XPolynomialComplex(q);
        
        if (q == null)
            return new XPolynomialComplex(p);
        
        int d = p.getDegree();
        int e = q.getDegree();
        
        if (d == -1)
            return new XPolynomialComplex(q);
        
        if (e == -1)
            return new XPolynomialComplex(p);
        
        int n = Math.max(d, e);
        
        XPolynomialComplex result = new XPolynomialComplex(n);
        
        for (int i = 0; i <= n; i++) {
            double x1 = 0;
            double y1 = 0;
            
            double x2 = 0;
            double y2 = 0;
            
            if ((i <= d) && (p.coefficients[i] != null)) {
                x1 = p.coefficients[i].Re();
                y1 = p.coefficients[i].Im();
            }
            
            
            if ((i <= e) && (q.coefficients[i] != null)) {
                x2 = q.coefficients[i].Re();
                y2 = q.coefficients[i].Im();
            }
            
            result.coefficients[i] = XComplex.subtract(x1, y1, x2, y2);
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (p * q).</p>
     *
     * <p>If either polynomial is <code>null</code> or the zero polynomial
     * returns a zero polynomial.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomialComplex multiply
        (XPolynomialComplex p, XPolynomialComplex q)
    {
        if ((p == null) || (q == null))
            return new XPolynomialComplex();
        
        int d = p.getDegree();
        int e = q.getDegree();
        
        if ((d == -1) || (e == -1))
            return new XPolynomialComplex();
        
        int n = d + e;
        
        XPolynomialComplex result = new XPolynomialComplex(n);
        
        for (int k = 0; k <= n; k++)
            result.coefficients[k] = new XComplex();
        
        for (int i = 0; i <= d; i++)
            for (int j = 0; j <= e; j++)
                result.coefficients[i+j].addProduct
                    (p.coefficients[i], q.coefficients[j]);
        
        return result;
    }
    
    
    /**
     * <p>Returns a polynomial array consisting of 2 items: the quotient
     * of p divided by q and the remainder of p divided by q.</p>
     *
     * <p>More precisely, if we let s,t denote the two components of the
     * returned array, then p = s * q + t and
     * degree(t)&nbsp;&lt;&nbsp;degree(q).
     * The equality is correct to within roundoff error.</p>
     *
     * <p>If the polynomial q is <code>null</code> or the zero polynomial,
     * then throws an ArithmeticException with the message:</p>
     *
     * <p><code>Division by zero in class XPolynomialComplex</code>.</p>
     *
     * <p>Otherwise, if the polynomial p is <code>null</code> or the zero
     * polynomial, returns a pair of zero polynomials.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomialComplex[] divide
        (XPolynomialComplex p, XPolynomialComplex q)
    {
        if (q == null)
            throw new ArithmeticException(divisionMessage);
        
        int e = q.getDegree();
        
        if (e == -1)
            throw new ArithmeticException(divisionMessage);
        
        if (p == null)
            return new XPolynomialComplex[]
                { new XPolynomialComplex(), new XPolynomialComplex() };
        
        int d = p.getDegree();
        
        if (d == -1)
            return new XPolynomialComplex[]
                { new XPolynomialComplex(), new XPolynomialComplex() };
        
        int n = d - e;
        
        XPolynomialComplex quo = new XPolynomialComplex(n);
        XPolynomialComplex rem = new XPolynomialComplex(p);
        
        if (n >= 0) {
            // to perform subtractions rem must not have null coefficients
            // so replace all nulls with zeros
            
            for (int k = 0; k <= d; k++)
                if (rem.coefficients[k] == null)
                    rem.coefficients[k] = new XComplex();
            
            XComplex leading = q.coefficients[e];
            
            for (int i = n; i >= 0; i--) {
                XComplex ratio = XComplex.divide(rem.coefficients[e + i], leading);
                
                quo.coefficients[i] = ratio;
                rem.coefficients[e + i] = null;
                
                for (int j = 0; j < e; j++)
                    rem.coefficients[j + i].subtractProduct(ratio, q.coefficients[j]);
            }
        }
        
        rem.shrinkCapacity();
        return new XPolynomialComplex[] { quo, rem };
    }
    
    
    /////////////////////////////
    // 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);
    }
    
}
