/*----------------------------------------------------------------------*\
 | WKS file decoder.													|
 |																		|
 | Peter N. Schweitzer (U.S. Geological Survey, Reston, VA 22092)		|
\*----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <math.h>

/*----------------------------------------------------------------------*\
 | Code to handle byte-ordering problems.								|
\*----------------------------------------------------------------------*/

enum byte_order_t {
	LSBfirst,
	MSBfirst
	};

static enum byte_order_t get_byte_order (void) {
	unsigned short w = 255;
	unsigned char *s = (unsigned char *) &w;
	if (*s) return (LSBfirst);
	else return (MSBfirst);
	}
static enum byte_order_t byte_order = LSBfirst;

#ifndef ASM_SWAB

static void swab_word (void *p) {
	unsigned char *s,q;
	s = (unsigned char *) p;
	q = *(s+0); *(s+0) = *(s+1); *(s+1) = q;
	}

static void swab_dword (void *p) {
	unsigned char *s,q;
	s = (unsigned char *) p;
	q = *(s+0); *(s+0) = *(s+3); *(s+3) = q;
	q = *(s+1); *(s+1) = *(s+2); *(s+2) = q;
	}

static void swab_qword (void *p) {
	unsigned char *s,q;
	s = (unsigned char *)p;
	q = *(s+0); *(s+0) = *(s+7); *(s+7) = q;
	q = *(s+1); *(s+1) = *(s+6); *(s+6) = q;
	q = *(s+2); *(s+2) = *(s+5); *(s+5) = q;
	q = *(s+3); *(s+3) = *(s+4); *(s+4) = q;
	}

#else

extern void swab_word (void *p);
extern void swab_dword (void *p);
extern void swab_qword (void *p);

#endif

/*----------------------------------------------------------------------*\
 | Table of WKS opcodes, with some functions to retrieve information	|
 | about a record given its opcode.										|
\*----------------------------------------------------------------------*/

#define WKS_BOF			0x00
#define WKS_EOF			0x01
#define WKS_CALCMODE	0x02
#define WKS_CALCORDER	0x03
#define WKS_SPLIT		0x04
#define WKS_SYNC		0x05
#define WKS_RANGE		0x06
#define WKS_WINDOW1		0x07
#define WKS_COLW1		0x08
#define WKS_WINTWO		0x09
#define WKS_COLW2		0x0A
#define WKS_NAME		0x0B
#define WKS_BLANK		0x0C
#define WKS_INTEGER		0x0D
#define WKS_NUMBER		0x0E
#define WKS_LABEL		0x0F
#define WKS_FORMULA		0x10
#define WKS_TABLE		0x18
#define WKS_ORANGE		0x19
#define WKS_PRANGE		0x1A
#define WKS_SRANGE		0x1B
#define WKS_FRANGE		0x1C
#define WKS_KRANGE1		0x1D
#define WKS_HRANGE		0x20
#define WKS_KRANGE2		0x23
#define WKS_PROTEC		0x24
#define WKS_FOOTER		0x25
#define WKS_HEADER		0x26
#define WKS_SETUP		0x27
#define WKS_MARGINS		0x28
#define WKS_LABELFMT	0x29
#define WKS_TITLES		0x2A
#define WKS_GRAPH		0x2D
#define WKS_NGRAPH		0x2E
#define WKS_CALCCOUNT	0x2F
#define WKS_UNFORMATTED	0x30
#define WKS_CURSORW12	0x31
#define WKS_WINDOW		0x32
#define WKS_STRING		0x33
#define WKS_PASSWORD	0x37
#define WKS_LOCKED		0x38
#define WKS_QUERY		0x3C
#define WKS_QUERYNAME	0x3D
#define WKS_PRINT		0x3E
#define WKS_PRINTNAME	0x3F
#define WKS_GRAPH2		0x40
#define WKS_GRAPHNAME	0x41
#define WKS_ZOOM		0x42
#define WKS_SYMSPLIT	0x43
#define WKS_NSROWS		0x44
#define WKS_NSCOLS		0x45
#define WKS_RULER		0x46
#define WKS_NNAME		0x47
#define WKS_ACOMM		0x48
#define WKS_AMACRO		0x49
#define WKS_PARSE		0x4A

