/*------------------------------------------------------------------------------*
 * File Name:	readjnb.c														*
 * Creation:	GRD 8/7/2001													*
 * Purpose:		OriginC Source C file											*
 * Copyright (c) OriginLab Corp. 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008	*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:	GRD 2003.01.14 No new worksheet for Text+Binary			*
 *------------------------------------------------------------------------------*/
 
#include <origin.h>
#include "readjnb.h"

#define DATA_RECORD	1
#define NAME_RECORD	2
#define EOF_RECORD	134

#define	FILETYPE1	1
#define	FILETYPE2	2

#define	ERR1	1	// Could not open file.
#define ERR2	2	// File truncated.
#define ERR3	3	// This file type is unknown.
#define ERR4	4	// Failed to identify more records.
#define ERR5	5	// Failed to find records.

// The main function

int readjnb(String filename, String strWksName)
{
	file	fid;				// The file handle
	uint	iFileSize = 0;		// The file size
	uint	iFileType = 0;		// Type 1 or Type 2
	uint	iRecordType = 0;	// Record types allowed : Data, Name and EOF	

	uint	iBytesRead = 0;		// Generic, returns bytes read using file.Read()
	uint	iTemp = 0;			// Generic
	uint	iStatus = 0;		// Status/Error of import
	uint	iNumCols = 0;		// Number of Columns
	uint	iOffset = 0;		// Offset to next record

// Begin code
	if( fid.Open(filename, file::modeRead) )
	{
		// What size is the file
		iFileSize = fid.SeekToEnd();
		// Rewind
		fid.SeekToBegin();
		iBytesRead = fid.Read(&iTemp, sizeof(iTemp));
		fid.SeekToBegin();
		if((iTemp & 255) == 0)
		{
			// This is a worksheet file, start with the header
			iStatus = read_header(fid, &iFileType, &iNumCols, iFileSize);
			if(iStatus)
			{
				fid.Close();
				return iStatus;
			}
			// Locate the first record
			iStatus = seek_start(fid, iFileSize, &iOffset, &iRecordType);
			if(iStatus)
			{
				fid.Close();
				return iStatus;
			}
			// Process the records
			iStatus = read_file(fid, &iOffset, iFileSize, iFileType, &iRecordType);
			if(iStatus)
			{
				fid.Close();
				return iStatus;
			}
		}
		else
		{
			// This is a text file
			fid.Close();
			// No new worksheet for Text+Binary
			return 2;
			
			// In case I decide to handle these ...
			if((iTemp & 255) == 255)
			{
				// This is a long text file
				iTemp = (iTemp&16776960)/256;
				LT_execute("Type -a $OMRDJNB.Warnings.Text;");
			}
			else
			{
				// This is a short text file
				iTemp&255;
				LT_execute("Type -a $OMRDJNB.Warnings.Text;");
			}
		}
	}
	else
	{
		// Couldn't open the file
		iStatus = 1;
		printf("Error %u in %s:\n", iStatus, filename);
		LT_execute("Type -a $OMRDJNB.Errors.Err1;");
	}
	fid.Close();
	return iStatus;
}


