#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <math.h>
#include <time.h>
#include <sys/types.h>
#include <machine/endian.h>
#include "BasicNewton.h"

/* While not usually a fan of globals, the input file pointer was basically
   just being passed to everything anyway.  Making it a global is just simpler
   and more efficient. */
FILE *InFile;

void reportError()
{
  perror(NULL);
  exit(-1);
}

void printWChar(wchar_t *wideStr)
{
  wchar_t wideChar=*wideStr;
  while(wideChar!=0)
  {
    printf("%lc",wideChar);
	wideStr++;
	wideChar=*wideStr;
  }
}

wchar_t *ulong2wchar(ULong input,wchar_t *output)
{
  /* requires an input buffer at least numOfDigits+1 in size */
  unsigned int digitNum;
  unsigned int numOfDigits=(unsigned int)floor(log10(input))+1;
  output[numOfDigits]=L'\0';
  digitNum=numOfDigits-1;
  do
  {
    output[digitNum]=(wchar_t)(input%10+L'0');
	input/=10;
	digitNum--;
  } while(input>0);
  return output;
}

ULong getULong()
{
  ULong ulongVal;
  if(fread(&ulongVal,sizeof(ULong),1,InFile)!=1)
  {
    puts("Can't read input file.");
    exit(-1);
  }
  return ntohl(ulongVal);
}

UShort getUShort()
{
  UShort ushortVal;
  if(fread(&ushortVal,sizeof(UShort),1,InFile)!=1)
  {
    puts("Can't read input file.");
    exit(-1);
  }
  return ntohs(ushortVal);
}

int isPointer(ULong ref)
{
  return((ref&0x00000003)==kPointerFlag);
}

ULong getPointer(ULong ref)
{
  return(ref-1);
}

int isInt(ULong ref)
{
  return((ref&0x00000003)==kIntegerFlag);
}

ULong getInt(ULong ref)
{
  return(ref>>2);
}

int isNil(ULong ref)
{
  return(ref==kNilFlag);
}

int isArray(ULong ref)
{
  return (ref&kObjSlotted)&&!(ref&kObjFrame);
}

int isFrame(ULong ref)
{
  return (ref&kObjSlotted)&&(ref&kObjFrame);
}

int isBinary(ULong ref)
{
  return !(ref&kObjSlotted);
}

int isSymbol(ULong classRef)
{
  return (classRef&kSymbolClass);
}

ULong extractSize(ULong ref)
{
  return (ref>>8);
}

ULong getNumOfSlots(ULong ref)
{
  return extractSize(ref)/sizeof(ULong)-3;
}

void printField(char *field,char *map[],ULong slots[],ULong numOfSlots)
{
  /* prints out string or integer fields only */
  ULong stringLoc,stringLen;
  ULong embeddedInt;
  ULong slotNum=0;
  wchar_t *stringSpace;
  while(slotNum<numOfSlots&&(strcmp(field,map[slotNum])!=0))slotNum++;
  if(slotNum!=numOfSlots)
  {
    if(isPointer(slots[slotNum]))
	{
	  stringLoc=getPointer(slots[slotNum]);
	  if(stringLen=isString(stringLoc))
	  {
		stringSpace=malloc(stringLen*sizeof(wchar_t));
		loadString(stringLoc,stringSpace);
		printWChar(stringSpace);
		free(stringSpace);
	  }
	  else
	  {
        puts("Non-string where a string was expected.");
	    exit(-1);
	  }
	}
	else if(isInt(slots[slotNum]))
	{
	  embeddedInt=getInt(slots[slotNum]);
	  printf("%ld",embeddedInt);
	}
	else
	{
	  puts("Unexpected object.");
	  exit(-1);
	}
  }
  else
  {
	puts("Required object not found.");
	exit(-1);
  }
}

void printDate(Date dateToPrint,int needsFixing)
{
  struct tm epochTimeStruct,dateStruct;
  time_t epochTime;
  char monthNames[][10]={"January","February","March","April","May","June",
                         "July","August","September","October","November","December"};
  epochTimeStruct.tm_sec=epochTimeStruct.tm_min=epochTimeStruct.tm_hour=0;
  epochTimeStruct.tm_mday=4;
  epochTimeStruct.tm_mon=0;
  epochTimeStruct.tm_year=4;
  epochTime=timegm(&epochTimeStruct);
  if(needsFixing)dateToPrint*=60;
  dateToPrint+=epochTime;
  gmtime_r(&dateToPrint,&dateStruct);
  printf("%s %d, %d",monthNames[dateStruct.tm_mon],dateStruct.tm_mday,dateStruct.tm_year+1900);
}

