/* ===========================================================

 * JFreeChart : a free chart library for the Java(tm) platform

 * ===========================================================

 *

 * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.

 *

 * Project Info:  http://www.jfree.org/jfreechart/index.html

 *

 * This library is free software; you can redistribute it and/or modify it under the terms

 * of the GNU Lesser General Public License as published by the Free Software Foundation;

 * either version 2.1 of the License, or (at your option) any later version.

 *

 * This library 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 Lesser General Public License for more details.

 *

 * You should have received a copy of the GNU Lesser General Public License along with this

 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,

 * Boston, MA 02111-1307, USA.

 *

 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 

 * in the United States and other countries.]

 *

 * --------------------------

 * DefaultContourDataset.java

 * --------------------------

 * (C) Copyright 2002-2004, by David M. O'Donnell and Contributors.

 *

 * Original Author:  David M. O'Donnell;

 * Contributor(s):   David Gilbert (for Object Refinery Limited);

 *

 * $Id: DefaultContourDataset.java,v 1.1 2004/08/31 15:24:21 mungady Exp $

 *

 * Changes (from 23-Jan-2003)

 * --------------------------

 * 23-Jan-2003 : Added standard header (DG);

 * 20-May-2003 : removed member vars numX and numY, which were never used (TM);

 * 06-May-2004 : Now extends AbstractXYZDataset (DG);

 * 15-Jul-2004 : Switched getX() with getXValue(), getY() with getYValue() and getZ() 

 *               with getZValue() methods (DG);

 * 

 */



package org.jfree.data.contour;



import java.util.Arrays;

import java.util.Date;

import java.util.Vector;



import org.jfree.data.Range;

import org.jfree.data.xy.AbstractXYZDataset;

import org.jfree.data.xy.XYDataset;



/**

 * A convenience class that provides a default implementation of the {@link ContourDataset}

 * interface.

 *

 * @author David M. O'Donnell

 */

public class DefaultContourDataset extends AbstractXYZDataset implements ContourDataset {



    /** The series name (this dataset supports only one series). */

    protected String seriesName = null;



    /** Storage for the x values. */

    protected Number[] xValues = null;



    /** Storage for the y values. */

    protected Number[] yValues = null;



    /** Storage for the z values. */

    protected Number[] zValues = null;



    /** The index for the start of each column in the data. */

    protected int[] xIndex = null;



    /** Flags that track whether x, y and z are dates. */

    boolean[] dateAxis = new boolean[3];



    /**

     * Creates a new dataset, initially empty.

     */

    public DefaultContourDataset() {

        super();

    }



    /**

     * Constructs a new dataset with the given data.

     *

     * @param seriesName  the series name.

     * @param xData  the x values.

     * @param yData  the y values.

     * @param zData  the z values.

     */

    public DefaultContourDataset(final String seriesName,

                                 final Object[] xData,

                                 final Object[] yData,

                                 final Object[] zData) {



        this.seriesName = seriesName;

        initialize(xData, yData, zData);

    }



    /**

     * Initialises the dataset.

     * 

     * @param xData  the x values.

     * @param yData  the y values.

     * @param zData  the z values.

     */

    public void initialize(final Object[] xData,

                           final Object[] yData,

                           final Object[] zData) {



        this.xValues = new Double[xData.length];

        this.yValues = new Double[yData.length];

        this.zValues = new Double[zData.length];



       // We organise the data with the following assumption:

       // 1) the data are sorted by x then y

       // 2) that the data will be represented by a rectangle formed by

       //    using x[i+1], x, y[j+1], and y.

       // 3) we march along the y-axis at the same value of x until a new value x is

       //    found at which point we will flag the index where x[i+1]<>x[i]



        final Vector tmpVector = new Vector(); //create a temporary vector

        double x = 1.123452e31; // set x to some arbitary value (used below)

        for (int k = 0; k < this.xValues.length; k++) {

            if (xData[k] != null) {

                final Number xNumber;

                if (xData[k] instanceof Number) {

                    xNumber = (Number) xData[k];

                }

                else if (xData[k] instanceof Date) {

                    this.dateAxis[0] = true;

                    final Date xDate = (Date) xData[k];

                    xNumber = new Long(xDate.getTime()); //store data as Long

                }

                else {

                    xNumber = new Integer(0);

                }

                this.xValues[k] = new Double(xNumber.doubleValue()); // store Number as Double



                // check if starting new column

                if (x != this.xValues[k].doubleValue()) {

                    tmpVector.add(new Integer(k)); //store index where new column starts

                    x = this.xValues[k].doubleValue(); // set x to most recent value

                }

            }

        }



        final Object[] inttmp = tmpVector.toArray();

        this.xIndex = new int[inttmp.length];  // create array xIndex to hold new column indices



        for (int i = 0; i < inttmp.length; i++) {

            this.xIndex[i] = ((Integer) inttmp[i]).intValue();

        }

        for (int k = 0; k < this.yValues.length; k++) { // store y and z axes as Doubles

            this.yValues[k] = (Double) yData[k];

            if (zData[k] != null) {

                this.zValues[k] = (Double) zData[k];

            }

        }

    }



