package lnsc;
import java.io.*;
import java.awt.*;
import java.awt.image.*;

/** <P> Set tool functions. </P>
 *
 *  @author Francois Rivest
 *  @version 1.0
 *  @since 1.0
 */
public final class Tools
{

	/** Make class non constructible. */
	private Tools() {}

	/** Generates a set of coordinates that form a grid in the
	 *  <code>[-1,1]x[-1,1]</code> space (including the boundary).
	 *  @param      size                    Number of grid element along both
	 *                                      dimensions.
	 *  @return     A set of <code>size*size</code> bi-dimensional vectors
	 *              equally spaced in the range <code>[-1,1]x[-1,1]</code>.
	 */
	public static final double[][] makeGrid(int size)
	{
		int i, j;
		double[][] ret = new double[(size)*(size)][2];

		for (i=0; i<(size); i++)
		{
			for (j=0; j<(size); j++)
			{
				ret[(size)*i+j][0] = (2.0/(double)(size-1))*(double)i -1.0;
				ret[(size)*i+j][1] = (2.0/(double)(size-1))*(double)j -1.0;
			}
		}

		return ret;
	}

	/** Takes a unit and a count and creates and array of that unit and count
	 * (by copy to the provided units).
	 * @param    newCount    Number of units in the array
	 * @param    newUnit     Units to initialise the array with.
	 * @return    An array of newCount newUnits.
	 */
	public static FunctionalUnit[] createUnitArray(int newCount, FunctionalUnit newUnit)
	{
		FunctionalUnit[] ret = new FunctionalUnit[newCount];
		for (int i=0; i<newCount; i++)
		{
			try {
				ret[i] = (FunctionalUnit) newUnit.clone();
			} catch (CloneNotSupportedException e){
				throw new java.lang.RuntimeException("FunctionalUnit must support Cloneable interface!");
			}
		}
		return ret;
	}

	/** Takes a set of data an generate <i>k</i> train sets and test sets by
	 *  splitting the data into <i>k</i> folds and using each fold as a
	 *  test set for the train set made of the other <i>k-1</i> folds.
	 *  @param      data        A valid {@link DataNames#TRAIN_SET}.
	 *  @param      foldCount   Number of folds into which the data must be split.
	 *  @return     A data set collection containing <i>k</i> pairs of
	 *              {@link DataNames#TRAIN_SET} and {@link DataNames#TEST_SET}.
	 */
	public static final DataSetCollection makeCrossValidationSets(DataSet data,
	                                                              int foldCount)
	{
		int patternCount = ((Integer) data.getData(DataNames.PATTERN_COUNT)).intValue();
		int[] sizes = new int[foldCount];
		int[][] partition = new int[foldCount][];
		int[] indexList = new int[patternCount];
		int i, j, k, val, ind, c=0;
		double[][] in, out, tin, tout;
		double[][] fullIn = (double[][]) data.getData(DataNames.INPUT_PATTERNS);
		double[][] fullOut = (double[][]) data.getData(DataNames.TARGET_PATTERNS);
		DataSet newSet;
		DataSetCollection ret = new DataSetCollection();

		//set up the fold sizes
		for (i=0; i<foldCount; i++)
		{
			if (i < patternCount % foldCount) {
				sizes[i] = (int) Math.ceil((double) patternCount / (double) foldCount);
			} else {
				sizes[i] = (int) Math.floor((double) patternCount / (double) foldCount);
			}
			partition[i] = new int[sizes[i]];
		}

		//generate the partition (i.e. smallSize patterns number of the k sets
		//to do this, generate a list of all numbers
		for (i=0; i<patternCount; i++)
		{
			indexList[i] = i;
		}
		//shuffle them
		for (i=0; i<patternCount-1; i++)
		{
			val = indexList[i];
			ind = (int) (Math.random()*((double)(patternCount-i-1))) + i+1;
			indexList[i] = indexList[ind];
			indexList[ind] = val;
		}
		//take them in order to make the partition
		for (i=0; i<foldCount; i++)
		{
			for (j=0; j<sizes[i]; j++)
			{
				partition[i][j] = indexList[c];
				c++;
			}
		}

		//print out the partition
		for (i=0; i<foldCount; i++)
		{
			for (j=0; j<sizes[i]; j++)
			{
				System.out.print(Integer.toString(partition[i][j]) + ", ");

			}
			System.out.println();
		}


		//make the sets by
		for (i=0; i<foldCount; i++)
		{
			//generate train set (using all subsets but the ith one)
			tin = new double[patternCount][];
			tout = new double[patternCount][];
			c=0;
			for (j=0; j<foldCount; j++)
			{
				if (j != i)
				{
					for (k=0; k<sizes[j]; k++)
					{
						tin[c] = fullIn[partition[j][k]];
						tout[c] = fullOut[partition[j][k]];
						c++;
					}
				}
			}
			//eliminate useless patterns
			in = new double[c][];
			out = new double[c][];
			for (j=0; j<c; j++)
			{
				in[j] = tin[j];
				out[j] = tout[j];
			}
			//save
		    newSet = new DataSet();
			newSet.setData(DataNames.INPUT_PATTERNS, in);
			newSet.setData(DataNames.TARGET_PATTERNS, out);
			newSet.setData(DataNames.PATTERN_COUNT, new Integer(c));
			ret.setData(DataNames.TRAIN_SET, i, newSet);

			//generate test set (using the ith subset)
			in = new double[sizes[i]][];
			out = new double[sizes[i]][];
			for (k=0; k<sizes[i]; k++)
			{
				in[k] = fullIn[partition[i][k]];
				out[k] = fullOut[partition[i][k]];
			}
		    newSet = new DataSet();
			newSet.setData(DataNames.INPUT_PATTERNS, in);
			newSet.setData(DataNames.TARGET_PATTERNS, out);
			newSet.setData(DataNames.PATTERN_COUNT, new Integer(sizes[i]));
			ret.setData(DataNames.TEST_SET, i, newSet);

		}

		//return them
		return ret;
	}