void printDateField(char *field,char *map[],ULong slots[],ULong numOfSlots,int needsFixing)
{
  Date embeddedDate;
  ULong slotNum=0;
  while(slotNum<numOfSlots&&(strcmp(field,map[slotNum])!=0))slotNum++;
  if(slotNum!=numOfSlots)
  {
	if(isInt(slots[slotNum]))
	{
	  embeddedDate=getInt(slots[slotNum]);
	  printDate(embeddedDate,needsFixing);
	}
	else
	{
	  puts("Unexpected object.");
	  exit(-1);
	}
  }
  else
  {
	puts("Required object not found.");
	exit(-1);
  }
}

void printStringsFromField(char *field,char *map[],ULong slots[],ULong numOfSlots,char *separator)
{
  /* prints out any strings found within the recursive structure labelled 'field' */
  ULong slotNum=0;
  while(slotNum<numOfSlots&&(strcmp(field,map[slotNum])!=0))slotNum++;
  if(slotNum!=numOfSlots)
  {
    if(isPointer(slots[slotNum]))
      processObject(getPointer(slots[slotNum]),separator);
  }
  else
  {
    puts("Required object not found.");
	exit(-1);
  }
}

/* This little routine is basically a copy of what was provided in the Apple documentation.
   Except it works. */
ULong symbolHashFunction(char* name)
{
  ULong result=0;
  Byte character;
  while(*name)
  {
    character=*name;
    if(character>='a'&&character<='z')
      result+=(character-('a'-'A'));
    else
      result+=character;
    name++;
  }
  return result*2654435769UL;
}

char *loadSymbol(unsigned long location,char *symbol)
{
  ULong hashVal,size;
  if(fseek(InFile,location,SEEK_SET)!=0)reportError();
  size=extractSize(getULong())-4*sizeof(ULong)+1;
  if(fseek(InFile,sizeof(ULong)*2,SEEK_CUR)!=0)reportError();
  hashVal=getULong();
  if(fread(symbol,1,size,InFile)!=size||
     symbolHashFunction(symbol)!=hashVal)
  {
    puts("Problem with symbol.");
    exit(-1);
  }
  return symbol;
}

ULong isString(unsigned long location)
{
  /* note that this actually does double duty by returning string size */
  ObjectHeaderPlus symbol,str;
  char *symbolData;
  int retVal=0;
  if(fseek(InFile,location,SEEK_SET)!=0)reportError();
  str.flags=getULong();
  str.marker=getULong();
  str.classOrMap=getULong();
  if((str.marker!=0)||(!isPointer(str.classOrMap))||(!isBinary(str.flags)))
    return retVal;
  else
  {
    if(fseek(InFile,getPointer(str.classOrMap),SEEK_SET)!=0)reportError();
	symbol.flags=getULong();
	symbol.marker=getULong();
	symbol.classOrMap=getULong();
	if((symbol.marker!=0)||(!isBinary(symbol.flags))||(!isSymbol(symbol.classOrMap)))
	  return retVal;
	else
	{
	  symbolData=malloc(extractSize(symbol.flags)-4*sizeof(ULong)+1);
	  loadSymbol(getPointer(str.classOrMap),symbolData);
	  if(strcmp(symbolData,"String")==0)
	    retVal=extractSize(str.flags)-3*sizeof(ULong)+1;
	  free(symbolData);	  
	}
  }
  return retVal;
}

wchar_t *loadString(unsigned long location,wchar_t *stringData)
{
  char *wrkStr;
  ObjectHeaderPlus str;
  ULong size,strNum;
  if(fseek(InFile,location,SEEK_SET)!=0)reportError();
  str.flags=getULong();
  str.marker=getULong();
  str.classOrMap=getULong();
  if((str.marker!=0)||(!isPointer(str.classOrMap))||(!isBinary(str.flags)))
  {
    puts("Not a string.");
	exit(-1);
  }
  size=extractSize(str.flags)-3*sizeof(ULong)+1;
  wrkStr=malloc(size);
  if(fread(wrkStr,1,size,InFile)!=size)
  {
    puts("Problem with string.");
    exit(-1);
  }
  for(strNum=0;strNum<size;strNum+=2)
  {
    stringData[strNum/2]=(wchar_t)((wrkStr[strNum]<<8)+wrkStr[strNum+1]);
  }
  free(wrkStr);
  return(stringData);
}


void linearizeMap(ULong mapStart,ULong map[],unsigned int last,ULong numOfSlots)
{
  ObjectHeaderPlus mapObj;
  ULong slot0,numOfSlotsInMap,slotNum;
  if(fseek(InFile,mapStart,SEEK_SET)!=0)reportError();
  mapObj.flags=getULong();
  mapObj.marker=getULong();
  mapObj.classOrMap=getULong();
  numOfSlotsInMap=getNumOfSlots(mapObj.flags);
  if((!isArray(mapObj.flags))||(mapObj.marker!=0))
  {
    puts("Bad map.");
    exit(-1);
  }
  slot0=getULong();
  for(slotNum=1;slotNum<numOfSlotsInMap;slotNum++)
  {
    map[last-numOfSlotsInMap+slotNum+1]=getULong();
  }
  last-=(numOfSlotsInMap-1);
  if(isNil(slot0))
  {
    /* no supermap */
    return;
  }
  else if(isPointer(slot0))
  {
    /* process the supermap */
    linearizeMap(getPointer(slot0),map,last,numOfSlots);
  }
  else
  {
    puts("Supermap error.");
    exit(-1);
  }
}