    /**

     * Creates an object array from an array of doubles.

     *

     * @param data  the data.

     *

     * @return An array of <code>Double</code> objects.

     */

    public static Object[][] formObjectArray(final double[][] data) {

        final Object[][] object = new Double[data.length][data[0].length];



        for (int i = 0; i < object.length; i++) {

            for (int j = 0; j < object[i].length; j++) {

                object[i][j] = new Double(data[i][j]);

            }

        }

        return object;

    }



    /**

     * Creates an object array from an array of doubles.

     *

     * @param data  the data.

     *

     * @return An array of <code>Double</code> objects.

     */

    public static Object[] formObjectArray(final double[] data) {



        final Object[] object = new Double[data.length];

        for (int i = 0; i < object.length; i++) {

            object[i] = new Double(data[i]);

        }

        return object;

    }



    /**

     * Returns the number of items in the specified series.

     * <P>

     * Method provided to satisfy the {@link XYDataset} interface implementation.

     *

     * @param series  must be zero, as this dataset only supports one series.

     *

     * @return the item count.

     */

    public int getItemCount(final int series) {

        if (series > 0) {

            System.out.println("Only one series for contour");

        }

        return this.zValues.length;

    }



    /**

     * Returns the maximum z-value.

     *

     * @return The maximum z-value.

     */

    public double getMaxZValue() {

        double zMax = -1.e20;

        for (int k = 0; k < this.zValues.length; k++) {

            if (this.zValues[k] != null) {

                zMax = Math.max(zMax, this.zValues[k].doubleValue());

            }

        }

        return zMax;

    }



    /**

     * Returns the minimum z-value.

     *

     * @return The minimum z-value.

     */

    public double getMinZValue() {



        double zMin = 1.e20;

        for (int k = 0; k < this.zValues.length; k++) {

            if (this.zValues[k] != null) {

                zMin = Math.min(zMin, this.zValues[k].doubleValue());

            }

        }

        return zMin;

    }



    /**

     * Returns the maximum z-value within visible region of plot.

     *

     * @param x  the x range.

     * @param y  the y range.

     *

     * @return The z range.

     */

    public Range getZValueRange(final Range x, final Range y) {



        final double minX = x.getLowerBound();

        final double minY = y.getLowerBound();

        final double maxX = x.getUpperBound();

        final double maxY = y.getUpperBound();



        double zMin = 1.e20;

        double zMax = -1.e20;

        for (int k = 0; k < this.zValues.length; k++) {

            if (this.xValues[k].doubleValue() >= minX

                && this.xValues[k].doubleValue() <= maxX

                && this.yValues[k].doubleValue() >= minY

                && this.yValues[k].doubleValue() <= maxY) {

                if (this.zValues[k] != null) {

                    zMin = Math.min(zMin, this.zValues[k].doubleValue());

                    zMax = Math.max(zMax, this.zValues[k].doubleValue());

                }

            }

        }



        return new Range(zMin, zMax);

    }



    /**

     * Returns the minimum z-value.

     *

     * @param minX  the minimum x value.

     * @param minY  the minimum y value.

     * @param maxX  the maximum x value.

     * @param maxY  the maximum y value.

     *

     * @return the minimum z-value.

     */

    public double getMinZValue(final double minX, 

                               final double minY, 

                               final double maxX, 

                               final double maxY) {



        double zMin = 1.e20;

        for (int k = 0; k < this.zValues.length; k++) {

            if (this.zValues[k] != null) {

                zMin = Math.min(zMin, this.zValues[k].doubleValue());

            }

        }

        return zMin;



    }