// Header contains the File Type and the Number of Columns
int read_header(file &fid, uint *piFileType, uint *piNumCols, uint iFileSize)
{
	const int	cM0 = 360704;
	const int	cM1 = 28674;
	const int	cM2 = 8324;
	uint		iBytesRead = 0;
	uint		iValue = 0;
	uint		iJunk = 0;
	ushort		iShort = 0;
	uint		iPos = 0;
	uint		iOffset = 0;
	char		cFlag1 = 0;
	char		cFlag2 = 0;
	
	// Must read this value
	iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x00 0x81 0x05 0x00
	if(cM0 != iValue)
	{
		printf("Error : 21\n");
		LT_execute("type -a $OMRDJNB.Errors.general;");
		return 21;
	}

	// True file size should match internal description
	iBytesRead = fid.Read(&iValue, sizeof(iValue));
	if(iFileSize != (iValue + 8)) LT_execute("type -a OMRDJNB.Warnings.warn1");

	// Must read this value
	iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x02 0x70 0x00 0x00
	if(cM1 != iValue)
	{
		printf("Error : 22\n");
		LT_execute("type -a $OMRDJNB.Errors.general;");
		return 21;
	}

	// Skip over standard header stuff
	iBytesRead = fid.Read(&iOffset, sizeof(iOffset));
	iPos = fid.GetPosition();
	iPos += iOffset;
	fid.Seek(iPos, file::begin);

	// Must read this value
	iBytesRead = fid.Read(&iShort, sizeof(iShort));	// 0x84 0x20
	if(cM2 != iShort)
	{
		printf("Error : 23\n");
		LT_execute("type -a $OMRDJNB.Errors.general;");
		return 23;
	}

	// Establish file Type
	iBytesRead = fid.Read(&cFlag1, sizeof(cFlag1));
	iBytesRead = fid.Read(&cFlag2, sizeof(cFlag2));
	if(3 != cFlag1 && 5 != cFlag1)
	{
		printf("Error : 24\n");
		LT_execute("type -a $OMRDJNB.Errors.general;");
		return 24;
	}
	iBytesRead = fid.Read(&iValue, sizeof(iValue));
	if(8 != iValue && 16 != iValue)
	{
		printf("Error : 25\n");
		LT_execute("type -a $OMRDJNB.Errors.general;");
		return 25;
	}

	// Set the file Type and get the number of columns
	iBytesRead = fid.Read(&iJunk, sizeof(iJunk));
	if(8 == iValue)
	{
		*piFileType = FILETYPE1;
		iBytesRead = fid.Read(&iShort, sizeof(iShort));
		*piNumCols = (uint) iShort + 1;
		iBytesRead = fid.Read(&iShort, sizeof(iShort)); // maxRows0 - not needed
	}
	else
	{
		*piFileType = FILETYPE2;
		iBytesRead = fid.Read(&iJunk, sizeof(iJunk));
		iBytesRead = fid.Read(&iValue, sizeof(iValue));
		*piNumCols = iValue + 1;
		iBytesRead = fid.Read(&iValue, sizeof(iValue)); // maxRows0 - not needed
	}

	// Update the worksheet with the number of columns
	LT_set_var("ncols", *piNumCols);
	LT_execute("if(ncols > wks.ncols) wo -a $(ncols - wks.ncols);");
	// This 'short-circuits' the rest of the code if this is Type1 format
	// Return of 99 means use old SPW routine to read stream stored in TEMPfolder
	// When we have a routine that reads long double, we can eliminate this line
	if(*piFileType == 1) return 99; else return 0;
}


// Skip over unprocessed record types, establish current record type and offset to next record
int	seek_start(file &fid, uint iFileSize, uint *piOffset, uint *piRecordType)
{
	uint	iPos = 0;
	uint	iMax = 0;
	uint	iLoop = 0;
	uint	iBytesRead = 0;
	char	cChar[4];

	iPos = fid.GetPosition();
	iMax = iFileSize - 4;
	// An infinite loop
	for(iLoop = 0; iLoop == 0 && iPos < iMax; )
	{
		iBytesRead = fid.Read(&cChar, 4);
		*piRecordType = (int) cChar[0];
		if(1 == cChar[0] || 2 == cChar[0])
		{
			if(32 == cChar[1] && 5 == cChar[2] && 0 == cChar[3])
				iLoop = 1;
		}
		else
		{
			iPos +=1;
			fid.Seek(iPos, file::begin);
		}
	}	// End for loop
	if(!iLoop)
	{
		LT_execute("type -a $OMRDJNB.Warnings.eof;");
		return 31;
	}
	iBytesRead = fid.Read(&iLoop, sizeof(iLoop)); // this is the offset to the next record
	*piOffset = iLoop;
	return 0;
}