	/** Imports a data set from a file.
	 *  @param      fileName                The name of the file.
	 *  @param      inputCount              Number of input values per row.
	 *  @param      outputCount             Number of output values per row.
	 *  @param      patternCount            Total number of pattern pairs to expect.
	 *  @return     A valid {@link DataNames#TRAIN_SET}.
	 *
	 * The file must contain 1 pattern per row, the first few elements being the
	 * input values and the last few the output values.
	 */
	public static DataSet importDataSet(String fileName, int inputCount, int outputCount, int patternCount) throws IOException
	{
		//Make data set and space for patterns
		double[][] inputs = new double[patternCount][inputCount];
		double[][] outputs = new double[patternCount][outputCount];
		DataSet ret = new DataSet(new String[] {DataNames.INPUT_PATTERNS, DataNames.TARGET_PATTERNS, DataNames.PATTERN_COUNT},
		                          new Object[] {inputs, outputs, new Integer(patternCount)});

		//File reader and tokenizer
		FileReader inFile = new FileReader(fileName);
		StreamTokenizer in = new StreamTokenizer(inFile);
		in.parseNumbers();
		in.eolIsSignificant(true);
		int count = 0, col = 0;

		//Read data
		while (in.nextToken() != StreamTokenizer.TT_EOF)
		{
			switch (in.ttype)
			{
				case StreamTokenizer.TT_EOL:
					if (col == inputCount + outputCount) {
						col = 0;
						count++;
					} else { //col < inputCount + outputCount
						throw new IOException("Line " + Integer.toString(count) + " is too short!");
					}
					break;
			    case StreamTokenizer.TT_NUMBER:
					if (col < inputCount) {
						inputs[count][col] = in.nval;
					} else if (col < inputCount + outputCount) {
						outputs[count][col-inputCount] = in.nval;
					} else {
						throw new IOException("Line " + Integer.toString(count) + " is too long!");
					}
					col++;
					break;
			    case StreamTokenizer.TT_WORD:
					throw new IOException("Line " + Integer.toString(count) + " column " + Integer.toString(col) + " not a number!");
			}
		}
		if (count != patternCount) {
			throw new IOException("Line: " + Integer.toString(count) + " Invalid pattern count!");
		}

		//Return the data set
		return ret;
	}

	/** Saves a data set to a file.
	 *  @param      fileName                The name of the file.
	 *  @param      dataSet                 The data set to  save.
	 */
	public static void saveDataSet(String fileName, DataSet dataSet) throws IOException
	{
		FileOutputStream fileOut = new FileOutputStream(fileName);
		ObjectOutputStream out = new ObjectOutputStream(fileOut);
		out.writeObject(dataSet);
	}

