package binaryMatrixFile;

import java.io.*;
import java.util.ArrayList;
import java.util.Vector;

import oneLiners.OneLiners;

/** One-Line reading and writing of 2D matrix of values to/from an ascii file.
 * 
 * For reading and writing matricies into ascii files.
 * For really large things this is slow, use BinaryMatrxFile or BinaryMatrixWriter instead
 * 
 * TODO: Write this as an extrenal library or something more efficient than java string processing!
 */
public class AsciiMatrixFile{
	/**
	 * mustLoad - simply calls load() but catches any IOException thrown. It simple re-throws it as a RuntimeException
	 * Try not to use this for final programs but it saves a lot of error catching in the meantime.
	 * @param fileName The file to read
	 * @param swapDims True to load into arrays as [column][row], false for [row][column]
	 * @returns 2D array of the data in the file
	 * @throws IOException 
	 */	
	public static double [][] mustLoad(String fileName, boolean swapDims){
		try{
			return load(fileName, swapDims);
		}
		catch(IOException err){
			throw new RuntimeException("AsciiMatrixFile.mustLoad() caught the following error from load():\n" + err.toString());
		}		
	}

	
	/**
	 * load - Loads data from a Reader: attempts to interpret it as a fixed size 2D matrix of doubles with cols seperated by \t and rows by \n 
	 * @params reader A reader from which to read the ascii data
	 * @params swapDims True to load into arrays as [column][row], flase for [row][column]
	 * @returns 2D array of the data in the file
	 * @throws IOException 
	 */
	public static double[][] load(Reader reader, boolean swapDims) throws IOException {
		return loadUsingArrayList(reader, swapDims, "resource");
	}
	
	/** Load using ArrayList, written by Jakob and probably much better than my one (loadBlockBuffered) <oliford> */
	private static double[][] loadUsingArrayList(Reader r, boolean swapDims, String sourceIdentifier) throws IOException {
		ArrayList<double[]> rows = new ArrayList<double[]>(1024);
		BufferedReader reader = new BufferedReader(r);
		double[] row = 	getLine(reader);
		if(row == null) throw new RuntimeException("There is no data in '"+sourceIdentifier+"'.");
		int numCols = row.length;
		rows.add(row);		
		while( (row = getLine(reader)) != null ) {			
			if(row.length != numCols)
				throw new RuntimeException("Line " + (rows.size()+1) + " of file '" + sourceIdentifier + "' has " + row.length + " columns but the 1st line had " + numCols);
			
			rows.add(row);
		}
		reader.close();
		
		double[][] ret = new double[rows.size()][];
		for(int i=0;i<rows.size();++i) {
			ret[i] = rows.get(i);
		}
		
		if (swapDims) ret = OneLiners.transpose(ret);
		
		return ret;		
	}
	
	/**  @dreprecated If no problems with load_internal2 found by ~1/8/14, drop this alltogether.  */
	private static double[][] loadBlockBuffered(Reader r, boolean swapDims, String sourceIdentifier) throws IOException {
		double data[][];
		double row[];
		Vector<double[][]> dataChunks = new Vector<double[][]>();
		int rowsPerChunk = 1024;
		int cols;
		int i,j;
					
		BufferedReader reader = new BufferedReader(r);

		//get 1 line of data, get the column count from it and use it to create the data object
		row = getLine(reader);
		if(row == null)throw new RuntimeException("There is no data in '"+sourceIdentifier+"'.");
		cols = row.length;
		data = new double[rowsPerChunk][cols];
		
		i=0;
		// foreach row
		do{
			//check the number of columns is correct
			if(row.length != cols)
				throw new RuntimeException("Line " + (i+1) + " of file '" + sourceIdentifier + "' has " + row.length + " columns but the 1st line had " + cols);
			
			//check we have room
			if(i >= rowsPerChunk){
				//if we've got the the end, add the data to the data chunks vector, allocate a new array and reset the counter
				dataChunks.add(data);
				data = new double[rowsPerChunk][cols];
				i = 0;
			}
			
			//copy the row into the data object
			for(j=0;j<cols;j++)
				data[i][j] = row[j];
			
			//move to the next row
			i++;
			row = getLine(reader);
			
		} //getLine returns null if it has no more lines to get
		while(row != null);
		
		//we're done with the file now
		reader.close();
		
		int chunkCount = dataChunks.size();
		int rowsInLastChunk = i;
		
		//allocate the final array and define a spair one for copying chunks		
		double bigData[][];
		double chunk[][];
		if(swapDims)
			bigData = new double[cols][(chunkCount * rowsPerChunk) + rowsInLastChunk];			
		else
			bigData = new double[(chunkCount * rowsPerChunk) + rowsInLastChunk][cols];					
			
		//copy each chunk (may not have any) into bigData
		for(int c=0;c<chunkCount;c++){
			chunk = (double[][])dataChunks.get(c);
			for(i=0;i<rowsPerChunk;i++)
				for(j=0;j<cols;j++)
					if(swapDims)
						bigData[j][(c*rowsPerChunk)+i] = chunk[i][j];
					else
						bigData[(c*rowsPerChunk)+i][j] = chunk[i][j];
		}
		
		//copy in whatever's in the last chunk 
		for(i=0;i<rowsInLastChunk;i++){
			for(j=0;j<cols;j++)				
				if(swapDims)
					bigData[j][(chunkCount*rowsPerChunk)+i] = data[i][j];
				else
					bigData[(chunkCount*rowsPerChunk)+i][j] = data[i][j];
		}
		
		return bigData;
		
	}
	