struct wks_opcode_t {
	short opcode;
	char *name;
	int max_length;
	char *description;
	};

static struct wks_opcode_t wks_opcode_table[] = {
	{WKS_BOF,			"BOF",			2,		"Beginning of file"},
	{WKS_EOF,			"EOF",			0,		"End of file"},
	{WKS_CALCMODE,		"CALCMODE",		1,		"Calculation mode"},
	{WKS_CALCORDER,		"CALCORDER",	1,		"Calculation order"},
	{WKS_SPLIT,			"SPLIT",		1,		"Split window type"},
	{WKS_SYNC,			"SYNC",			1,		"Split window sync"},
	{WKS_RANGE,			"RANGE",		8,		"Active worksheet range"},
	{WKS_WINDOW1,		"WINDOW1",		31,		"Window 1 record"},
	{WKS_COLW1,			"COLW1",		3,		"Column width, window 1"},
	{WKS_WINTWO,		"WINTWO",		31,		"Window 2 record"},
	{WKS_COLW2,			"COLW2",		3,		"Column width, window 2"},
	{WKS_NAME,			"NAME",			24,		"Named range"},
	{WKS_BLANK,			"BLANK",		5,		"Blank cell"},
	{WKS_INTEGER,		"INTEGER",		7,		"Integer number cell"},
	{WKS_NUMBER,		"NUMBER",		13,		"Floating point number"},
	{WKS_LABEL,			"LABEL",		245,	"Label cell"},
	{WKS_FORMULA,		"FORMULA",		2062,	"Formula cell"},
	{WKS_TABLE,			"TABLE",		25,		"Data table range"},
	{WKS_ORANGE,		"ORANGE",		25,		"Query range"},
	{WKS_PRANGE,		"PRANGE",		8,		"Print range"},
	{WKS_SRANGE,		"SRANGE",		8,		"Sort range"},
	{WKS_FRANGE,		"FRANGE",		8,		"Fill range"},
	{WKS_KRANGE1,		"KRANGE1",		9,		"Primary sort key range"},
	{WKS_HRANGE,		"HRANGE",		16,		"Distribution range"},
	{WKS_KRANGE2,		"KRANGE2",		9,		"Secondary sort key range"},
	{WKS_PROTEC,		"PROTEC",		1,		"Global protection"},
	{WKS_FOOTER,		"FOOTER",		242,	"Print footer"},
	{WKS_HEADER,		"HEADER",		242,	"Print header"},
	{WKS_SETUP,			"SETUP",		40,		"Print setup"},
	{WKS_MARGINS,		"MARGINS",		10,		"Print margins code"},
	{WKS_LABELFMT,		"LABELFMT",		1,		"Label alignment"},
	{WKS_TITLES,		"TITLES",		16,		"Print borders"},
	{WKS_GRAPH,			"GRAPH",		437,	"Current graph settings"},
	{WKS_NGRAPH,		"NGRAPH",		453,	"Named graph settings"},
	{WKS_CALCCOUNT,		"CALCCOUNT",	1,		"Iteration count"},
	{WKS_UNFORMATTED,	"UNFORMATTED",	1,		"Formatted/unformatted print"},
	{WKS_CURSORW12,		"CURSORW12",	1,		"Cursor location"},
	{WKS_WINDOW,		"WINDOW",		144,	"Symphony window settings"},
	{WKS_STRING,		"STRING",		32762,	"Value of string formula"},
	{WKS_PASSWORD,		"PASSWORD",		4,		"File lockout (CHKSUM)"},
	{WKS_LOCKED,		"LOCKED",		1,		"Lock flag"},
	{WKS_QUERY,			"QUERY",		127,	"Symphony query settings"},
	{WKS_QUERYNAME,		"QUERYNAME",	16,		"Query name"},
	{WKS_PRINT,			"PRINT",		679,	"Symphony print record"},
	{WKS_PRINTNAME,		"PRINTNAME",	16,		"Print record name"},
	{WKS_GRAPH2,		"GRAPH2",		499,	"Symphony graph record"},
	{WKS_GRAPHNAME,		"GRAPHNAME",	16,		"Graph record name"},
	{WKS_ZOOM,			"ZOOM",			9,		"Orig coordinates expanded window"},
	{WKS_SYMSPLIT,		"SYMSPLIT",		2,		"Nos. of split windows"},
	{WKS_NSROWS,		"NSROWS",		2,		"Nos. of screen rows"},
	{WKS_NSCOLS,		"NSCOLS",		2,		"Nos. of screen columns"},
	{WKS_RULER,			"RULER",		25,		"Named ruler range"},
	{WKS_NNAME,			"NNAME",		25,		"Named sheet range"},
	{WKS_ACOMM,			"ACOMM",		65,		"Autoload.comm code"},
	{WKS_AMACRO,		"AMACRO",		8,		"Autoexecute macro address"},
	{WKS_PARSE,			"PARSE",		16,		"Query parse"},
	{-1,				"(unknown)",	0,		"Unrecognized WKS opcode"}
	};

