#ifdef HAVE_CONFIG_H
#include <acconfig.h>
#endif
#include <stdlib.h>
#include <string.h>
#include "xls.h"
#include "catdoc.h"
#include "xltypes.h"
#include <math.h>
#include <time.h>

static unsigned char rec[MAX_MS_RECSIZE];
int biff_version=0;
void do_table(FILE *input,char *filename) {    
	long rectype;
	long reclen,build_year=0,build_rel=0,offset=0;
	int eof_flag=0;
	int itemsread=1;
	while (itemsread) {
		catdoc_read(rec,2,1,input);
		biff_version=getshort(rec,0);
		catdoc_read(rec,2,1,input);
		reclen=getshort(rec,0);
		if ( biff_version == 0x0809 || biff_version == 0x0409 ||
				biff_version == 0x0209 || biff_version == 0x0009 ) {
			if (reclen==8 || reclen==16) {
				if (biff_version == 0x0809 ) {
					itemsread=catdoc_read(rec,4,1,input);
					build_year=getshort(rec+2,0);
					build_rel=getshort(rec,0);
					if(build_year > 5 ) {
						itemsread=catdoc_read(rec,8,1,input);
						biff_version=8;
						offset=12;
					}
					else {
						biff_version=7;
						offset=4;
					}
				} else if (biff_version == 0x0209 ) {
					biff_version=3;
					offset=2;
				} else if (biff_version == 0x0409 ) {
					offset=2;
					biff_version=4;
				} else {
					biff_version=2;
				}
				itemsread=catdoc_read(rec,reclen-offset,1,input);
				break;
			} else {
				fprintf(stderr,"%s: Invalid BOF record\n",filename);
				return;
			} 
		} else {
			itemsread=catdoc_read(rec,126,1,input);
		}
	}
	if (catdoc_eof(input)) {
		fprintf(stderr,"%s: No BOF record found\n",filename);
		exit(1);
	}    
	while(itemsread){
		char buffer[2];
		rectype = 0;
		itemsread = catdoc_read(buffer, 2, 1, input);
		if (catdoc_eof(input)) {
			process_item(MSEOF,0,NULL);
			return;
		}
		
		rectype=getshort(buffer,0);
		if(itemsread == 0)
			break;
		reclen=0;

		itemsread = catdoc_read(buffer, 2, 1, input);
		reclen=getshort(buffer,0);
		if (reclen && reclen <MAX_MS_RECSIZE &&reclen >0){
			itemsread = catdoc_read(rec, 1, reclen, input);
			rec[reclen] = '\0';
		}
		if(eof_flag) {
			if (rectype != BOF) {
				break;
			}    
		}
/* 		fprintf(stderr,"Rectype %04xh reclen=%d\n",rectype, reclen);  */
		process_item(rectype,reclen,rec);
		if (rectype == MSEOF) {
			eof_flag=1;
		} else {
			eof_flag=0;	
		}  
	}
	return;
}
unsigned char **sst=NULL;/* Shared string table parsed into array of strings in
							output encoding*/
int sstsize = 0; /*Number of strings in SST*/
unsigned char *sstBuffer=NULL; /*Unparsed sst to accumulate all its parts*/
int sstBytes = 0; /*Size of SST Data, already accumulated in the buffer */
int codepage=1251; /*default*/
int prev_rectype=0;
/* holds a pointer to formula value, becouse value itself would be in
 * next biff record
 */
unsigned char **saved_reference = NULL;

