#include <stdio.h>
#include <time.h>
#include <sys/timeb.h>
#include <string.h>
#include "listdb.h"

/* used to gate debug
#define DEBUG
 */

#define DELIMITER ','
#define ESCAPE '\\'
#define FALSE 0
#define TRUE 1

/* Prototypes */
void dump(char *source, char *destination);
void create(char *filename, int private);
void readInput(char *filename);


/* Globals 'cuz I'm lazy */
DatabaseHeader header;
ListAppInfo appInfo;
PCRecord *entries[20000]; /* Max 20000 records? */
int records = 0; /* number of records used */

#ifdef DEBUG
dumpInternalRecord( PCRecord *entry )
{
  char *record, *ptr;
  
  record = entry->record;
  
  printf("//Internal Record\n");
  printf("Size: %d\n", entry->size );
  if( record[0] != 0  ) {
    ptr = record + record[0];
    printf("field1:[%s]\n", ptr );
  }
  if( record[1] != 0  ) {
    ptr = record + record[1];
    printf("field2:[%s]\n", ptr );
  }
  if( record[2] != 0  ) {
    ptr = record + record[2];
    printf("  note:[%s]\n", ptr );
  }
  printf("//\n");
  
}
#endif


int main(int argc, char **argv)
{
  
  if( argc != 4 ) {
    printf("Version 0.99\n");
    printf("Usage: %s -[p|c|x] <filename.pdb> <in/out filename>\n", argv[0]);
	printf("  -p => create private (non-beamable) database\n");
	printf("  -c => create database\n");
	printf("  -x => extract database\n");
    exit( -1 );
  }
  
  if( argv[1][0] != '-' ||
	  !(argv[1][1] == 'p' || argv[1][1] == 'c' || argv[1][1] == 'x') ||
	   (strlen( argv[1] ) != 2))	{
    printf("ERROR: First parameter must be -p, -c or -x, not: %s", argv[1]);
    exit( -1 );
  }
  /* are we creating or extracting? */
  if(( argv[1][1] == 'c') || (argv[1][1] == 'p') ) {
    /* creating */
    readInput( argv[3] );
    create( argv[2], (argv[1][1] == 'p') );
    
  } else {
    /* extracting */
    dump( argv[2], argv[3] );
  }
  return 0; /* Exit success */
}


void dumpField( char *destination, char *source, int note )
{
  char *src, *dest;
  int i;
  
  src = source;
  dest = destination;
  for( i=0; i<strlen(source);i++) {
    if( *src == DELIMITER ) {
      /* insert escape character */
      *dest++ = ESCAPE;
    }
	if( !note ) {
		if( *src == '\n' ) {
			/* Simply don't copy the CR -- this is a
			limitation of the listdb converter as it
			doesn't handle CRs in the first two fields */
			*dest++ = '-'; /* replace CRs with dashes */
			src++;
		} else {
			/* copy the character */
			*dest++ = *src++;
		}
	} else {
		/* copy the character */
		*dest++ = *src++;
	}
  }
  *dest = '\0';
  if( note ) {
    /* Convert CR's into commas */
    dest = destination;
    for( i=0; i<strlen(destination);i++,dest++) {
      if( *dest == '\n' ) {
	*dest = DELIMITER;
      }
    }
  }
}