void processObject(ULong location,char *separator)
{
  ObjectHeaderPlus obj;
  ULong numOfSlots,slotNum,slotRef;
  ULong curPos;
  ULong *map;
  ULong strLength;
  wchar_t *stringSpace;
  if(fseek(InFile,location,SEEK_SET)!=0)reportError();
  obj.flags=getULong();
  obj.marker=getULong();
  if(obj.marker!=0)
  {
    puts("Non-zero marker.");
	exit(-1);
  }
  obj.classOrMap=getULong();
  if(isBinary(obj.flags))
  {
	if(strLength=isString(location))
	{
	  curPos=ftell(InFile);
      stringSpace=malloc(strLength);
      loadString(location,stringSpace);
	  printWChar(stringSpace);
      free(stringSpace);
	  printf(separator);
	  if(fseek(InFile,curPos,SEEK_SET)!=0)reportError();
	}
  }
  else
    numOfSlots=getNumOfSlots(obj.flags);
  if(isPointer(obj.classOrMap))
  {
	curPos=ftell(InFile);
    if(isFrame(obj.flags))
	{
	  map=malloc(numOfSlots*sizeof(ULong));
	  linearizeMap(getPointer(obj.classOrMap),map,numOfSlots-1,numOfSlots);
	  for(slotNum=0;slotNum<numOfSlots;slotNum++)
	  {
	    if(isPointer(map[slotNum]))
		  processObject(getPointer(map[slotNum]),separator);
	  }
	  free(map);
	}
	else
	{
      processObject(getPointer(obj.classOrMap),separator);
	}
	if(fseek(InFile,curPos,SEEK_SET)!=0)reportError();
  }
  if(isArray(obj.flags)||isFrame(obj.flags))
  {
	for(slotNum=0;slotNum<numOfSlots;slotNum++)
	{
	  slotRef=getULong();
	  if(isPointer(slotRef))
	  {
	    curPos=ftell(InFile);
	    processObject(getPointer(slotRef),separator);
		if(fseek(InFile,curPos,SEEK_SET)!=0)reportError();
	  }
	}
  }
}

void processPartFrame(ULong partFrameStart,int needsFixing)
{
  ObjectHeaderPlus map,frame,name;
  ULong mapSlot0,mapSlot1;
  ULong partContentsRef;
  ULong curPos;
  ULong numOfSlots;
  char symbolData[5];
  if(fseek(InFile,partFrameStart,SEEK_SET)!=0)reportError();
  frame.flags=getULong();
  frame.marker=getULong();
  frame.classOrMap=getULong();
  if((!isFrame(frame.flags))||
     (frame.marker!=0)||
     (!isPointer(frame.classOrMap)))
  {
    puts("Bad part frame.");
    exit(-1);
  }
  numOfSlots=getNumOfSlots(frame.flags);
  if(numOfSlots!=1)
  {
    puts("Wrong number of part frame slots.");
    exit(-1);
  }
  curPos=ftell(InFile);
  if(fseek(InFile,getPointer(frame.classOrMap),SEEK_SET)!=0)reportError();
  map.flags=getULong();
  map.marker=getULong();
  map.classOrMap=getULong();
  mapSlot0=getULong();
  mapSlot1=getULong();
  if((!isArray(map.flags))||
     (map.marker!=0)||
     (!isInt(map.classOrMap))||
     (getNumOfSlots(map.flags)!=2)||
     (!isNil(mapSlot0))||
     (!isPointer(mapSlot1)))
  {
    puts("Bad part frame map.");
    exit(-1);
  }
  if(fseek(InFile,getPointer(mapSlot1),SEEK_SET)!=0)reportError();
  name.flags=getULong();
  name.marker=getULong();
  name.classOrMap=getULong();
  if((!isBinary(name.flags))||
     (name.marker!=0)||
     (!isSymbol(name.classOrMap))||
	 (extractSize(name.flags)-4*sizeof(ULong)!=5))
  {
    puts("Bad part frame label.");
    exit(-1);
  }
  loadSymbol(getPointer(mapSlot1),symbolData);
  if(strcmp(symbolData,"book")!=0)
  {
    puts("Bad part frame label data.");
    exit(-1);
  }
  if(fseek(InFile,curPos,SEEK_SET)!=0)reportError();
  partContentsRef=getULong();
  if(!isPointer(partContentsRef))
  {
    puts("Bad part contents.");
	exit(-1);
  }
  processContentsFrame(getPointer(partContentsRef),needsFixing);
}

