package oneLinersForAlgoTests;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
import java.util.Random;

import javax.management.RuntimeErrorException;

/** One-Line reading and writing of 2D matrix of values to/from a binary file.
 * 
 * Format is fixed and very simple:
 * 
 * 32-bit unsigned integer M - number of rows (might be -1, in which case work it out from file size)
 * 32-bit unsigned integer N - number of columns.
 * 64-bit IEEE floating point (double) data:
 *      row0-col0, row0-col1, ... row0-col(N-1), row1-col0, row1-col1, ... row(M-1)-col(N-1)
 */
public class algoBinaryMatrixFile {
	/**
	 * 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.
	 * @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 [][] mustLoad(String fileName, boolean swapDims){
		try{
			return load(fileName, swapDims);
		}
		catch(IOException err){
			throw new RuntimeException("BinaryMatrixFile.mustLoad() caught the following error from load():\n" + err.toString());
		}		
	}
	
	/*
	 * 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);		
		DataInputStream ds = new DataInputStream(file);
		
		int i,j,ni,nj;
		double data[][];
		
		ni = ds.readInt();
		nj = ds.readInt();
		
		if(ni < 0){ //-ve ni usually means it didn't finish writing, we can maybe work out the number of rows by the file size
			long size = (new File(fileName)).length();
			size -= 8; //don't count the ni and nj
			
			double dni = size / (nj * 8);
			ni = (int)dni;
			if( dni != (double)ni)
				throw new RuntimeException("'"+fileName+"' doesn't have a row count. Working it out from the size and given number of colums ("+nj+") gives non-integer number of rows "+dni+".");
		}
		              
		if(swapDims){
			i=ni;
			ni = nj;
			nj = i;
			
			data = new double[ni][nj];
			
			for(j=0;j<nj;j++)			
				for(i=0;i<ni;i++)
					data[i][j] = ds.readDouble();
		}else{
			
			data = new double[ni][nj];
			
			for(i=0;i<ni;i++)
				for(j=0;j<nj;j++)	
					data[i][j] = ds.readDouble();
		}
		return data;
	}

	public static void mustWrite(String fileName, double data[]){ mustWrite(fileName,new double[][]{ data },true); }
	

	public static void mustWrite(String fileName, double data[][], boolean swapDims){
		try{
		    algoOneLiners.makePath(fileName);
			write(fileName, data, swapDims);
		}
		catch(IOException err){
			throw new RuntimeException("AsciiMatrixFile.mustWrite() caught the following error from write():\n" + err.toString());
		}		
	}
	
	public static void writeStd(String fileName, double data[][], boolean swapDims) throws IOException{
		
		FileOutputStream file = new FileOutputStream(fileName);
		DataOutputStream ds = new DataOutputStream(file);
		
		int i,j;
		int ni = data.length;
		//if(ni<1)throw new RuntimeException("Nothing to put into '"+fileName+"', data has 0 rows!");
		int nj = (ni > 0) ? data[0].length : 0;
		
		
		
		if(swapDims){
			ds.writeInt(nj);
			ds.writeInt(ni);
			
			for(j=0;j<nj;j++)		
				for(i=0;i<ni;i++)
					ds.writeDouble(data[i][j]);
		}else{
			ds.writeInt(ni);
			ds.writeInt(nj);
			
			for(i=0;i<ni;i++)
				for(j=0;j<nj;j++)			
					ds.writeDouble(data[i][j]);
		}
		
		//really really write it please
		ds.flush();
		file.flush();
		file.close();
	}
	
	public static void write(String fileName, double data[][], boolean swapDims) throws IOException{
		
		FileOutputStream file = new FileOutputStream(fileName);
		FileChannel out = file.getChannel();
		
		int i,j;
		int ni = data.length;
		//if(ni<1)throw new RuntimeException("Nothing to put into '"+fileName+"', data has 0 rows!");
		int nj = (ni > 0) ? data[0].length : 0;
				
		ByteBuffer bBuf = ByteBuffer.allocate(8);
		IntBuffer iBuf = bBuf.asIntBuffer();
		if(swapDims){
			iBuf.put(nj);
			iBuf.put(ni);
		}else{
			iBuf.put(ni);
			iBuf.put(nj);
		}
		out.write(bBuf);

		
		if(swapDims){
			bBuf = ByteBuffer.allocate(ni * 8);
			DoubleBuffer dBuf = bBuf.asDoubleBuffer();
			
			for(j=0;j<nj;j++){
				dBuf.rewind();
				bBuf.rewind();
				for(i=0;i<ni;i++)
					dBuf.put(data[i][j]);
				
				out.write(bBuf);
			}
			
		}else{
			bBuf = ByteBuffer.allocate(nj * 8);
			DoubleBuffer dBuf = bBuf.asDoubleBuffer();
			
			for(i=0;i<ni;i++){
				dBuf.rewind();
				bBuf.rewind();
				dBuf.put(data[i]);

				out.write(bBuf);
			}
		}
			
		out.close();
		file.close();
		
	}
	
	
	public static void main(String[] args) {
	
		int nx = 2000;
		int ny = 1000;
		Random randGen = new Random();
		double d[][] = new double[ny][nx];
		
		System.out.print("Randomise ");
		for(int y=0; y < ny; y++){
			for(int x=0; x < nx; x++)
				d[y][x] = randGen.nextDouble();
			System.out.print(".");
		}
		System.out.println("done.");
		
		try {
			long t0 = System.currentTimeMillis();
			algoBinaryMatrixFile.writeStd("/tmp/std.bin", d, false);
			System.out.println("Standard: " + (System.currentTimeMillis() - t0) + " ms");
			
			t0 = System.currentTimeMillis();
			algoBinaryMatrixFile.write("/tmp/nio.bin", d, false);
			System.out.println("NIO: " + (System.currentTimeMillis() - t0) + " ms");
			
			t0 = System.currentTimeMillis();
			algoBinaryMatrixFile.writeStd("/tmp/std-transposed.bin", d, true);
			System.out.println("Standard transposed: " + (System.currentTimeMillis() - t0) + " ms");
			
			t0 = System.currentTimeMillis();
			algoBinaryMatrixFile.write("/tmp/nio-transposed.bin", d, true);
			System.out.println("NIO transposed: " + (System.currentTimeMillis() - t0) + " ms");
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}