	/** Saves a data set to a file.
	 *  @param      file                    The file descriptor.
	 *  @param      dataSet                 The data set to  save.
	 */
	public static void saveDataSet(File file, DataSet dataSet) throws IOException
	{
		FileOutputStream fileOut = new FileOutputStream(file);
		ObjectOutputStream out = new ObjectOutputStream(fileOut);
		out.writeObject(dataSet);
	}

	/** Loads a data set (saved using {@link #saveDataSet}) from a file.
	 *  @param      fileName                The name of the file.
	 *  @return     The data set read.
	 */
	public static DataSet loadDataSet(String fileName) throws IOException, ClassNotFoundException
	{
		FileInputStream fileIn = new FileInputStream(fileName);
		ObjectInputStream in = new ObjectInputStream(fileIn);
		return (DataSet) in.readObject();
	}

	/** Loads a data set (saved using {@link #saveDataSet}) from a file.
	 *  @param      file                  The file descriptor.
	 *  @return     The data set read.
	 */
	public static DataSet loadDataSet(File file) throws IOException, ClassNotFoundException
	{
		FileInputStream fileIn = new FileInputStream(file);
		ObjectInputStream in = new ObjectInputStream(fileIn);
		return (DataSet) in.readObject();
	}

	/** Copies an object by writing it to a temp file and reading it afterwards.
	 *  This function requires serializability of the object.
	 *  @param      obj                     A serializable object.
	 *  @return     A deep copy of the object.
	 */
	public static Serializable copyObject(Serializable obj)
	{
		Serializable ret;
		try {
			//take temp file name
			String fileName = Double.toString(Math.random());
			//write
			FileOutputStream fileOut = new FileOutputStream(fileName);
			ObjectOutputStream out = new ObjectOutputStream(fileOut);
	    	out.writeObject(obj);
		    out.close();
			fileOut.close();
			//read
			FileInputStream fileIn = new FileInputStream(fileName);
			ObjectInputStream in = new ObjectInputStream(fileIn);
			ret = (Serializable) in.readObject();
			in.close();
		    //clean up temp file
			(new File(fileName)).delete();
		} catch (Exception e) {
			throw new RuntimeException(e.toString());
		}
		return ret;
	}

	/** Tabulates text. Given a string with new line character, a tab character
	 *  is inserted at the beginning of every line.
	 *  @param      text                    Text to be tabulated.
	 *  @param      tabCount                Number of tab to add.
	 *  @return     The tabulated text.
	 */
	public static String tabText(String text, int tabCount)
	{
		int start = 0, stop;
		String newString = new String();
		String tabs = new String();
		for (int i=0; i<tabCount; i++) {tabs += "\t";}
		while ((stop = text.indexOf('\n',start)) != -1)
		{
			//copy the line with the newline char with tabs before
			newString += tabs + text.substring(start,stop+1);
			start = stop+1;
		    if (start >= text.length()) {break;}
		}
		//add tabbed last line if any
		if (!(start >= text.length())) {
			newString += tabs + text.substring(start, text.length());
		}
		return newString;
	}

	/** Takes an images and returns and r,g,b arrays of values between 0 and 256.
	 *  It supports only 256 color Gif.
	 *  @param      image                   The image to transform.
	 *  @return     double[3][width][height]    An array where the first index
	 *                                          indicates red (0), green (1), or
	 *                                          blue (2) component, and the two
	 *                                          others the x,y positon of the
	 *                                          pixel. Values range [0,256).
	 */
	public int[][][] GIFEncoder(Image image) throws AWTException, InterruptedException
	{
		int width = image.getWidth(null);
		int height = image.getHeight(null);

		int values[] = new int[width*height];
		PixelGrabber grabber = new PixelGrabber(image, 0, 0, width, height, values, 0, width);

		if(grabber.grabPixels() != true) {
			throw new AWTException("Grabber returned false: " + grabber.status());
		}

		int r[][] = new int[width][height];
		int g[][] = new int[width][height];
		int b[][] = new int[width][height];

		int index = 0;
		for (int y = 0; y < height; ++y)
		{
			for (int x = 0; x < width; ++x)
			{
				r[x][y] = (int) ((values[index] >> 16) & 0xFF);
				g[x][y] = (int) ((values[index] >> 8) & 0xFF);
				b[x][y] = (int) ((values[index]) & 0xFF);
				index++;
			}
		}

		return new int[][][] {r,g,b};
    }
}