char *opcode_name (short opcode) {
	struct wks_opcode_t *t;
	for (t=wks_opcode_table; t->opcode != -1; t++)
		if (t->opcode == opcode) return (t->name);
	return (t->name);
	}

int opcode_max_length (short opcode) {
	struct wks_opcode_t *t;
	for (t=wks_opcode_table; t->opcode != -1; t++)
		if (t->opcode == opcode) return (t->max_length);
	return (t->max_length);
	}

char *opcode_description (short opcode) {
	struct wks_opcode_t *t;
	for (t=wks_opcode_table; t->opcode != -1; t++)
		if (t->opcode == opcode) return (t->description);
	return (t->description);
	}

/*----------------------------------------------------------------------*\
\*----------------------------------------------------------------------*/

static void hexdump (unsigned char *b, int n) {
	int i = 0;
	char ascii [20];
	char *a = ascii;

	if (n <= 0) return;
	do {
		if (i && !(i & 0xF)) {
			memset (ascii,0,20);
			fprintf (stdout,"\t# %s\n",ascii);
			a = ascii;
			}
		*a++ = (isprint(b[i]) ? b[i] : '.');
		fprintf (stdout,"%02X ",b[i++]);
		} while (i < n);
	i %= 16;
	while (i < 16) {
		fprintf (stdout,"   ");
		i++;
		}
	fprintf (stdout,"\t# %s\n",ascii);
	}

/*----------------------------------------------------------------------*\
 | Return the 2-byte integer whose address is given as the argument.	|
\*----------------------------------------------------------------------*/

static short short_at (unsigned char *b) {
	short v;
	unsigned char *d = (unsigned char *) &v;

	if (byte_order == MSBfirst) {
		*d++ = *(b+1);
		*d = *b;
		}
	else {
		*d++ = *b++;
		*d = *b;
		}
	return (v);
	}

/*----------------------------------------------------------------------*\
 | Return the 8-byte double whose address is given as the argument.		|
\*----------------------------------------------------------------------*/

static double double_at (unsigned char *b) {
	double v;
	unsigned char *d = (unsigned char *) &v;

	memcpy (d,b,8);
	if (byte_order == MSBfirst) swab_qword (d);
	return (v);
	}

/*----------------------------------------------------------------------*\
 | Get cell reference from column and row address.						|
\*----------------------------------------------------------------------*/

static char cell_id[12];

static char *cell (int c, int r) {
	char *s = cell_id;

	if (c < 26) *s++ = 'A' + c;
	else {
		*s++ = 'A' + (c / 26);
		*s++ = 'A' + (c % 26);
		}
	sprintf (s,"%d",r);
	return (cell_id);
	}

/*----------------------------------------------------------------------*\
 | Describe cell format byte											|
\*----------------------------------------------------------------------*/

static char *special_format_type[] = {
	"-/+",
	"general",
	"day-month-year",
	"day-month",
	"month-day",
	"text",
	"hidden",
	"date;hour-min-sec",
	"date;hour-min",
	"date;intnt'l1",
	"date;intnt'l2",
	"time;intnt'l1",
	"time;intnt'l2",
	"unused",
	"unused",
	"default"
	};

