
package jp.riken.brain.ni.samuraigraph.data;

import java.io.IOException;
import java.io.Writer;

import jp.riken.brain.ni.samuraigraph.base.SGData;
import jp.riken.brain.ni.samuraigraph.base.SGUtility;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;


/**
 * Scalar type XY data.
 * This object has of x and y values.
 * If given, this object can keep error bar values and text strings.
 * 
 */
public class SGSXYData extends SGData
	implements SGISXYTypeData, Cloneable
{

	/**
	 * Array of x values.
	 */
	protected double[] mXValueArray = null;


	/**
	 * Array of y values.
	 */
	protected double[] mYValueArray = null;


	/**
	 * Array of lower error values.
	 */
	protected double[] mLowerErrorArray = null;


	/**
	 * Array of upper error values.
	 */
	protected double[] mUpperErrorArray = null;


	/**
	 * Array of strings.
	 */
	protected String[] mStringArray = null;


	/**
	 * Construct an data object.
	 *
	 */
	public SGSXYData()
	{
		super();
	}


	/**
	 * Construct an data object.
	 * @param titleArray  - colum titles 
	 * @param xValueArray - x values
	 * @param yValueArray - y values
	 */
	public SGSXYData(
		final String[] titleArray,		
		final double[] xValueArray,
		final double[] yValueArray )
	{
		this( titleArray, xValueArray, yValueArray, null, null, null );
	}


	/**
	 * Construct an data object with error bars and strings.
	 * @param titleArray  - colum titles 
	 * @param xValueArray - x values
	 * @param yValueArray - y values
	 * @param lowerErrorArray - lower error values
	 * @param upperErrorArray - upper error values
	 */
	public SGSXYData(
		final String[] titleArray,
		final double[] xValueArray,
		final double[] yValueArray,
		final double[] lowerErrorArray,
		final double[] upperErrorArray )
	{
		this( titleArray, xValueArray, yValueArray, lowerErrorArray, upperErrorArray, null );
	}


	/**
	 * Construct an data object with a string array.
	 * @param titleArray  - colum titles 
	 * @param xValueArray - x values
	 * @param yValueArray - y values
	 * @param stringArray - strings
	 */
	public SGSXYData(
		final String[] titleArray,
		final double[] xValueArray,
		final double[] yValueArray,
		final String[] stringArray )
	{
		this( titleArray, xValueArray, yValueArray, null, null, stringArray );
	}


	/**
	 * Construct an data object with error bars and strings.
	 * @param titleArray  - colum titles 
	 * @param xValueArray - x values
	 * @param yValueArray - y values
	 * @param lowerErrorArray - lower error values
	 * @param upperErrorArray - upper error values
	 * @param stringArray - strings
	 */
	public SGSXYData(
		final String[] titleArray,
		final double[] xValueArray,
		final double[] yValueArray,
		final double[] lowerErrorArray,
		final double[] upperErrorArray,
		final String[] stringArray )
	{
		super();

		this.setTitle( titleArray );
		
		if( xValueArray==null || yValueArray==null )
		{
			throw new IllegalArgumentException("xValueArray==null || yValueArray==null");
		}

		if( xValueArray.length!=yValueArray.length )
		{
			throw new IllegalArgumentException("xValueArray.length!=yValueArray.length");
		}

		// set x and y values
		this.mXValueArray = SGUtility.copyDoubleArray( xValueArray );
		this.mYValueArray = SGUtility.copyDoubleArray( yValueArray );

		if( lowerErrorArray!=null && upperErrorArray!=null )
		{
			if( lowerErrorArray.length!=xValueArray.length )
			{
				throw new IllegalArgumentException("lowerErrorArray.length!=xValueArray.length");
			}

			if( upperErrorArray.length!=xValueArray.length )
			{
				throw new IllegalArgumentException("upperErrorArray.length!=xValueArray.length");
			}

			// set error values
			this.mLowerErrorArray = SGUtility.copyDoubleArray( lowerErrorArray );
			this.mUpperErrorArray = SGUtility.copyDoubleArray( upperErrorArray );
		}

		if( stringArray!=null )
		{
			if( stringArray.length!=xValueArray.length )
			{
				throw new IllegalArgumentException("stringArray.length!=xValueArray.length");
			}

			// set strings
			this.mStringArray = SGUtility.copyStringArray( stringArray );
		}

	}



	/**
	 * Construct an data object with given data.
	 * @param data - data set to this object
	 */
	public SGSXYData( final SGData data )
	{
		super();
		this.setData(data);
	}


	/**
	 * Set data.
	 * @param data - data set to this object
	 */
	public boolean setData( final SGData data )
	{
		if( !(data instanceof SGSXYData) )
		{
			throw new IllegalArgumentException("!(data instanceof SGSXYData)");
		}

		SGSXYData data_ = (SGSXYData)data;
		this.setTitle(data_.getTitleArray());
		this.mXValueArray = data_.getXValueArray();
		this.mYValueArray = data_.getYValueArray();
		this.mLowerErrorArray = data_.getLowerErrorValueArray();
		this.mUpperErrorArray = data_.getUpperErrorValueArray();
		this.mStringArray = data_.getStringArray();

		return true;
	}


	/**
	 * Returns a number of data points.
	 * @return - a number of data points
	 */
	public int getPointsNumber()
	{
		if( this.mXValueArray==null )
		{
			return -1;
		}
		return this.mXValueArray.length;
	}



	/**
	 * Returns the name of data type.
	 * @return
	 */
	public String getDataType()
	{
		return SGDataTypeConstants.SXY_DATA;
	}


	/**
	 * Returns the minimum x-value.
	 * @return the minimum x-value
	 */
	public double getMinValueX()
	{
		double min = Double.MAX_VALUE;
		final double[] array = this.mXValueArray;
		for( int ii=0; ii<array.length; ii++ )
		{
			final double x = array[ii];
			if( x < min )
			{
				min = x;
			}
		}
		return min;
	}


	/**
	 * Returns the maximum x-value.
	 * @return the maximum x-value
	 */
	public double getMaxValueX()
	{
		double max = - Double.MAX_VALUE;
		final double[] array = this.mXValueArray;
		for( int ii=0; ii<array.length; ii++ )
		{
			final double x = array[ii];
			if( x > max )
			{
				max = x;
			}
		}
		return max;
	}


	/**
	 * Returns the minimum y-value.
	 * If this data has error values, they are taken into account. 
	 * @return the minimum y-value 
	 */
	public double getMinValueY()
	{
		double min = Double.MAX_VALUE;
		final double[] array = this.mYValueArray;
		if( this.mLowerErrorArray!=null )
		{
			final double[] lArray = this.mLowerErrorArray;
			for( int ii=0; ii<array.length; ii++ )
			{
				final double y = array[ii] + lArray[ii];
				if( y < min )
				{
					min = y;
				}
			}
		}
		else
		{
			for( int ii=0; ii<array.length; ii++ )
			{
				final double y = array[ii];
				if( y < min )
				{
					min = y;
				}
			}
		}

		return min;
	}


	/**
	 * Returns the maximum y-value.
	 * If this data has error values, they are taken into account. 
	 * @return the maximum y-value 
	 */
	public double getMaxValueY()
	{
		double max = - Double.MAX_VALUE;
		final double[] array = this.mYValueArray;
		if( this.mUpperErrorArray!=null )
		{
			final double[] uArray = this.mUpperErrorArray;
			for( int ii=0; ii<array.length; ii++ )
			{
				double y = array[ii] + uArray[ii];
				if( y > max )
				{
					max = y;
				}
			}
		}
		else
		{
			for( int ii=0; ii<array.length; ii++ )
			{
				double y = array[ii];
				if( y > max )
				{
					max = y;
				}
			}
		}

		return max;
	}



	/**
	 * Returns the x value with the given index.
	 * @param n - index
	 * @return x-value with the given index
	 */
	public Double getXValue( final int n )
	{
		return new Double( this.mXValueArray[n] );
	}


	/**
	 * Returns the y value with the given index.
	 * @param n - index
	 * @return y-value with the given index
	 */
	public Double getYValue( final int n )
	{
		return new Double( this.mYValueArray[n] );
	}


	/**
	 * Returns the lower error value with the given index if it exists.
	 * @param n - index
	 * @return lower error value with the given index
	 */
	public Double getLowerErrorValue( final int n )
	{
		if( this.isErrorValueHolding() )
		{
			return new Double( this.mLowerErrorArray[n] );
		}
		return null;
	}


	/**
	 * Returns the upper error value with the given index if it exists.
	 * @param n - index
	 * @return upper error value with the given index
	 */
	public Double getUpperErrorValue( final int n )
	{
		if( this.isErrorValueHolding() )
		{
			return new Double( this.mUpperErrorArray[n] );
		}
		return null;
	}



	/**
	 * Returns the string with the given index if it exists.
	 * @param n - index
	 * @return a string with the given index
	 */
	public String getString( final int n )
	{
		if( this.isStringArrayHolding() )
		{
			return this.mStringArray[n];
		}
		return null;
	}


	/**
	 * Returns a copy of x-value array.
	 * @return x-value array
	 */
	public double[] getXValueArray()
	{
		return SGUtility.copyDoubleArray( this.mXValueArray );
	}


	/**
	 * Returns a copy of y-value array.
	 * @return y-value array
	 */
	public double[] getYValueArray()
	{
		return SGUtility.copyDoubleArray( this.mYValueArray );
	}


	/**
	 * Returns a copy of lower-error-value array if they exist.
	 * @return lower-error-value array
	 */
	public double[] getLowerErrorValueArray()
	{
		if( this.isErrorValueHolding() )
		{
			return SGUtility.copyDoubleArray( this.mLowerErrorArray );
		}
		return null;
	}


	/**
	 * Returns a copy of upper-error-value array if they exist.
	 * @return upper-error-value array
	 */
	public double[] getUpperErrorValueArray()
	{
		if( this.isErrorValueHolding() )
		{
			return SGUtility.copyDoubleArray( this.mUpperErrorArray );
		}
		return null;
	}


	/**
	 * Returns a copy of string array if they exist.
	 * @return string array
	 */
	public String[] getStringArray()
	{
		if( this.isStringArrayHolding() )
		{
			return SGUtility.copyStringArray( this.mStringArray );
		}
		return null;
	}


	/**
	 * Returns whether this data has error values.
	 * @return whether this data has error values
	 */
	public boolean isErrorValueHolding()
	{
		return (this.mLowerErrorArray!=null);
	}



	/**
	 * Returns whether this data has strings.
	 * @return whether this data has strings
	 */
	public boolean isStringArrayHolding()
	{
		return (this.mStringArray!=null);
	}


	/**
	 * Clone this data object.
	 * @return copy of this data object
	 */
	public Object clone()
	{
		try
		{
			return super.clone();
		}
		catch( CloneNotSupportedException ex )
		{
			// this shouldn't happen, since we are Cloneable
			throw new InternalError();
		}
	}


	/**
	 * Calling the clone method.
	 * @return copy of this data object
	 */
	public Object copy()
	{
		return this.clone();
	}



	/**
	 * 
	 */
	public void dispose()
	{
		super.dispose();
		this.mXValueArray = null;
		this.mYValueArray = null;
		this.mLowerErrorArray = null;
		this.mUpperErrorArray = null;
		this.mStringArray = null;
	}

	/*
	 * write data to file
	 * @see jp.riken.brain.ni.samuraigraph.base.SGData#writeData(java.io.FileWriter)
	 */
	protected boolean writeData( Writer fw ) throws IOException
	{
		if(this.mXValueArray == null || this.mXValueArray.length == 0)
			return false;
		for(int ii=0; ii<this.mXValueArray.length; ii++){
			String line = this.mXValueArray[ii] + "," + this.mYValueArray[ii];
			if(this.mUpperErrorArray != null){
				line += "," + this.mUpperErrorArray[ii] + "," + this.mLowerErrorArray[ii];
			}
			if(this.mStringArray != null){
				line += "," + SGUtilityText.getCSVString( this.mStringArray[ii] );
			}
			line += "\n";
			fw.write(line);
		}
		return true;
	}
}