// Apologies for the long function. This was translated from LabTalk.
// Read the file records
int	read_file(file &fid, uint *piOffset, uint iFileSize, uint iFileType, uint *piRecordType)
{
	uint		iBytesRead = 0;
	uint		iValue = 0;
	uint		iPos = 0;
	uint		iLastPos = 0;
	uint		iStatus = 0;
	uint		iDone = 0;
	char		cChar = 0;
	uint		iThisCol =0;
	uint		iNumRows = 0;
	uint		iThisRow = 0;
	uint		iIgnore = 0;
	double		dBlockFlag = 0;
	string		sColName;	// this didn't work too well
	char		cBuffer[256];	// try this
	unsigned char		cTag = 0;
	double		dValue = 0;
	uint		iLength = 0;
	
	const uint	cM2 = 83914754;
	const uint	cM3 = 235085952;
	const uint	cM4 = 1253505;
	const uint	cM5 = 201662592;
	const uint	cM6 = 1253511;
	const uint	cM7 = 6;
	const uint	cM8 = 0;
	const uint	cM9 = 83914754;
	const uint	cM10 = 0;
	const uint	cM11 = 8322;
	const uint	cM12 = 16;
	const uint	cM13 = 204931;
	const uint	cM14 = 8;
	const uint	cM15 = 256;
	const uint	cM16 = 336003;
	const uint	cM17 = 16;

	// Set up an infinite loop with no third expression
	for(iDone = 0; iDone == 0; )
	{
		iLastPos = fid.GetPosition();
		switch(*piRecordType)
		{
			case DATA_RECORD:
				// Reading Data
				// Must read this value
				iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x02 0x70 0x00 0x05
				if(cM2 != iValue)
				{
					printf("Error : 51\n");
					LT_execute("type -a $OMRDJNB.Errors.general;");
					return 51;
				}
				// Must read this value
				iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x00 0x00 0x00 0x00
				if(cM8 != iValue)
				{
					printf("Error : 52\n");
					LT_execute("type -a $OMRDJNB.Errors.general;");
					return 52;
				}
				switch(iFileType)
				{
					case FILETYPE1:
						// This code does not handle Type1
						break;
					case FILETYPE2:
						// Must read this value
						iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x80 0x20 0x05 0x0C
						if(cM5 != iValue)
						{
							printf("Error : 53\n");
							LT_execute("type -a $OMRDJNB.Errors.general;");
							return 53;
						}
						
						iBytesRead = fid.Read(&iThisCol, sizeof(iThisCol)); // indexed from zero
						
						// Must read this value
						iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x87 0x20 0x13 0x00
						if(cM6 != iValue)
						{
							printf("Error : 54\n");
							LT_execute("type -a $OMRDJNB.Errors.general;");
							return 54;
						}
						iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));	// Ignore this
						
						// Must read this value
						iBytesRead = fid.Read(&iValue, sizeof(iValue));	// 0x06 0x00 0x00 0x00
						if(cM7 != iValue)
						{
							printf("Error : 55\n");
							LT_execute("type -a $OMRDJNB.Errors.general;");
							return 55;
						}
						iBytesRead = fid.Read(&iValue, sizeof(iValue));		// Block decision
						iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));	// Ignore this (duplicate column #?)
						iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));	// Ignore this
						iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));	// Ignore this (duplicate column #?)
						
						iBytesRead = fid.Read(&iNumRows, sizeof(iNumRows));	// Indexed from zero
						
						iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));	// Ignore this
						iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));	// Ignore this

						iPos = fid.GetPosition();

						// Read the 'block' (see developer's documentation) of FILETYPE2 DATA_RECORD values
						char	wksname[25];
						
						LT_get_str("%H", wksname, 25);
						Dataset	ThisCol(wksname, iThisCol);
						ThisCol.SetSize(iNumRows + 1);
						
						// Decision to read first or second block
						// fid.Seek(iPos + 15, file::begin);
						// iBytesRead = fid.Read(&cTag, 1);
						// if(cTag < 15 || cTag == 22)
						if(!(iValue & 16))
						{
							// Read the first block
							// skip over column name - may be long name
							fid.Seek(iPos + 15, file::begin);
							iBytesRead = fid.Read(&cTag, 1);
							if(cTag < 16)
							{
								iPos += 16;
								fid.Seek(iPos, file::begin);
							}
							else
							{
								fid.Seek(iPos + 4, file::begin);
								iBytesRead = fid.Read(&iValue, sizeof(iValue));
								iPos += (16 + iValue);
								fid.Seek(iPos, file::begin);
							}
						}
						else
						{
							// Read the second block
							iPos += 16 * (iNumRows + 2);
							fid.Seek(iPos, file::begin);
						}
						
						for(iThisRow = 0; iThisRow <= iNumRows ; iThisRow++)
						{
							// Read individual rows of Type 2 data here
							fid.Seek(iPos + 15, file::begin);
							iBytesRead = fid.Read(&cTag, 1);
							fid.Seek(iPos, file::begin);	// 'backup'
							switch(cTag)
							{
								case 18:	// Ignore
								case 21:	// Ignore
									iPos +=16;
									fid.Seek(iPos, file::begin);
									break;
								case 22:	//	This is a double
									iBytesRead = fid.Read(&dValue, sizeof(dValue));
									ThisCol[iThisRow] = dValue;
									iPos +=16;
									fid.Seek(iPos, file::begin);
									break;
								case 32:	// This is Long text
									iBytesRead = fid.Read(&iIgnore, sizeof(iIgnore));
									iBytesRead = fid.Read(&iLength, sizeof(iLength));
									iPos += 16;
									fid.Seek(iPos, file::begin);
									if(iLength < 254)
									{
											if(iLength > 24)
											{
												// Must prepare Origin for this
												LT_set_var("width", iLength);
												LT_set_var("thiscol", iThisCol + 1);
												LT_execute("wks.col$(thiscol).twidth = width;");
											}
											for(iIgnore = 0; iIgnore < iLength; iIgnore++)
											{
												// copy string
												iBytesRead = fid.Read(&cChar, 1);
												cBuffer[iIgnore] = cChar;
											}
											cBuffer[iIgnore] = 0;
											// write the value
											ThisCol.SetText(iThisRow, cBuffer);
											iPos += iLength;
									}
									else
									{
										// it better be
									}
									break;
								case 255:	// Ignore
									iPos +=16;
									fid.Seek(iPos, file::begin);
									break;
								default:	// Treat this as short text of length 'cTag'
									if(cTag < 16)
									{
										for(iIgnore = 0; iIgnore < cTag ; iIgnore++)
										{
											iBytesRead = fid.Read(&cChar, 1);
											cBuffer[iIgnore] = cChar;
										}
										cBuffer[iIgnore] = 0;
										ThisCol.SetText(iThisRow, cBuffer);
										iPos += 16;
										fid.Seek(iPos, file::begin);
									}
									else
									{
										// who knows
									}
							}	// End switch(cTag)
						}
						break;
					default:
						// Can't happen
				}	// End switch(iFileType) for DATA_RECORD
				break;
			case NAME_RECORD:
				// Reading Name
				// Must read this value
				iBytesRead = fid.Read(&iValue, sizeof(iValue));
				if(cM9 != iValue)
				{
					printf("Error : 61\n");
					LT_execute("type -a $OMRDJNB.Errors.general;");
					return 61;
				}
				// Must read this value
				iBytesRead = fid.Read(&iValue, sizeof(iValue));
				if(cM10 != iValue)
				{
					printf("Error : 62\n");
					LT_execute("type -a $OMRDJNB.Errors.general;");
					return 62;
				}
				// Must read this value
				iBytesRead = fid.Read(&iValue, sizeof(iValue));
				if(cM11 != iValue)
				{
					printf("Error : 63\n");
					LT_execute("type -a $OMRDJNB.Errors.general;");
					return 63;
				}

				switch(iFileType)
				{
					case FILETYPE1:		// This is a FILETYPE1 NAME_RECORD
						// This code does not handle Type1
						break;
					case FILETYPE2:		// This is a FILETYPE2 NAME_RECORD
						// Must read this value
						iBytesRead = fid.Read(&iValue, sizeof(iValue));
						if(cM15 != iValue)
						{
							printf("Error : 67\n");
							LT_execute("type -a $OMRDJNB.Errors.general;");
							return 67;
						}
						
						// Read an ASCIIZ value, then skip ahead
						iPos = fid.GetPosition();	// mark here
						for(iIgnore = 0; iIgnore <= 255; iIgnore ++) 
						{
							iBytesRead = fid.Read(&cChar, 1);
							cBuffer[iIgnore] = cChar;
							if(cChar == 0) iIgnore = 255;
						}
						iPos += 256;
						fid.Seek(iPos, file::begin);
						
						// Must read this value
						iBytesRead = fid.Read(&iValue, sizeof(iValue));
						if(cM16 != iValue)
						{
							printf("Error : 68\n");
							LT_execute("type -a $OMRDJNB.Errors.general;");
							return 68;
						}
						// Must read this value
						iBytesRead = fid.Read(&iValue, sizeof(iValue));
						if(cM17 != iValue)
						{
							printf("Error : 69\n");
							LT_execute("type -a $OMRDJNB.Errors.general;");
							return 69;
						}

						// Read which column this is (indexed from zero)
						iBytesRead = fid.Read(&iThisCol, sizeof(iThisCol));
						// and set the label
						LT_set_var("thiscol", iThisCol);
						LT_set_str("%N", cBuffer);
						LT_execute("wks.col$(thiscol+1).label$ = %N;");
						break;
						
					default:
						// This can't happen

				}	// End switch(iFileType)
				break;
				
			case EOF_RECORD:
				// Reading EOF
				iStatus = 0;
				break;
				
			default:
				iStatus = 41;
				printf("%u\n",*piRecordType);
				printf("Error : 41\n");
				LT_execute("type -a $OMRDJNB.Errors.general;");
		}	// End switch(*piRecordType)
		iLastPos += *piOffset;
		if(iLastPos >= iFileSize)
		{
			iDone = 1;
		}
		else
		{
			fid.Seek(iLastPos, file::begin);
			iBytesRead = fid.Read(&cChar, 1); // This is the Record Type
			*piRecordType = (uint) cChar;
			*piRecordType = *piRecordType & 255;
			iBytesRead = fid.Read(&cChar, 1); // This we assume is OK
			iBytesRead = fid.Read(&cChar, 1); // This we assume is OK
			iBytesRead = fid.Read(&cChar, 1); // This we assume is OK
			iBytesRead = fid.Read(piOffset, sizeof(uint));
		}
	}
	LT_execute("wks.labels();");
	return 0;
}


