// Binary Matrix File Slicer

const char usageText[]="Usage:\n\
	-i/--inFile inFile	Input file.\n\
	-o/--outFile outFile	Output file.\n\
	[--nRows nRows] 	Number of rows to put in output file.\n\
	[--nCols nCols] 	Number of cols to put in output file.\n\
	[--rowStep rowStep] 	Interval of input rows to put in output file.\n\
	[--colStep colStep] 	Interval of input cols to put in output file\n\
	[--row0 row0]		Starting row in input file.\n\
	[--row1 row1]		Ending row in input file.\n\
	[--col0 col0]		Starting col in input file.\n\
	[--col1 col1]		Endingcol in input file.\n\
\n";

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include "binaryMatrixFile.h"

struct timeval t0;

void tic(){ gettimeofday(&t0, NULL); }
double toc(){
        struct timeval t1;
        gettimeofday(&t1, NULL);
        return (double)(t1.tv_sec - t0.tv_sec) + (double)(t1.tv_usec - t0.tv_usec)/1000000.0;
}

void calcSlice(int64_t *step, int64_t *nOut, int64_t nInRange);

int main(int argc, char *argv[]) {
	char *inFileName = NULL, *outFileName = NULL;
	int64_t nRowsOut = -1, nColsOut = -1;
	int64_t rowStep = -1,  colStep = -1;
	int64_t row0 = -1, row1 = -1;
	int64_t col0 = -1, col1 = -1;
	int opt;

	do {
		int option_index = 0;
		static struct option long_options[] = {
			{"nRows", 1, 0, 0},
			{"nCols", 1, 0, 1},
			{"rowStep", 1, 0, 2},
			{"colStep", 1, 0, 3},
			{"row0", 1, 0, 4},
			{"row1", 1, 0, 5},
			{"col0", 1, 0, 6},
			{"col1", 1, 0, 7},
			{"inFile", 1, 0, 'i'},
			{"outFile", 1, 0, 'o'},
			{0, 0, 0, 0}
		};

		opt = getopt_long(argc, argv, "i:o:", long_options, &option_index);
		switch (opt) {
			case 0: nRowsOut = atoll(optarg); break;
			case 1: nColsOut = atoll(optarg); break;
			case 2: rowStep = atoll(optarg); break;
			case 3: colStep = atoll(optarg); break;
			case 4: row0 = atoll(optarg); break;
			case 5: row1 = atoll(optarg); break;
			case 6: col0 = atoll(optarg); break;
			case 7: col1 = atoll(optarg); break;
			case 'i': inFileName = optarg; break;
			case 'o': outFileName = optarg; break;
			case -1:
			case '?':
				break;
			default: fprintf(stderr, "%s\n", usageText); exit(EXIT_FAILURE);
		}
	}while(opt != -1 && opt != '?');

	if(inFileName == NULL || outFileName == NULL){ fprintf(stderr, "%s\n", usageText); exit(EXIT_FAILURE); }

	printf("optind=%i argc=%i\n", optind, argc);
	printf("inFileName=%s, outFileName=%s\n", inFileName, outFileName);
	printf("nRowsOut=%lli, nColsOut=%lli, colStep=%lli, rowStep=%lli\n", nRowsOut, nColsOut, colStep, rowStep);
	printf("row0=%lli, row1=%lli, col0=%lli, col1=%lli\n\n", row0, row1, col0, col1);

	int64_t nRowsIn = -1, nColsIn = -1;
	FILE *fin, *fout;

	fin = fopen(inFileName, "r");
	printf("fin=%p\n",fin);
	if(!fin){ fprintf(stderr, "Couldn't open input file '%s'\n", inFileName); perror(NULL); exit(EXIT_FAILURE); }

	int32_t nRowsInRead, nColsInRead;
	freadRev(&nRowsInRead,4,1,fin); nRowsIn = (int64_t)nRowsInRead;
	freadRev(&nColsInRead,4,1,fin); nColsIn = (int64_t)nColsInRead;

	if(nRowsIn <= 0){
		printf("Unknown or zero nRowsIn, determining from file size: ");
		struct stat sb;
	
		if (stat(inFileName, &sb) == -1) { printf("stat(%s) failed\n", inFileName); exit(EXIT_FAILURE); }

		int64_t fileSize = (int64_t) sb.st_size;

		printf("size = %lld bytes, ", fileSize);

		nRowsIn = ((fileSize - 8) / nColsIn) / 8;

		printf("nRows= %lld\n", nRowsIn);
	}

	printf("inFile: nRowsIn=%lli, nColsIn=%lli\n", nRowsIn, nColsIn);

	if(row0 < 0) row0 = 0;
	if(col0 < 0) col0 = 0;
	if(row1 < 0) row1 = nRowsIn - 1;
	if(col1 < 0) col1 = nColsIn - 1;

	if(row1 >= nRowsIn){ printf("WARNING: Finishing row (%lli) is greater than last row (%lli), truncating\n", row1, (nRowsIn-1)); row1 = nRowsIn - 1; }
	if(col1 >= nColsIn){ printf("WARNING: Finishing col (%lli) is greater than last col (%lli), truncating\n", col1, (nColsIn-1)); col1 = nColsIn - 1; }

	int64_t nRowsInRange = row1 - row0 + 1;
	int64_t nColsInRange = col1 - col0 + 1;

	if(nRowsInRange <= 0 || (nRowsOut >= 0 && nRowsInRange < nRowsOut)){
		fprintf(stderr, "ERROR: Row range is empty, or less than requested nRows: row0=%lli, row1=%lli, n=%lli, nReq=%lli\n", row0, row1, nRowsInRange, nRowsOut);
		fprintf(stderr, "%s\n", usageText);
		exit(EXIT_FAILURE);
	}

	if(nColsInRange <= 0 || (nColsOut >= 0 && nColsInRange < nColsOut)){
		fprintf(stderr, "ERROR: Col range is empty, or less than requested nCols: col0=%lli, col1=%lli, n=%lli, nReq=%lli\n", col0, col1, nColsInRange, nColsOut);
		fprintf(stderr, "%s\n", usageText);
		exit(EXIT_FAILURE);
	}

	printf("nRowsInRange = %lli, nColsInRange = %lli\n", nRowsInRange, nColsInRange);

	calcSlice(&rowStep, &nRowsOut, nRowsInRange);
	calcSlice(&colStep, &nColsOut, nColsInRange);

	printf("inFileName=%s, outFileName=%s\n", inFileName, outFileName);
	printf("nRowsOut=%lli, nColsOut=%lli, colStep=%lli, rowStep=%lli\n", nRowsOut, nColsOut, colStep, rowStep);
	printf("row0=%lli, row1=%lli, col0=%lli, col1=%lli\n\n", row0, row1, col0, col1);

	fout = fopen(outFileName, "w");
	if(!fout){ fprintf(stderr, "Couldn't open output file '%s'\n", outFileName); perror(NULL); exit(EXIT_FAILURE); }

	fwriteRev(&nRowsOut, 4, 1, fout);
	fwriteRev(&nColsOut, 4, 1, fout);

	int64_t i,j;
	double val;

	tic();

	if(row0 > 0){
		int64_t shift=row0*nColsIn*8;
		if(fseeko(fin, shift, SEEK_CUR)) {
			fprintf(stderr, "ERROR: fseeko to row0 failed on input file. (shift = %lli)\n", shift);
			exit(EXIT_FAILURE);
		}
	}
	
	for(i=0; i < nRowsOut; i++){
		//seek up to col0
		if(col0 > 0){
			int64_t shift = col0*8;
			if(fseeko(fin, shift, SEEK_CUR)) {
				fprintf(stderr, "ERROR: fseeko to col0 failed on input file on row %lli of output. (shift=%lli)\n", i, shift);
				exit(EXIT_FAILURE);
			}
		}

		for(j=0; j < nColsOut; j++){
			//write the col
			freadRev(&val, 8, 1, fin);
			fwriteRev(&val, 8, 1, fout);			
			
			//now seek past (colStep-1) cols
			if(colStep > 1){
				if(fseeko(fin, (colStep-1)*8, SEEK_CUR)) {
					fprintf(stderr, "ERROR: fseeko by colStep failed on input file after row %lli, col %lli of output\n", i, j);
					exit(EXIT_FAILURE);
				}
			}

		}

		//now seek past (rowStep-1) rows
		if(rowStep > 1){
			if(fseeko(fin, (rowStep-1)*nColsIn*8, SEEK_CUR)) {
				fprintf(stderr, "ERROR: fseeko by rowStep failed on input file after row %lli of output\n", i);
				exit(EXIT_FAILURE);
			}
		}		

		if(toc() > 3.0) {
			printf("%f%% ", (100.0 * (double)i) / (double)nRowsOut);
			fflush(stdout);
			tic();
		}
	}

	fclose(fout);
	fclose(fin);

	/* Other code omitted */
	exit(EXIT_SUCCESS);
}

void calcSlice(int64_t *step, int64_t *nOut, int64_t nInRange) {

	if(*step > 0){
		if(*nOut > 0){ 
			fprintf(stderr, "ERROR: Can't specify both nRows and rowStep or nCols and colStep\n");
			exit(EXIT_FAILURE);
		}else{ //request in terms of rowstep		
			*nOut = nInRange / *step;
		}
	}else{
		if(*nOut > 0){ //request in terms of nRowsOut
			*step = nInRange / *nOut;
			*nOut = nInRange / *step;
		}else{ //no request, use all rows
			*step = 1;
			*nOut = nInRange;		
		}
	}
}