void dump(char *source, char *destination)
{
  FILE *database, *csv;
  RecordEntry recordEntry;
  char buffer[256+4096+1];
  char field[4096];
  int offset;
  int i;

  /* We can't yet dump from stdin, because dumping uses fseek -- Eric Backus <eric_backus@agilent.com> */
  if( strcmp( source, "-" ) == 0 ) {
	  printf("ERROR: extracting from stdin not yet supported\n");
	  exit( -1 );
  }
  
  database = fopen( source, "rb" );
  if( database == NULL ) {
    printf("ERROR: Unable to open database: %s\n", source );
    exit( -1 );
  }
  
  fseek( database, 0, SEEK_END );
  offset = ftell( database );
  fseek( database, 0, SEEK_SET );
  
  /* Allow "-" to mean dump to stdout -- Eric Backus <eric_backus@agilent.com> */
  if( strcmp( destination, "-" ) == 0 ) {
	  csv = stdout;
  } else {
	  csv = fopen( destination, "w" );
  }

  if( csv == NULL ) {
    printf("ERROR: Unable to open destination file: %s\n", destination );
    fclose( database );
    exit( -1 );
  }
  
  
  fread( &header, 1, sizeof( DatabaseHeader ), database );
  if( ferror( database ) ) {
    printf("ERROR: Error reading database header.\n");
    fclose( database );
    fclose( csv );
    exit( -1 );
  }
  
  SWAP_LONG( header.creator );
  if( ((header.creator & 0xff000000) >> 24) != 'L' ||
	  ((header.creator & 0xff0000) >> 16) != 'S' ||
	  ((header.creator & 0xff00) >> 8) != 'd' ||
	  (header.creator & 0xff) != 'b') {
    printf("ERROR: Invalid creator -- This is not a List Database: %s\n", source);
    fclose( database );
    fclose( csv );
    exit( -1 );
    
  }
  SWAP_LONG( header.type );
  if( ((header.type & 0xff000000) >> 24) != 'D' ||
	  ((header.type & 0xff0000) >> 16) != 'A' ||
	  ((header.type & 0xff00) >> 8) != 'T' ||
	  (header.type & 0xff) != 'A') {
    printf("ERROR: Invalid type -- This is not a List data file: %s\n", source);
    fclose( database );
    fclose( csv );
    exit( -1 );
    
  }
  
  /* Sanity checking */
  SWAP_LONG( header.creationDate );
#ifdef DEBUG
  {
    time_t time= header.creationDate ;
    struct tm tm;
    
    tm = *localtime( &time );
    printf("creation date: %04d-%02d-%2d %02d:%02d:%02d\n",
	   tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
	   tm.tm_hour, tm.tm_min, tm.tm_sec );
  }
#endif
  SWAP_WORD( header.recordList.numRecords ); 
#ifdef DEBUG
  printf("nextRecordList:%08x ", header.recordList.nextRecordList );
  printf("numRecords: %d ", header.recordList.numRecords );
  printf("numRecords: %x ", header.recordList.numRecords );
#endif
  for( i=0; i<header.recordList.numRecords; i++ ) {
    fread( &recordEntry, 1, sizeof( RecordEntry ), database );
    if( ferror( database ) ) {
      printf("ERROR: Error reading database record.\n");
      fclose( database );
      fclose( csv );
      exit( -1 );
    }
    SWAP_LONG( recordEntry.offset );
#ifdef DEBUG
    printf("offset:%08x ", recordEntry.offset );
    printf("%d\n", records );
#endif
    entries[records] = (PCRecord *)malloc( sizeof( PCRecord ));
    entries[records]->offset = recordEntry.offset;
    entries[records]->category = recordEntry.attributes & 0x0f;
    records++;
#ifdef DEBUG
    printf("attributes: %x ", recordEntry.attributes & 0xf0 );
    printf("category: %d ", recordEntry.attributes & 0x0f );
    printf("uniqueID: %d,%d,%d\n", recordEntry.uniqueID[0],
	   recordEntry.uniqueID[1], recordEntry.uniqueID[2]);
#endif
  }
  
  /* Now determine the overall .pdb file size.  With this we
     can calculate the size of the last record */
  
  for( i = records; i > 0; i-- ) {
    int newOffset;
    newOffset = entries[i - 1]->offset;
    entries[i - 1]->size = offset - entries[i-1]->offset;
    offset = newOffset;
  }
#ifdef DEBUG
  for( i = 0; i < records ; i++ )
    printf("#size %d\n", entries[i]->size );
#endif
  
  { short pad;
  fread( &pad, 1, 2, database ); /* ROOHACK: padding? */
  }
  
  fread( &appInfo, 1, sizeof( ListAppInfo ), database );
  if( ferror( database ) ) {
    printf("ERROR: Error reading database info.\n");
    fclose( database );
    fclose( csv );
    exit( -1 );
  }
  
#ifdef DEBUG	
  printf("renamed categories: %x\n", appInfo.renamedCategories );
  printf("display style: %x\n", appInfo.displayStyle );
  printf("last category: %x\n", appInfo.lastCategory );
  
  { int xx; printf("{");
  for( xx=0; xx<16; xx++ )  printf("%c",appInfo.customField1[xx]);
  printf("}\n");}
  
#endif
  
  fprintf( csv, "%s,%s,%s", header.name, appInfo.customField1,
	   appInfo.customField2 );
  
  /* Dump categories -- magic constant 0x0e? */
  /* The magic constant 0x0e is wrong.. just use 16 */
#ifdef DEBUG
  printf("appInfo.lastUniqID: %d\n", appInfo.lastUniqID);
  printf("appInfo.lastUniqID - 0x0e: %d\n", appInfo.lastUniqID - 0x0e);
#endif

  for( i=1; i < 16; i++ ) {
    fprintf(csv, ",%s", appInfo.categoryLabels[i]);
  }
/*
  if(( appInfo.lastUniqID - 0x0e ) == 1 ) {
    fprintf( csv, "," );
  }
*/
  
  fprintf( csv, "\n" );
  
  
  /* now dump records */
  for( i=0; i < records; i++ ) {
    fseek( database, entries[i]->offset, SEEK_SET ); /* HACK? */
    fread( buffer, 1, entries[i]->size, database );
    
    fprintf( csv, "%s,", appInfo.categoryLabels[entries[i]->category]);
    
    if( buffer[0] != 0 ) {
#ifdef DEBUG
      printf("field1:buffer[0]=%d\n", buffer[0]);
      { int xx; printf("buffer={");
      for( xx=0; xx<16; xx++ )  printf("%02x ",buffer[xx]);
      printf("}\n");}
#endif
      dumpField( field, &(buffer[(unsigned char)buffer[0]]), FALSE ); 
      fprintf( csv, "%s,", field );
    } else {
      fprintf( csv, "," );
    }
    if( buffer[1] != 0 ) {
      dumpField( field, &(buffer[(unsigned char)buffer[1]]), FALSE ); 
      fprintf( csv, "%s,", field );
    } else {
      fprintf( csv, "," );
    }		
    if( buffer[2] != 0 ) {
      dumpField( field, &(buffer[(unsigned char)buffer[2]]), TRUE ); 
      fprintf( csv, "%s", field );
    }
    fprintf( csv, "\n" );		
  }
  
  
  fclose( database );
  fclose( csv );
}