static char cell_format_string [64];

static char *cell_format (unsigned char b) {
	char *s = cell_format_string;

	if ((b & 0x80) == 0) {
		strcpy (s,"un");
		s += strlen ("un");
		}
	strcpy (s,"protected ");
    s += strlen ("protected ");
	switch (b & 0x70) {
		case 0x00:
			strcpy (s,"fixed ");
			break;
		case 0x10:
			strcpy (s,"scientific notation ");
			break;
		case 0x20:
			strcpy (s,"currency ");
			break;
		case 0x30:
			strcpy (s,"percent ");
			break;
		case 0x40:
			strcpy (s,"comma ");
			break;
		case 0x50:
			strcpy (s,"unused ");
			break;
		case 0x60:
			strcpy (s,"unused ");
			break;
		}
	s += strlen (s);
	if ((b & 0x70) == 0x70) {
		strcpy (s,special_format_type [b & 0x0F]);
		}
	else {
		*s++ = '0' + ((b & 0x0F) / 10);
		*s++ = '0' + ((b & 0x0F) % 10);
		strcpy (s," places");
		}
	return (cell_format_string);
	}

/*----------------------------------------------------------------------*\
\*----------------------------------------------------------------------*/

main (int argc, char *argv[]) {
	char *input_file;
	FILE *in;
	unsigned char *buffer,*end;
	unsigned char *b;
	long size,len;

	if (argc > 1) {
		input_file = argv[1];
		if (in = fopen (input_file,"rb")) {
			fseek (in,0L,SEEK_END);
			size = ftell (in);
			rewind (in);
			if (buffer = (unsigned char *) malloc (1+size)) {
				len = fread (buffer,1,size,in);
				end = buffer + len;
				*end = 0;

				byte_order = get_byte_order();

				/*------------------------------------------------------*\
				 | Read each record, interpreting if you can, dumping	|
				 | in hex if you cannot.								|
				\*------------------------------------------------------*/

				b = buffer;
				while (b < end) {
					short opcode,length,max_length;
					char *name,*description;
					unsigned short r,c,v;
					double value;
					unsigned char *d;
					char *s;

					opcode = short_at (b); b += 2;
					length = short_at (b); b += 2;

					name = opcode_name (opcode);
					max_length = opcode_max_length (opcode);
					description = opcode_description (opcode);

					printf ("%s (%d) ",opcode_name(opcode),length);
					if (length > max_length)
						printf ("(Warning: expected %d bytes or less) ",max_length);
					if (length <= 0) printf ("\n");
					else {
						d = b;
						switch (opcode) {
							case WKS_BOF:
								v = short_at (d); d += 2;
								printf ("0x%04X\n",v);
								break;
							case WKS_CALCMODE:
								if (*d == 0x00) printf ("Manual mode\n");
								if (*d == 0xFF) printf ("Automatic\n");
								break;
							case WKS_CALCORDER:
								if (*d == 0x00) printf ("natural\n");
								if (*d == 0x01) printf ("by column\n");
								if (*d == 0xFF) printf ("by row\n");
								break;
							case WKS_SPLIT:
								if (*d == 0x00) printf ("not split\n");
								if (*d == 0x01) printf ("vertical split\n");
								if (*d == 0xFF) printf ("horizontal split\n");
								break;
							case WKS_SYNC:
								if (*d == 0x00) printf ("not synchronized\n");
								if (*d == 0xFF) printf ("synchronized\n");
								break;
							case WKS_RANGE:
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("%s to ",cell(c,r));
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("%s\n",cell(c,r));
								break;
							case WKS_WINDOW1:
								printf ("\n");
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("  cursor at %s\n",cell(c,r));
								printf ("  format: %s\n",cell_format (*d));
								d++;
								printf ("  unused byte: 0x%02X\n",*d);
								d++;
								v = short_at (d); d += 2;
								printf ("  column width: %d\n",v);
								c = short_at (d); d += 2;
								printf ("  number of columns on screen: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  number of rows on screen: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  left column: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  top row: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  number of title columns: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  number of title rows: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  left title column: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  top title row: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  border width column: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  border width row: %d\n",r);
								v = short_at (d); d += 2;
								printf ("  window width: %d\n",v);
								printf ("  unused byte: 0x%02X\n",*d);
								break;
							case WKS_COLW1:
								c = short_at (d); d += 2;
								printf ("column %d has width %d\n",c,*d);
								break;
							case WKS_WINTWO:
								printf ("\n");
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("  cursor at %s\n",cell(c,r));
								printf ("  format: %s\n",cell_format (*d));
								d++;
								printf ("  unused byte: 0x%02X\n",*d);
								d++;
								v = short_at (d); d += 2;
								printf ("  column width: %d\n",v);
								c = short_at (d); d += 2;
								printf ("  number of columns on screen: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  number of rows on screen: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  left column: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  top row: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  number of title columns: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  number of title rows: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  left title column: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  top title row: %d\n",r);
								c = short_at (d); d += 2;
								printf ("  border width column: %d\n",c);
								r = short_at (d); d += 2;
								printf ("  border width row: %d\n",r);
								v = short_at (d); d += 2;
								printf ("  window width: %d\n",v);
								printf ("  unused byte: 0x%02X\n",*d);
								break;
							case WKS_COLW2:
								c = short_at (d); d += 2;
								printf ("column %d has width %d\n",c,*d);
								break;
							case WKS_NAME:
								printf ("\"%s\" for ",d);
								d += 16;
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("%s to ",cell(c,r));
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("%s\n",cell(c,r));
								break;
							case WKS_BLANK:
								s = cell_format (*d); d++;
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("at %s has format %s\n",cell(c,r),s);
								break;
							case WKS_INTEGER:
								s = cell_format (*d); d++;
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								v = short_at (d); d += 2;
								printf ("at %s has value %d and format %s\n",cell(c,r),v,s);
								break;
							case WKS_NUMBER:
								s = cell_format (*d); d++;
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								value = double_at (d); d += 8;
								printf ("at %s has value %lf and format %s\n",cell(c,r),value,s);
								hexdump ((unsigned char *)&value,8);
								break;
							case WKS_LABEL:
								s = cell_format (*d); d++;
								c = short_at (d); d += 2;
								r = short_at (d); d += 2;
								printf ("at %s has value \"%s\" and format %s\n",cell(c,r),d,s);
								break;
							case WKS_FORMULA:
							case WKS_TABLE:
							case WKS_ORANGE:
							case WKS_PRANGE:
							case WKS_SRANGE:
							case WKS_FRANGE:
							case WKS_KRANGE1:
							case WKS_HRANGE:
							case WKS_KRANGE2:
							case WKS_PROTEC:
							case WKS_FOOTER:
							case WKS_HEADER:
							case WKS_SETUP:
							case WKS_MARGINS:
							case WKS_LABELFMT:
							case WKS_TITLES:
							case WKS_GRAPH:
							case WKS_NGRAPH:
							case WKS_CALCCOUNT:
							case WKS_UNFORMATTED:
							case WKS_CURSORW12:
							case WKS_WINDOW:
							case WKS_STRING:
							case WKS_PASSWORD:
							case WKS_LOCKED:
							case WKS_QUERY:
							case WKS_QUERYNAME:
							case WKS_PRINT:
							case WKS_PRINTNAME:
							case WKS_GRAPH2:
							case WKS_GRAPHNAME:
							case WKS_ZOOM:
							case WKS_SYMSPLIT:
							case WKS_NSROWS:
							case WKS_NSCOLS:
							case WKS_RULER:
							case WKS_NNAME:
							case WKS_ACOMM:
							case WKS_AMACRO:
							case WKS_PARSE:
							default:
								hexdump (d,length);
								break;
							}
						}
					b += length;
					}

				}
			else
				fprintf (stderr,"Error: could not allocate %ld bytes for input file %s\n",size+1,input_file);
			fclose (in);
			}
		else {
			fprintf (stderr,"Error: could not open input file %s\n",input_file);
			exit (1);
			}
		}
	else {
		fprintf (stderr,"Usage: %s wks_file\n",argv[0]);
		exit (0);
		}
	}

/*----------------------------------------------------------------------*\
\*----------------------------------------------------------------------*/