void process_item (int rectype, int reclen, char *rec) {
	if (rectype != CONTINUE && prev_rectype == SST) {
		/* we have accumulated  unparsed SST, and now encountered
		 * another record, which indicates that SST is ended */
		parse_sst(sstBuffer,sstBytes);
	}	
	switch (rectype) {
		case FILEPASS: {
						   fprintf(stderr,"File is encrypted\n");
						   exit(69);
						   break;
					   }
		case 0x42: {
					   char *source_csname;			   
					   if (source_charset) break;
					   codepage=getshort(rec,0);
					   /*fprintf(stderr,"CODEPAGE %d\n",codepage);*/ 
					   if (codepage!=1200) {
						   char cp[9];
						   sprintf(cp,"cp%d",codepage);
						   check_charset(&source_csname,cp);
						   source_charset=read_charset(source_csname);
					   }		 
					   break;
				   }  
		case FORMAT: {
						 /*     int i;
								char *ptr;
								printf( "Format %x \"",getshort(rec,0));	 
								if (rec[2] == reclen - 3 && rec[3] != 0) {
								for (i=0,ptr=rec+3;i<rec[2];i++,ptr++) {
								fputc(*ptr,stdout);
								}
								} else {
								for (i=0,ptr=rec+5;i<rec[2];i++,ptr+=2) {
								fputc(*ptr,stdout);
								}    
								}	 
								printf ("\"\n"); */
						 break;
					 }			 
		case SST: {
					  /* Just copy SST into buffer, and wait until we get
					   * all CONTINUE records
					   */ 
					  if (sstBuffer||sst) {
						  fprintf(stderr,"Duplicated SST found\n");
						  return;
					  }    
					  sstBuffer=(char*)malloc(reclen);
					  sstBytes = reclen;
					  if (sstBuffer == NULL ) {
						  perror("SSTptr alloc error! ");
						  exit(1);
					  }	  
					  memcpy(sstBuffer,rec,reclen);
					  break;
				  }	
		case CONTINUE: {
						   if (prev_rectype != SST) {
							   /*fprintf(stderr,"Unexpected CONTINUE of record 0x%X\n",prev_rectype);*/
							   return; /* to avoid changing of prev_rectype;*/
						   }    
						   sstBuffer=realloc(sstBuffer,sstBytes+reclen);
						   if (sstBuffer == NULL ) {
							   perror("SSTptr realloc error! ");
							   exit(1);
						   }	  
						   memcpy(sstBuffer+sstBytes,rec,reclen);
						   sstBytes+=reclen;
						   return;
					   }			   
		case LABEL: {
						int row,col;
						unsigned char **pcell;
						unsigned char *src=(unsigned char *)rec+6;

						saved_reference=NULL;
						row = getshort(rec,0); 
						col = getshort(rec,2);
						/* 		fprintf(stderr,"LABEL!\n"); */
						pcell=allocate(row,col);
					    *pcell=copy_unicode_string(&src);
						break;
					}     
		case CONSTANT_STRING: {
								  int row = getshort(rec,0); 
								  int col = getshort(rec,2);
								  unsigned char **pcell;
								  int string_no=getshort(rec,6);
								  if (!sst) {
									  fprintf(stderr,"CONSTANT_STRING before SST parsed\n");
									  exit(1);
								  }    
								  saved_reference=NULL;
								  pcell=allocate(row,col);
								  if (string_no>=sstsize|| string_no < 0 ) {
									  fprintf(stderr,"string index out of boundary\n"); 
									  exit(1);	 
								  } else if (sst[string_no] !=NULL) {	
									  int len;
									  char *outptr;
									  len=strlen(sst[string_no]);
									  outptr=*pcell=malloc(len+2);
									  *(outptr++)='"';
									  strcpy(outptr,sst[string_no]);
								  } else {
									  *pcell=malloc(1);
									  strcpy(*pcell,"");
								  }	
								  break;
							  }
		case 0x03:
		case 0x103:
		case 0x303:
							  fprintf(stderr,"Something like number found: %X\n",rectype);		      
		case NUMBER: {
						 int row,col;
						 unsigned char **pcell;

						 saved_reference=NULL;
						 row = getshort(rec,0)-startrow; 
						 col = getshort(rec,2);
						 pcell=allocate(row,col);
						 *pcell=strdup(format_double(rec,6,getshort(rec,4)));
						 break;
					 }
		case INTEGER_CELL: {
							   int row,col;
							   unsigned char **pcell;

							   row = getshort(rec,0)-startrow;
							   col = getshort(rec,2);
							   pcell=allocate(row,col);
							   *pcell=strdup(format_int(getshort(rec,7),getshort(rec,4)));		  
							   break;

						   }				  
		case RK: {
					 int row,col;
					 unsigned char **pcell;

					 saved_reference=NULL;
					 row = getshort(rec,0)-startrow; 
					 col = getshort(rec,2);
					 pcell=allocate(row,col);
					 *pcell=strdup(format_rk(rec+6));
					 break;
				 }
		case MULRK: {
						int row,col,startcol,endcol,offset;
						unsigned char **pcell;
						row = getshort(rec,0)-startrow; 
						startcol = getshort(rec,2);
						endcol = getshort(rec,reclen-2);
						saved_reference=NULL;

						for (offset=6,col=startcol;col<=endcol;offset+=6,col++) { 
							pcell=allocate(row,col);
							*pcell=strdup(format_rk(rec+offset));

						}		 
						break;
					} 
		case FORMULA: { 
						  int row,col;
						  unsigned char **pcell;
						  saved_reference=NULL;
						  row = getshort(rec,0)-startrow; 
						  col = getshort(rec,2);
						  pcell=allocate(row,col);
						  if (((unsigned char)rec[12]==0xFF)&&(unsigned char)rec[13]==0xFF) {
							  /* not a floating point value */
							  if (rec[6]==1) {
								  /*boolean*/
								  char buf[2]="0";
								  buf[0]+=rec[9];
								  *pcell=strdup(buf);
							  } else if (rec[6]==2) {
								  /*error*/
								  char buf[6]="ERROR";
								  *pcell=strdup(buf);
							  } else if (rec[6]==0) {
								  saved_reference=pcell;
							  }   
						  } else {
							  int format_code=getshort(rec,4);
							  *pcell=strdup(format_double(rec,6,format_code));
						  }		 
						  break;
					  }
		case STRING: {
						 unsigned char *src=(unsigned char *)rec;
						 if (!saved_reference) {
							 fprintf(stderr,"String record without preceeding string formula\n");
							 break;
						 }
						 *saved_reference=copy_unicode_string(&src);
						 break;
					 }	    
		case BOF: {
					  if (rowptr) {
						  fprintf(stderr,"BOF when current sheet is not flushed\n");
						  free_sheet();
					  }
					  break;
				  }	  
		case MSEOF: {
						if (!rowptr) break;
						print_sheet();
						free_sheet();
						break;
					}
		case ROW: { 
					  /*  		fprintf(stderr,"Row! %d %d %d\n",getshort(rec,0), getshort(rec+2,0),getshort(rec+4,0));  */
					  break; 
				  } 
		case INDEX: {
						/* 		fprintf(stderr,"INDEX! %d %d\n", getlong(rec+4,0), getlong(rec+8,0));  */
						break;
					}
		default: {
#if	0	
					 fprintf(stderr,"Unknown record 0x%x\n length %d\n",rectype,reclen);		
#endif 
				 }
	}
	prev_rectype=rectype; 
}  