void create( char *filename, int private )
{
  FILE *database;
  int offset;
  RecordEntry recordEntry;
  struct timeb timeBuffer;
  int i;
  
  /* Create the file */
  /* Allow "-" to mean write database to stdout -- Eric Backus <eric_backus@agilent.com> */
  if( strcmp( filename, "-" ) == 0 ) {
	  database = stdout;
  } else {
	  database = fopen( filename, "wb" );
  }

  if( database == NULL ) {
    printf("ERROR: Unable to create file: %s\n", filename );
    exit(-1);
  }
  
  /* Fill in and write the header */
  header.creator = ('L' << 24) | ('S' << 16) | ('d' << 8) | 'b';
  SWAP_LONG( header.creator );
  header.type = ('D' << 24) | ('A' << 16) | ('T' << 8) | 'A';
  SWAP_LONG( header.type );
  header.recordList.numRecords = records;
  SWAP_WORD( header.recordList.numRecords );
  
  /* Todays date */
  ftime( &timeBuffer );
  SWAP_LONG( timeBuffer.time );
  /* timeBuffer.time + 0x40000000; Apparently this is bogus */
  header.creationDate = timeBuffer.time;
  header.modificationDate = timeBuffer.time;
  header.lastBackupDate = timeBuffer.time;
  
  /* attributes */
  header.attributes = 0x0800; /* BACKUP bit set */

  if( private ) {
	  header.attributes = 0x4000; /* Copy Protect bit set */
  }
  
  /* Offset of the appInfoID from the start of the file */
  header.appInfoID = sizeof( DatabaseHeader ) +
    (records * sizeof(RecordEntry)) + 2 /* Pad */; 
  SWAP_LONG( header.appInfoID );
  
  fwrite( &header, 1, sizeof( DatabaseHeader ), database );
  
  /* Add the record entries */
  
  offset = sizeof( DatabaseHeader ) +
    (records * sizeof( RecordEntry)) +
    sizeof( ListAppInfo ) + 2 /* Pad */; 
  
  
  for( i = 0; i < records; i++ ) {
    short *uid;
    
    memset( &recordEntry, 0, sizeof( RecordEntry ));
    
    /* Offset of record */
    recordEntry.offset = offset;
    SWAP_LONG( recordEntry.offset );
    offset += entries[i]->size;
    /* The lo 16 bits of the attributes are the category # */
    recordEntry.attributes = entries[i]->category;
#ifdef DEBUG
    printf("cat: %d\n", recordEntry.attributes);
#endif
    uid = (short*)&(recordEntry.uniqueID[1]);
    *uid = i;
    SWAP_WORD( *uid );
    
    fwrite( &recordEntry, 1, sizeof( RecordEntry ), database );
  }
  
  appInfo.renamedCategories = 0x0e00;
  appInfo.lastCategory = 0xff; /* dmAllCategories */
  SWAP_WORD( appInfo.lastCategory );
  
  { short pad = 0x0080;
  fwrite( &pad, 1, 2, database ); /* ROOHACK: padding? */
  }
  
  fwrite( &appInfo, 1, sizeof( ListAppInfo ), database );
  
  /* Now do all the records we have */
  for( i = 0; i < records; i++ ) {
#ifdef DEBUG
    printf("[%d]\n", entries[i]->size);
#endif
    fwrite( entries[i]->record, 1, entries[i]->size, database );
  }
  
  
  fclose( database );
  
  
}