    /**

     * Returns the number of series.

     * <P>

     * Required by XYDataset interface (this will always return 1)

     *

     * @return 1.

     */

    public int getSeriesCount() {

        return 1;

    }



    /**

     * Returns the name of the specified series.

     *

     * Method provided to satisfy the XYDataset interface implementation

     *

     * @param series must be zero.

     *

     * @return the series name.

     */

    public String getSeriesName(final int series) {

        if (series > 0) {

            System.out.println("Only one series for contour");

        }

        return this.seriesName;

    }



    /**

     * Returns the index of the xvalues.

     *

     * @return The x values.

     */

    public int[] getXIndices() {

        return this.xIndex;

    }



    /**

     * Returns the x values.

     *

     * @return The x values.

     */

    public Number[] getXValues() {

        return this.xValues;

    }



    /**

     * Returns the x value for the specified series and index (zero-based indices).

     * Required by the XYDataset

     *

     * @param series  must be zero;

     * @param item  the item index (zero-based).

     *

     * @return The x value.

     */

    public Number getX(int series, int item) {

        if (series > 0) {

            System.out.println("Only one series for contour");

        }

        return this.xValues[item];

    }



    /**

     * Returns an x value.

     *

     * @param item  the item index (zero-based).

     *

     * @return The X value.

     */

    public Number getXValue(final int item) {

        return this.xValues[item];

    }



    /**

     * Returns a Number array containing all y values.

     *

     * @return The Y values.

     */

    public Number[] getYValues() {

        return this.yValues;

    }



    /**

     * Returns the y value for the specified series and index (zero-based indices).

     * Required by the XYDataset

     *

     * @param series  the series index (must be zero for this dataset).

     * @param item  the item index (zero-based).

     *

     * @return The Y value.

     */

    public Number getY(int series, int item) {

        if (series > 0) {

            System.out.println("Only one series for contour");

        }

        return this.yValues[item];

    }



    /**

     * Returns a Number array containing all z values.

     *

     * @return The Z values.

     */

    public Number[] getZValues() {

        return this.zValues;

    }



    /**

     * Returns the z value for the specified series and index (zero-based indices).

     * Required by the XYDataset

     *

     * @param series  the series index (must be zero for this dataset).

     * @param item  the item index (zero-based).

     *

     * @return The Z value.

     */

    public Number getZ(int series, int item) {

        if (series > 0) {

            System.out.println("Only one series for contour");

        }

        return this.zValues[item];

    }



    /**

     * Returns an int array contain the index into the x values.

     *

     * @return The X values.

     */

    public int[] indexX() {

        final int[] index = new int[this.xValues.length];

        for (int k = 0; k < index.length; k++) {

            index[k] = indexX(k);

        }

        return index;

    }



    /**

     * Given index k, returns the column index containing k.

     *

     * @param k index of interest.

     *

     * @return The column index.

     */

    public int indexX(final int k) {

        final int i = Arrays.binarySearch(this.xIndex, k);

        if (i >= 0) {

            return i;

        } 

        else {

            return -1 * i - 2;

        }

    }





    /**

     * Given index k, return the row index containing k.

     *

     * @param k index of interest.

     *

     * @return The row index.

     */

    public int indexY(final int k) { // this may be obsolete (not used anywhere)

        return (k / this.xValues.length);

    }



    /**

     * Given column and row indices, returns the k index.

     *

     * @param i index of along x-axis.

     * @param j index of along y-axis.

     *

     * @return The Z index.

     */

    public int indexZ(final int i, final int j) {

        return this.xValues.length * j + i;

    }



    /**

     * Returns true if axis are dates.

     * 

     * @param axisNumber The axis where 0-x, 1-y, and 2-z.

     * 

     * @return A boolean.

     */

    public boolean isDateAxis(final int axisNumber) {

        if (axisNumber < 0 || axisNumber > 2) {

            return false; // bad axisNumber

        }

        return this.dateAxis[axisNumber];

    }



    /**

     * Sets the names of the series in the data source.

     *

     * @param seriesNames The names of the series in the data source.

     */

    public void setSeriesNames(final String[] seriesNames) {

        if (seriesNames.length > 1) {

            System.out.println("Contours only support one series");

        }

        this.seriesName = seriesNames[0];

        fireDatasetChanged();

    }



}