// The filter function versions
int ImportJNB(Page& pgTarget, TreeNode& trFilter, LPCSTR lpcszFile, int nFile)
{
	double	dResult;
	string	strWksName;
	
	if(pgTarget.GetType() == EXIST_WKS)
	{
		pgTarget.GetName(strWksName);
	}
	else
	{
		WorksheetPage	pg;
		pg.Create("origin.otp", CREATE_VISIBLE);
		pg.GetName(strWksName);
	}
	LT_set_str("%B", strWksName);
	LT_execute("win -a %B;");
	LT_set_str("%A", lpcszFile);
	LT_execute("result = run.section(ordjnb,main);");
	LT_get_var("result", &dResult);
	return 0;
}

int ImportSPW(Page& pgTarget, TreeNode& trFilter, LPCSTR lpcszFile, int nFile)
{
	string	strFileName;
	string	strPathName;
	double	dResult;
	string	strWksName;
	
	if(pgTarget.GetType() == EXIST_WKS)
	{
		pgTarget.GetName(strWksName);
	}
	else
	{
		WorksheetPage	pg;
		pg.Create("origin.otp", CREATE_VISIBLE);
		pg.GetName(strWksName);
	}
	// Old LabTalk function requires Path in %B and filename in %A
	LT_set_str("%B", strWksName);
	LT_execute("win -a %B;");
	strFileName = GetFileName(lpcszFile);
	strPathName = GetFilePath(lpcszFile);
	LT_set_str("%A", strFileName);
	LT_set_str("%B", strPathName);
	LT_execute("result = run.section(appfile,SPWIN);");
	LT_get_var("result", &dResult);
	return 0;
}

int ImportSPD(Page& pgTarget, TreeNode& trFilter, LPCSTR lpcszFile, int nFile)
{
	string	strFileName;
	string	strPathName;
	double	dResult;
	string	strWksName;
	
	if(pgTarget.GetType() == EXIST_WKS)
	{
		pgTarget.GetName(strWksName);
	}
	else
	{
		WorksheetPage	pg;
		pg.Create("origin.otp", CREATE_VISIBLE);
		pg.GetName(strWksName);
	}
	// Old LabTalk function requires Path in %B and filename in %A
	LT_set_str("%B", strWksName);
	LT_execute("win -a %B;");
	strFileName = GetFileName(lpcszFile);
	strPathName = GetFilePath(lpcszFile);
	LT_set_str("%A", strFileName);
	LT_set_str("%B", strPathName);
	LT_execute("result = run.section(appfile,SPDOS);");
	LT_get_var("result", &dResult);
	return 0;
}