/* 
 * Quotes a string and returns mallocked copy of quoted string
 */ 
char *mark_string(char *instr) {
	int len=0;
	char *out;
	strlen(instr);
	if (instr==NULL) {
		out = malloc(2);
		strcpy(out,"");
	} else {
		out = malloc(len+2);
		*out='"';
		strcpy(out+1,instr);
		free (instr);
	}    
	return out;
}    
/*
 * Extracts string from sst and returns mallocked copy of it
 */
char *copy_unicode_string (unsigned char **src) {
	int count=0;
	int flags = 0;
	int start_offset=0;
	int to_skip=0;								/* используется для подсчета длины данных
												 * за концом строки */
	int offset = 1;								/* для учета переменной длины первого поля  */
	int charsize;
	/* 	char *realstart=*src; */
	char *dest;										/* куда будем копировать строку */
	char *s,*d,*c;

	int i,u,l,len;

	/* 	for(i=0;i<20;i++) */
	/* 		fprintf(stderr,"%02x ",(*src)[i]); */
	/* 	fprintf(stderr,"\n"); */

	flags = *(*src+1+offset);
	if (! ( flags == 0 || flags == 1 || flags == 8 || flags == 9 ||
				flags == 4 || flags == 5 || flags == 0x0c || flags == 0x0d ) ) {
		count=**src;
		flags = *(*src+offset);
		offset --;
	}
	else {
		count=getshort(*src,0);
	}   
	charsize=(flags &0x01) ? 2 : 1;

	switch (flags & 12 ) {
		case 0x0c: /* Far East with RichText formating */
			to_skip=4*getshort(*src,2+offset)+getlong(*src, 4+offset);
			start_offset=2+offset+2+4;
			/* 		fprintf(stderr,"Far East with RichText formating\n"); */
			break;

		case 0x08: /* With RichText formating */
			to_skip=4*getshort(*src,2+offset);
			start_offset=2+offset+2;
			/* 		fprintf(stderr,"With RichText formating %d\n",getshort(*src,2+offset)); */
			break;

		case 0x04: /* Far East */
			to_skip=getlong(*src, 2+offset);
			start_offset=2+offset+4;
			/* 		fprintf(stderr,"Far East\n"); */
			break;

		default:
			to_skip=0;
			start_offset=2+offset;
			/* 		fprintf(stderr,"Default string\n"); */
	}

	/* 	fprintf(stderr,"count=%d skip=%d start_offset=%d\n",count, to_skip, start_offset); */
	/* а здесь мы копируем строку	*/
	if ( (dest=malloc(count+1)) == NULL ) {
		perror("Dest string alloc error");
		*src+=(to_skip+start_offset+(count*charsize));
		exit(0);
	}
	*src+=start_offset;
	len = count;
	*dest=0;l=0;
	for (s=*src,d=dest,i=0;i<count;i++,s+=charsize) {
		/* 		fprintf(stderr,"l=%d len=%d count=%d charsize=%d\n",l,len,count,charsize); */
		if ( (charsize == 1 && (*s == 1 || *s == 0)) ||
				(charsize == 2 && (*s == 1 || *s == 0) && *(s+1) != 4)) {
			/* 			fprintf(stderr,"extchar (unicode)=%02x %02x\n",*s, *(s+1)); */
			charsize=(*s &0x01) ? 2 : 1;
			if (charsize == 2)
				s-=1;
			count++;
			continue;
		}
		if ( charsize == 2 ){
			u=(unsigned short)getshort(s,0);
			c=convert_char(u);
			/* 			fprintf(stderr,"char=%02x %02x\n", *s, *(s+1)); */
		} else {
			if (source_charset) {
				u=(unsigned short)to_unicode(source_charset,(unsigned char)*s);
			}	else {
				u=(unsigned char)*s;
			}	 
			c=convert_char(u);
		}
		if (c != NULL) {
			l+=strlen(c);
		}
		while (l>len) {
			len+=16;
			dest=realloc(dest,len+1);
			d=dest+l;
		}
		strcpy(d,c);
		if (c != NULL) {
			d+=strlen(c);
		}      
	}
	*src=s+to_skip;
	return dest;
}
/*
 * Extracts floating point value and formats it 
 */