	/**
	 * load - Loads a file, attempts to interpret it as a fixed size 2D matrix of doubles with cols seperated by \t and rows by \n 
	 * @params fileName The file to read
	 * @params swapDims True to load into arrays as [column][row], flase for [row][column]
	 * @returns 2D array of the data in the file
	 * @throws IOException 
	 */
	public static double [][] load(String fileName, boolean swapDims) throws IOException{
		//open the file
		FileInputStream file = new FileInputStream(fileName);		
		Reader reader = new InputStreamReader(file);
		
		return loadUsingArrayList(reader, swapDims, fileName);
	}
	  
	/**
	 * getLine - reads a line from 'reader' and attempts to get an array of at least 1 double value
	 * the function also ignores any line starting with //
	 * 
	 *  @params reader - BufferedReader object to read from
	 *  @throws IOException
	 *  @returns The array of doubles
	 */
	private static double[] getLine(BufferedReader reader) throws IOException{
		String line,fields[];
		double vals[];
		int i,n;
		  
		do{
			do{
				line = reader.readLine();				
				if(line == null)break;
				line = line.trim();
			}
			while(line.length() == 0 || (line.charAt(0) == '/' && line.charAt(1) == '/'));
				  
			if(line == null)
				return null;
			
			line = line.replaceAll("\\s+", "\t");
				  
			fields = line.split("\t");
			n = fields.length;
			
			/*if(n==1){
				fields2 = line.split("\\s+");
				if(fields2.length > 1){
					fields = fields2;
					n = fields2.length;
				}
			}*/
			
			vals = new double[n];
				  
			try{
				for(i=0;i<n;i++){
					vals[i] = Double.parseDouble(fields[i]);
				}
				break;
			}catch(NumberFormatException nfe){
				
			}
			
		}while(true);
		
		return vals;
	}
	
	public static void mustWrite(String fileName, double data[]){ mustWrite(fileName,data,null,true); }
	public static void mustWrite(String fileName, double data[], String initialComments){ mustWrite(fileName,new double[][]{ data },initialComments,true); }
	public static void mustWrite(String fileName, double data[], boolean swapDims){ mustWrite(fileName,data,null,swapDims); }
	public static void mustWrite(String fileName, double data[], String initialComments, boolean swapDims){ mustWrite(fileName,new double[][]{ data },initialComments,swapDims); }
	
	public static void mustWrite(String fileName, double data[][]){ mustWrite(fileName,data,null,false); }
	public static void mustWrite(String fileName, double data[][], boolean swapDims){ mustWrite(fileName,data,null,swapDims); }
	public static void mustWrite(String fileName, double data[][], String initialComments){ mustWrite(fileName,data,initialComments,false); }
		
	public static void write(String fileName, double data[][]) throws IOException{ write(fileName,data,null); }
	
	public static void mustWrite(String fileName, double data[][], String initialComments, boolean swapDims){
		try{
			OneLiners.makePath(fileName);
			write(fileName, data, initialComments, swapDims);
		}
		catch(IOException err){
 			throw new RuntimeException("AsciiMatrixFile.mustWrite() caught the following error from write():\n" + err.toString());
		}		
	}
	
	
	
	public static void write(String fileName, double data[][], String initialComments) throws IOException
	{ write(fileName,data,initialComments,false); }
	
	public static void write(String fileName, double data[][], String initialComments, boolean swapDims) throws IOException{
		
		FileOutputStream file = new FileOutputStream(fileName);
		PrintStream ps = new PrintStream(file);
		
		if(initialComments != null)
			ps.println(initialComments);
		
		int i,j;
		int ni = data.length;
		if(ni<1)throw new RuntimeException("Nothing to put into '"+fileName+"', data has 0 rows!");
		int nj = data[0].length;
		
		if(ni > 0 && nj > 0){ //if we have something to write (no dim is size 0)
			if(swapDims){
				for(j=0;j<nj;j++){			
					for(i=0;i<(ni-1);i++){
						ps.print(data[i][j] + "\t");
					}
					
					ps.println(data[i][j]);
				}			
			}
			else{
				for(i=0;i<ni;i++){
					for(j=0;j<(nj-1);j++){			
						ps.print(data[i][j] + "\t");
					}
						ps.println(data[i][j]);
				}
			}
		}
		file.close();
	}
	
	

	
}