/* Return a pointer to the next delimiter character
   or NULL if there isn't one, skipping escape'd
   delimiters. */
char *fieldEnd(char *string) 
{
  char *ptr;
  char *result;
  char *escape;
  
  ptr = string;
  do {
    result = strchr( ptr, DELIMITER );
    escape = strchr( ptr, ESCAPE );
    ptr = result + 1;
  } while (escape == (result - 1));
  
  return( result );
}

/* Copy size bytes of the source into destination,
   when encountering an escape character skip it */
void copyField( char *destination, char *source, int size )
{
  char *src, *dest;
  int count, skip;
#ifdef DEBUG
  printf("<");
#endif
  skip = FALSE;
  src = source;
  dest = destination;
  for( count=0; count<size; count++ ) {
    if( skip ) {
      if( *src == DELIMITER ) {
#ifdef DEBUG
	printf("over-write\n");
#endif
	dest--;	 /* back up and over-write */
      }
      skip = FALSE;
    } 
    if( *src == ESCAPE ) {
#ifdef DEBUG
      printf("found esc\n");
#endif
      skip = TRUE;
    }
    /* actually copy the character now */
#ifdef DEBUG
    printf("%c", *src);
#endif
    *dest++=*src++;
  }
#ifdef DEBUG
  printf(">\n");
#endif
}