char *format_double(char *rec,int offset,int format_code) {	
	static char buffer [128];
	union { char cc[8];
		double d;} dconv;
		char *d,*s;
		int i;
# ifdef WORDS_BIGENDIAN     
		for(s=rec+offset+8,d=dconv.cc,i=0;
				i<8;i++) *(d++)=*(--s);
# else       
				for(s=rec+offset,d=dconv.cc,i=0;
						i<8;i++) *(d++)=*(s++);
# endif     
						sprintf(buffer,"%.10g",dconv.d);
						return buffer;
}

/*
 * Formats integer value into static buffer
 */
char *format_int(int value,int format_code) {
	static char buffer[12];
	sprintf(buffer,"%i",value);
	return buffer;
}	
/*
 * Formats RK record
 */
char* format_rk(char *rec) {
	static char buffer [128];
	double value=0.0;
	int i;

	if ( *(rec) & 0x02 )
	{
		value=(double)(getlong(rec,0)>>2);
	}
	else { 
		union { char cc[8];
			double d;} dconv;
			char *d,*s;
			for(i=0;i<8;i++)
				dconv.cc[i]='\0';
# ifdef WORDS_BIGENDIAN     
			for(s=rec+4,d=dconv.cc,i=0;
					i<4;i++) *(d++)=*(--s);
					dconv.cc[0]=dconv.cc[0] & 0xfc;
# else       
					for(s=rec,d=dconv.cc+4,i=0;
							i<4;i++) *(d++)=*(s++);
							dconv.cc[4]=dconv.cc[4] & 0xfc;
							value=dconv.d;
# endif     
	}
	if ( *(rec) & 0x01 ) 
		value=value*0.01;
	sprintf(buffer,"%.10g",value);
	return buffer;
}


/* 
 * Converts excel date into time_t
 */
time_t float2date(double f) { 
	/* Hacked version. Excell stores date as floating point count of days
	 * since 1.1.1900. We are substracting value of 1.1.1970 and multiplying
	 * by 86400 thus getting seconds from the epoch
	 */
	return rint((f-25569.0)*86400); 
}

/*
 * Parses SST into array of strings
 */
void parse_sst(char *sstbuf,int bufsize) {
	int i; /* index into sst */
	unsigned char *curString; /* pointer into unparsed buffer*/
	unsigned char *barrier=(unsigned char *)sstbuf+bufsize; /*pointer to end of buffer*/
	unsigned char **parsedString;/*pointer into parsed array*/ 
	sstsize = getlong(sstbuf+4,0);
	sst=malloc(sstsize*sizeof(char *));
	if (sst == NULL) {
		perror("SST allocation error");
		exit(1);
	}
	for (i=0,parsedString=sst,curString=sstbuf+8;
			i<sstsize && curString<barrier; i++,parsedString++) {

		*parsedString = copy_unicode_string(&curString);
	}       
}	
