/***************************************************************************
    File                 : OriginDefaultParser.cpp
    --------------------------------------------------------------------
    Copyright            : (C) 2005-2008 Stefan Gerlach
						   (C) 2007-2008 Alex Kargovsky, Ion Vasilief
    Email (use @ for *)  : kargovsky*yumr.phys.msu.su, ion_vasilief*yahoo.fr
    Description          : Default Origin file parser class

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/

#include "OriginDefaultParser.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <cstring>

using namespace Origin;

#define MAX_LEVEL 20
#define ERROR_MSG "Please send the OPJ file and the opjfile.log to the author of liborigin!\n"

#define SwapBytes(x) ByteSwap((unsigned char *) &x,sizeof(x))

void ByteSwap(unsigned char * b, int n) {
	register int i = 0;
	register int j = n-1;
	while (i<j) {
		std::swap(b[i], b[j]);
		i++, j--;
	}
}

bool IsBigEndian()
{
	short word = 0x4321;
	if((*(char *)& word) != 0x21 )
		return true;
	else
		return false;
}

OriginDefaultParser::OriginDefaultParser(const string& fileName)
:	fileName(fileName)
{
}

bool OriginDefaultParser::parse()
{
	int i;
	FILE *f, *debug;
	if((f=fopen(fileName.c_str(),"rb")) == NULL ) {
		printf("Could not open %s!\n",fileName);
		return false;
	}

	if((debug=fopen("opjfile.log","w+")) == NULL ) {
		printf("Could not open log file!\n");
		return false;
	}

	////////////////////////////// check version from header ///////////////////////////////
	char vers[5];
	vers[4]=0;

	// get version
	fseek(f,0x7,SEEK_SET);
	fread(&vers,4,1,f);
	int version = atoi(vers);

	unsigned char c=0;	// tmp char

	fprintf(debug,"HEADER :\n");
	for(i=0;i<0x16;i++) {	// skip header + 5 Bytes ("27")
		fread(&c,1,1,f);
		fprintf(debug,"%.2X ",c);
		if(!((i+1)%16)) fprintf(debug,"\n");
	}
	fprintf(debug,"\n");

	do{
		fread(&c,1,1,f);
	} while (c != '\n');
	fprintf(debug,"	[file header @ 0x%X]\n", (unsigned int) ftell(f));

	/////////////////// find column ///////////////////////////////////////////////////////////
	if(version>410)
		for(i=0;i<5;i++)	// skip "0"
			fread(&c,1,1,f);

	int col_found;
	fread(&col_found,4,1,f);
	if(IsBigEndian()) SwapBytes(col_found);

	fread(&c,1,1,f);	// skip '\n'
	fprintf(debug,"	[column found = %d/0x%X @ 0x%X]\n",col_found,col_found,(unsigned int) ftell(f));

	int current_col=1, nr=0, nbytes=0;
	double a;
	char name[25], valuesize;
	while(col_found > 0 && col_found < 0x84) {	// should be 0x72, 0x73 or 0x83
		//////////////////////////////// COLUMN HEADER /////////////////////////////////////////////
		fprintf(debug,"COLUMN HEADER :\n");
		for(i=0;i < 0x3D;i++) {	// skip 0x3C chars to value size
			fread(&c,1,1,f);
			//if(i>21 && i<27) {
			fprintf(debug,"%.2X ",c);
			if(!((i+1)%16)) fprintf(debug,"\n");
			//}
		}
		fprintf(debug,"\n");

		fread(&valuesize,1,1,f);
		fprintf(debug,"	[valuesize = %d @ 0x%X]\n",valuesize,(unsigned int) ftell(f)-1);
		if(valuesize <= 0) {
			fprintf(debug,"	WARNING : found strange valuesize of %d\n",valuesize);
			valuesize=10;
		}

		fprintf(debug,"SKIP :\n");
		for(i=0;i<0x1A;i++) {	// skip to name
			fread(&c,1,1,f);
			fprintf(debug,"%.2X ",c);
			if(!((i+1)%16)) fprintf(debug,"\n");
		}
		fprintf(debug,"\n");

		// read name
		fprintf(debug,"	[Spreadsheet @ 0x%X]\n",(unsigned int) ftell(f));
		fflush(debug);
		fread(&name,25,1,f);
		//char* sname = new char[26];
		char sname[26];
		sprintf(sname,"%s",strtok(name,"_"));	// spreadsheet name
		char* cname = strtok(NULL,"_");	// column name
		while(char* tmpstr = strtok(NULL,"_")) {	// get multiple-"_" title correct
			strcat(sname,"_");
			strcat(sname,cname);
			strcpy(cname,tmpstr);
		}
		int spread=0;
		if(speadSheets.size() == 0 || findSpreadByName(sname) == -1) {
			fprintf(debug,"NEW SPREADSHEET\n");
			current_col=1;
			speadSheets.push_back(SpreadSheet(sname));
			spread=speadSheets.size()-1;
			speadSheets.back().maxRows=0;
		}
		else {

			spread = findSpreadByName(sname);

			current_col=speadSheets[spread].columns.size();

			if(!current_col)
				current_col=1;
			current_col++;
		}
		fprintf(debug,"SPREADSHEET = %s COLUMN %d NAME = %s (@0x%X)\n",
			sname, current_col, cname, (unsigned int) ftell(f));
		fflush(debug);

		if(cname == 0) {
			fprintf(debug,"NO COLUMN NAME FOUND! Must be a Matrix or Function.\n");
			////////////////////////////// READ matrixes or functions ////////////////////////////////////
			fprintf(debug,"Reading MATRIX.\n");
			fflush(debug);

			fprintf(debug,"	[position @ 0x%X]\n",(unsigned int) ftell(f));
			// TODO
			fprintf(debug,"	SIGNATURE : ");
			for(i=0;i<2;i++) {	// skip header
				fread(&c,1,1,f);
				fprintf(debug,"%.2X ",c);
			}
			fflush(debug);

			do{	// skip until '\n'
				fread(&c,1,1,f);
				// fprintf(debug,"%.2X ",c);
			} while (c != '\n');
			fprintf(debug,"\n");
			fflush(debug);

			// read size
			int size;
			fread(&size,4,1,f);
			fread(&c,1,1,f);	// skip '\n'
			// TODO : use entry size : double, float, ...
			size /= 8;
			fprintf(debug,"	SIZE = %d\n",size);
			fflush(debug);

			// catch exception
			if(size>10000)
				size=1000;

			fprintf(debug,"VALUES :\n");
			speadSheets[speadSheets.size()-1].maxRows=1;

			double value=0;
			for(i=0;i<size;i++) {	// read data
				string stmp;
				if(i<26)
					stmp=i+0x41;
				else if(i<26*26) {
					stmp = 0x40+i/26;
					stmp[1] = i%26+0x41;
				}
				else {
					stmp = 0x40+i/26/26;
					stmp[1] = i/26%26+0x41;
					stmp[2] = i%26+0x41;
				}
				speadSheets[speadSheets.size()-1].columns.push_back(stmp);
				fread(&value,8,1,f);
				speadSheets[speadSheets.size()-1].columns[i].data.push_back(/*Data(value)*/value);

				fprintf(debug,"%g ",value);
			}
			fprintf(debug,"\n");
			fflush(debug);

		}
		else {	// worksheet
			speadSheets[spread].columns.push_back(SpreadColumn(cname));

			////////////////////////////// SIZE of column /////////////////////////////////////////////
			do{	// skip until '\n'
				fread(&c,1,1,f);
			} while (c != '\n');

			fread(&nbytes,4,1,f);
			if(IsBigEndian()) SwapBytes(nbytes);
			if(fmod(nbytes,(double)valuesize)>0)
				fprintf(debug,"WARNING: data section could not be read correct\n");
			nr = nbytes / valuesize;
			fprintf(debug,"	[number of rows = %d (%d Bytes) @ 0x%X]\n",nr,nbytes,(unsigned int) ftell(f));
			fflush(debug);

			speadSheets[spread].maxRows<nr?speadSheets[spread].maxRows=nr:0;

			////////////////////////////////////// DATA ////////////////////////////////////////////////
			fread(&c,1,1,f);	// skip '\n'
			if(valuesize != 8 && valuesize <= 16) {	// skip 0 0
				fread(&c,1,1,f);
				fread(&c,1,1,f);
			}
			fprintf(debug,"	[data @ 0x%X]\n",(unsigned int) ftell(f));
			fflush(debug);

			for (i=0;i<nr;i++) {
				if(valuesize <= 16) {	// value
					fread(&a,valuesize,1,f);
					if(IsBigEndian()) SwapBytes(a);
					fprintf(debug,"%g ",a);
					speadSheets[spread].columns[(current_col-1)].data.push_back(/*Data(a)*/a);
				}
				else {			// label
					char *stmp = new char[valuesize+1];
					fread(stmp,valuesize,1,f);
					fprintf(debug,"%s ",stmp);
					speadSheets[spread].columns[(current_col-1)].data.push_back(/*Data(stmp)*/string(stmp));
					delete stmp;
				}
			}
		}	// else
		//		fprintf(debug,"	[now @ 0x%X]\n",ftell(f));
		fprintf(debug,"\n");
		fflush(debug);

		for(i=0;i<4;i++)	// skip "0"
			fread(&c,1,1,f);
		if(valuesize == 8 || valuesize > 16) {	// skip 0 0
			fread(&c,1,1,f);
			fread(&c,1,1,f);
		}
		fread(&col_found,4,1,f);
		if(IsBigEndian()) SwapBytes(col_found);
		fread(&c,1,1,f);	// skip '\n'
		fprintf(debug,"	[column found = %d/0x%X (@ 0x%X)]\n",col_found,col_found,(unsigned int) ftell(f)-5);
		fflush(debug);
	}

	////////////////////// HEADER SECTION //////////////////////////////////////
	// TODO : use new method ('\n')

	int POS = ftell(f)-11;
	fprintf(debug,"\nHEADER SECTION\n");
	fprintf(debug,"	nr_spreads = %d\n",speadSheets.size());
	fprintf(debug,"	[position @ 0x%X]\n",POS);
	fflush(debug);

	///////////////////// speadSheets INFOS ////////////////////////////////////
	int LAYER=0;
	int COL_JUMP = 0x1ED;
	for(unsigned int i=0; i < speadSheets.size(); i++) {
		fprintf(debug,"		reading	Spreadsheet %d/%zd properties\n",i+1,speadSheets.size());
		fflush(debug);
		if(i > 0) {
			if (version == 700 )
				POS += 0x2530 + speadSheets[i-1].columns.size()*COL_JUMP;
			else if (version == 610 )
				POS += 0x25A4 + speadSheets[i-1].columns.size()*COL_JUMP;
			else if (version == 604 )
				POS += 0x25A0 + speadSheets[i-1].columns.size()*COL_JUMP;
			else if (version == 601 )
				POS += 0x2560 + speadSheets[i-1].columns.size()*COL_JUMP;	// ?
			else if (version == 600 )
				POS += 0x2560 + speadSheets[i-1].columns.size()*COL_JUMP;
			else if (version == 500 )
				POS += 0x92C + speadSheets[i-1].columns.size()*COL_JUMP;
			else if (version == 410 )
				POS += 0x7FB + speadSheets[i-1].columns.size()*COL_JUMP;
		}

		fprintf(debug,"			reading	Header\n");
		fflush(debug);
		// HEADER
		// check header
		int ORIGIN = 0x55;
		if(version == 500)
			ORIGIN = 0x58;
		fseek(f,POS + ORIGIN,SEEK_SET);	// check for 'O'RIGIN
		char c;
		fread(&c,1,1,f);
		int jump=0;
		if( c == 'O')
			fprintf(debug,"			\"ORIGIN\" found ! (@ 0x%X)\n",POS+ORIGIN);
		while( c != 'O' && jump < MAX_LEVEL) {	// no inf loop
			fprintf(debug,"		TRY %d	\"O\"RIGIN not found ! : %c (@ 0x%X)",jump+1,c,POS+ORIGIN);
			fprintf(debug,"			POS=0x%X | ORIGIN = 0x%X\n",POS,ORIGIN);
			fflush(debug);
			POS+=0x1F2;
			fseek(f,POS + ORIGIN,SEEK_SET);
			fread(&c,1,1,f);
			jump++;
		}

		int spread=i;
		if(jump == MAX_LEVEL){
			fprintf(debug,"		Spreadsheet SECTION not found ! 	(@ 0x%X)\n",POS-10*0x1F2+0x55);
			// setColName(spread);
			return -5;
		}

		fprintf(debug,"			[Spreadsheet SECTION (@ 0x%X)]\n",POS);
		fflush(debug);

		// check spreadsheet name
		fseek(f,POS + 0x12,SEEK_SET);
		fread(&name,25,1,f);

		spread=findSpreadByName(name);
		if(spread == -1)
			spread=i;

		fprintf(debug,"			SPREADSHEET %d NAME : %s	(@ 0x%X) has %d columns\n",
			spread+1,name,POS + 0x12,speadSheets[spread].columns.size());
		fflush(debug);

		int ATYPE=0;
		LAYER = POS;
		if (version == 700)
			ATYPE = 0x2E4;
		else if (version == 610)
			ATYPE = 0x358;
		else if (version == 604)
			ATYPE = 0x354;
		else if (version == 601)
			ATYPE = 0x500;	// ?
		else if (version == 600)
			ATYPE = 0x314;
		else if (version == 500) {
			COL_JUMP=0x5D;
			ATYPE = 0x300;
		}
		else if (version == 410) {
			COL_JUMP = 0x58;
			ATYPE = 0x229;
		}
		fflush(debug);

		/////////////// COLUMN Types ///////////////////////////////////////////
		fprintf(debug,"			Spreadsheet has %d columns\n",speadSheets[spread].columns.size());
		for (unsigned int j=0;j<speadSheets[spread].columns.size();j++) {
			fprintf(debug,"			reading	COLUMN %d/%zd type\n",j+1,speadSheets[spread].columns.size());
			fflush(debug);
			fseek(f,LAYER+ATYPE+j*COL_JUMP, SEEK_SET);
			fread(&name,25,1,f);

			fseek(f,LAYER+ATYPE+j*COL_JUMP-1, SEEK_SET);
			fread(&c,1,1,f);
			SpreadColumn::ColumnType type;
			switch(c) {
			case 3:
				type = SpreadColumn::X;
				break;
			case 0:
				type = SpreadColumn::Y;
				break;
			case 5:
				type = SpreadColumn::Z;
				break;
			case 6:
				type = SpreadColumn::XErr;
				break;
			case 2:
				type = SpreadColumn::YErr;
				break;
			case 4:
				type = SpreadColumn::Label;
				break;
			default:
				type = SpreadColumn::NONE;
				break;
			}

			speadSheets[spread].columns[j].type=type;

			fprintf(debug,"				COLUMN \"%s\" type = %s (@ 0x%X)\n",
				speadSheets[spread].columns[j].name.c_str(),type,LAYER+ATYPE+j*COL_JUMP);
			fflush(debug);

			// check column name
			int max_length=11;	// only first 11 chars are saved here !
			int name_length = speadSheets[spread].columns[j].name.length();
			int length = (name_length < max_length) ? name_length : max_length;

			if(speadSheets[spread].columns[j].name.substr(0,length) == name) {
				fprintf(debug,"				TEST : column name = \"%s\". OK!\n",
					speadSheets[spread].columns[j].name.c_str());
			}
			else {
				fprintf(debug,"				TEST : COLUMN %d name mismatch (\"%s\" != \"%s\")\n",
					j+1,name,speadSheets[spread].columns[j].name.c_str());
				//fprintf(debug,"ERROR : column name mismatch! Continue anyway.\n"ERROR_MSG);
			}
			fflush(debug);
		}
		fprintf(debug,"		Done with spreadsheet %d\n",spread);
		fflush(debug);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////////

	// TODO : GRAPHS

	fprintf(debug,"Done parsing\n");
	fclose(debug);

	return true;
}

OriginParser* createOriginDefaultParser(const string& fileName)
{
	return new OriginDefaultParser(fileName);
}