void readInput( char *filename )
{
  FILE *input;
  char buffer[8192]; /* max size of an input line */
  char *ptr, *buf;
  int index;
  char category[16];
  char field1[256];
  char field2[256];
  char note[4096];
  char *notePtr;
  
  memset( &header, 0, sizeof( DatabaseHeader ));
  memset( &appInfo, 0, sizeof( ListAppInfo ));
  
  /* set the display style */
  appInfo.displayStyle =0x80; /* force a resort, field1/field2 */
  
  /* Allow "-" to mean read from stdin -- Eric Backus <eric_backus@agilent.com> */
  if( strcmp( filename, "-" ) == 0 ) {
	  input = stdin;
  } else {
	  input = fopen( filename, "r" );
  }

  if( input == NULL ) {
    printf("ERROR: Unable to open file for input: %s\n", filename );
    exit( -1 );
  }

  fgets( buffer, 8192, input );

  /* The first field is the database name */
  ptr = fieldEnd( buffer );
  if( ptr == NULL ) {
    printf("ERROR: Invalid input file format -- no database name\n");
    fclose( input );
    exit( -1 );
  }
  if( (ptr - buffer) >= 32 ) {
    printf("ERROR: Database name too long \n");
    fclose( input );
    exit( -1 );
  }
  copyField( header.name, buffer, (ptr - buffer) );
#ifdef DEBUG
  printf("Name:[%s]\n", header.name );
#endif
  /*ROOHACK: not handled -- only a database name??!! (no field1/field2/cats?)*/
  /* The second field is the first custom field */
  buf = ptr + 1;
  ptr = fieldEnd( buf );
  if( ptr == NULL ) {
#ifdef DEBUG
    printf("XXX\n");
#endif
    ptr = &buf[strlen(buf) - 1];
  }
  if( (ptr - buf) >= 16 ) {
    printf("ERROR: Custom field 1 name too long\n");
    fclose( input );
    exit( -1 );		
  }
  copyField( appInfo.customField1, buf, (ptr - buf ));
#ifdef DEBUG
  printf("appInfo.customField1:[%s]\n", appInfo.customField1 );
#endif
  
  /* The third field is the second custom field */
  buf = ptr + 1;
  ptr = fieldEnd( buf );
  if( ptr == NULL ) {
#ifdef DEBUG
    printf("XXX\n");
#endif
    ptr = &buf[strlen(buf) - 1];
  }
  if( (ptr - buf) >= 16 ) {
    printf("ERROR: Custom field 2 name too long\n");
    fclose( input );
    exit( -1 );		
  }
  copyField( appInfo.customField2, buf, (ptr - buf ));
#ifdef DEBUG
  printf("appInfo.customField2:[%s]\n", appInfo.customField2 );
#endif

  /* Now read the categories.. */
  for( index=0; index < 16; index++ ) {
    appInfo.categoryUniqIDs[index] = index;
  }
  /* First category is Unfiled */
  strcpy( appInfo.categoryLabels[0], "Unfiled" );
  index = 1;
  while( ptr != NULL ) {
    buf = ptr + 1;
    ptr = fieldEnd( buf );
	if( ptr != NULL ) {
		if( (ptr - buf) >= 16 ) {
			printf("ERROR: Category name too long: %s\n", buf);
			fclose( input );
			exit( -1 );
		} else {
			/* Skip zero length categories */
			if((ptr - buf) > 0) {
				copyField( appInfo.categoryLabels[index], buf, (ptr - buf) );
#ifdef DEBUG
				printf("CAT[%s]\n", appInfo.categoryLabels[index]);
#endif
				appInfo.categoryUniqIDs[index] = 0x0f + index;
				index++;
			} 
		}
    } else {
      /* Last category */
      if( (strlen( buf ) - 1) >= 16 ) {
		  printf("ERROR: Category name too long %s\n", buf);
		  fclose( input );
		  exit( -1 );
	  } else {
		  /* Skip zero length categories */
		  if((strlen(buf) - 1) > 0) {
			  copyField( appInfo.categoryLabels[index], buf, (strlen(buf) - 1) );
#ifdef DEBUG
			  printf("CAT[%s]\n", appInfo.categoryLabels[index]);
#endif
		  } 
		  appInfo.categoryUniqIDs[index] = 0x0f + index;
		  appInfo.lastUniqID = 0x0f + index;
	  }
	}
    if( index > 16 ) {
      printf("ERROR: Too many categories (max 16): %d\n", index - 1);
      fclose( input );
      exit( -1 );
    }
  }
  
  /* Now start doing records */
  records = 0;
  while( fgets( buffer, 8192, input ) != 0 ) {

	/* Modification by Eric Backus, strip the trailing newline so it
       doesn't end up as part of the note.  This allows for empty
       notes. */
	  if (buffer[strlen(buffer) - 1] == '\n')
		  buffer[strlen(buffer) - 1] = '\0'; /* Strip off trailing newline */
	  
	/* Each line is a record */
    ptr = fieldEnd( buffer );
    if( ptr == NULL ) {
      printf("ERROR: Line:%d Malformed record: %s -- no category\n", records + 2, buffer );
      fclose( input );
      exit( -1 );
    }
	/* Skip zero length categories */
	if((ptr - buffer) == 0 ) {
		index = 0; /* Unfiled */
	} else {
		memset( category, 0, 16);
		copyField( category, buffer, (ptr - buffer) );
		/* locate the category and get its number */
		for( index=0; index < 16; index++ ) {
			if( strcmp( category, appInfo.categoryLabels[index] ) == 0 ) {
				break;
			}
		}
		if( index == 16 ) {
			printf("WARNING: Line:%d Invalid category: %s -- defaults to Unfiled\n", records + 2, category );
			index = 0; /* Unfiled */
		}
	}
    /* ROOHACK: no size check on field1/field2/note?!*/
    /* Now get field 1 */
    buf = ptr + 1;
    ptr = fieldEnd( buf );
    if( ptr == NULL ) {
      printf("ERROR: Line:%d Malformed record: %s -- no field1\n", records + 2, buffer );
      fclose( input );
      exit( -1 );
    }
    if( (ptr - buf) >= 120 ) {
      printf("ERROR: Line:%d Field1 too large: %s\n", records + 2, buffer );
      fclose( input );
      exit( -1 );
    }
    memset( field1, 0, 256);
    copyField( field1, buf, (ptr - buf) );
    
    /* And field 2 */
    buf = ptr + 1;
    ptr = fieldEnd( buf );
	if( ptr == NULL ) {
	  /* Modification by Eric Backus, don't require "," at end of line
	 with no note. */
		ptr = &buf[strlen(buf)]; /* The rest of the line is field2 */
#if 0
		printf("ERROR: Line:%d Malformed record: %s -- no field2", records + 2, buffer );
		fclose( input );
		exit( -1 );
#endif
	}
	if( (ptr - buf) >= 120 ) {
      printf("ERROR: Line:%d Field2 too large: %s\n", records + 2, buffer );
      fclose( input );
      exit( -1 );
    }
    memset( field2, 0, 256);
    copyField( field2, buf, (ptr - buf) );
    
    /* Last the note ROOHACK: handle extra ,'s as CR values..*/
    memset(note, 0, 4096);
    notePtr = note;
	
	/* Modification by Eric Backus, account for possible end of buffer
       from modification made above */
	if (*ptr == '\0')
		buf = ptr;	/* Don't overflow end of buffer */
	else
		buf = ptr + 1;

    while( strlen(buf) != 0) {
#if 1 /* new code from: Martin Mueller mm@lunetix.de */
      ptr = &buf[strlen(buf) - 1];
      if( *buf == ESCAPE ) {
	buf++;
	*notePtr = *buf;
      } else {
	if( *buf == DELIMITER ) {
	  *notePtr = '\n';
	} else {
	  *notePtr = *buf;
	}
      }
      buf++;
      notePtr++;
#else /* old bogus code (did not handle escaped characters ) */
      ptr = fieldEnd( buf );
      if( ptr == NULL ) {
	/* Assume the note is the rest of the buffer */
	ptr = &buf[strlen(buf) - 1];
      }
      copyField( notePtr, buf, (ptr - buf) );
      notePtr += (ptr - buf);
      if( *ptr == DELIMITER ) {
	*notePtr++ = '\n';
      } 
      buf = ptr + 1;
#endif
    }
#ifdef DEBUG
    printf("[%s][%s][%s]\n", field1, field2, note);
#endif
    
    /* Create an internal record */
    entries[records] = (PCRecord *)malloc( sizeof( PCRecord ));
    entries[records]->category = index;
    
    /* calculate the size */
    (entries[records])->size = 3; /* record header */
    (entries[records])->size += strlen( field1 );
    if( strlen( field1 ) > 0 ) (entries[records])->size++;
    (entries[records])->size += strlen( field2 );
    if( strlen( field2 ) > 0 ) (entries[records])->size++;
    (entries[records])->size += strlen( note );
    if( strlen( note ) > 0 ) (entries[records])->size++;
    
    (entries[records])->record = (char *)malloc( (entries[records])->size );
    memset( (entries[records])->record, 0, (entries[records])->size );
    
    ptr = &(entries[records])->record[3];
    if( strlen( field1 ) > 0 ) {
      (entries[records])->record[0] = ptr - (char*)(entries[records])->record;
      strcpy( ptr, field1 );
      ptr += strlen( field1 ) + 1;
    }
    if( strlen( field2 ) > 0 ) {
      (entries[records])->record[1] = ptr - (char*)(entries[records])->record;
      strcpy( ptr, field2 );
      ptr += strlen( field2 ) + 1;
    }
    if( strlen( note ) > 0 ) {
      (entries[records])->record[2] = ptr - (char*)(entries[records])->record;
      strcpy( ptr, note );
    }
#ifdef DEBUG
    dumpInternalRecord( entries[records] );
#endif
    
    records++;
  }
  
  
  fclose( input );
}








