/* Implementation for MyDB.h */
/* Kevin Thorley and Chris Darmetko */

#include <stdio.h>
#include <string.h>
#include "MyDB.h"

#define HEADERLENGTH 20
#define HEADERDELIMITER "|"

void ParseHeader(char* format, int lengths[], int* numfields); 
void StripWhitespace(char* input);

/* Create a database with name given by dbName from the ascii 
 * file specified by datafile.  Resulting database is stored as 
 * a fixed length ascii file with a header of size HEADERLENGTH.
 * This header specifies the lengths of the fields in the database.
 */
int CreateDB(char* dbName, char* datafile) {

  FILE *inputfile;
  FILE *dbfile;

  char format[50];
  char outputformat[HEADERLENGTH];
  int numfields = 0;
  int fieldlengths[20];
  int maxlength = 0;
  int recordlength = 0;
  char ctemp;
  char strtemp[100];
  char *record;
  char **fields;
  int i, j, k;

  if ((inputfile = fopen(datafile, "r")) == NULL) {
    error(1);
    return -1;
  }

  if ((dbfile = fopen(dbName, "w")) == NULL) {
    error(1);
    return -1;
  }

  /* Read field lengths until a non '&' character is found */
  while(1) {
    fscanf(inputfile, "%d", &(fieldlengths[numfields]));
    numfields++;
    fscanf(inputfile, "%c", &ctemp);
    if (ctemp != '&')
      break;
  }

  /* Initialize fields array */
  fields = (char*)malloc(10);
  for (i = 0; i < numfields; i++) {
    fields[i] = (char*)malloc(fieldlengths[i] * sizeof(char));
  }

  /* Find record length and length of longest field */
  for (i = 0; i < numfields; i++) {
    recordlength += fieldlengths[i];
    if (fieldlengths[i] > maxlength)
      maxlength = fieldlengths[i];
  }

  record = (char*)malloc(recordlength * sizeof(char));

  /* Write field length info to a 100 character array */
  sprintf(outputformat, "%d", fieldlengths[0]);
  for (i = 1; i < numfields; i++) {
    sprintf(strtemp, "%s%d", HEADERDELIMITER, fieldlengths[i]);
    strcat(outputformat, strtemp);
  }

  /* Write outputformat to file... pad end with blanks */
  for (i = 0; i < strlen(outputformat); i++) {
    fputc(outputformat[i], dbfile);
  }
  for (j = i; j < HEADERLENGTH; j++) {
    fputc(' ', dbfile);
  }


  
  /* Read each record and buffer in fields[][] */
  while(fgets(record, recordlength, inputfile) != NULL) {
        
    fields[0] = strtok(record, "&");
    
    for (i = 1; i < numfields; i++) {
      fields[i] = strtok(NULL, "&");
    }
    
    /* Output each record to database file */
    /* First character output is delete flag (0 represents not deleted) */
    fputc('0', dbfile);

    /* Now write fixed length fields */
    for (i = 0; i < numfields; i++) {
      j = strlen(fields[i]);
      if (j > fieldlengths[i]) 
	j = fieldlengths[i];
      
      for (k = 0; k < j; k++) 
	fputc(fields[i][k], dbfile);
      
      for (k = j; k < fieldlengths[i]; k++)
	fputc(' ', dbfile);
      
    }
  }
  

  /* close files */
  fclose(dbfile);
  fclose(inputfile);
  
  return 0;
}

int PrintDB(char* dbName) {

  FILE* database;
  int fieldlengths[HEADERLENGTH];
  int maxfield = 0;
  int numfields;
  int recordlength = 0;
  char* inputbuffer;
  char* fieldbuffer;
  char delflag;
  char format[HEADERLENGTH];
  int i, j;
  int currpos;

  /* Open databse for reading */
  database = fopen(dbName, "r");
  
  /* Read fieldlength data from file */
  fread(format, HEADERLENGTH, 1, database);
  
  /* Parse header data */
  ParseHeader(format, fieldlengths, &numfields);
  
  /* Find longest field length and compute record length */
  for (i = 0; i < numfields; i++) {
    recordlength += fieldlengths[i];
    if (fieldlengths[i] > maxfield)
      maxfield = fieldlengths[i];
  }
  
  /* Initialize input and field buffers */
  inputbuffer = (char*)malloc(recordlength * sizeof(char));
  fieldbuffer = (char*)malloc(maxfield * sizeof(char));

  /* Go through each record and print it out */
  /* First, read delete flag of the record, then read record */
  while ((delflag = fgetc(database)) != EOF) {
    /* Skip record */
    if (delflag == '1') {
      fseek(database, recordlength, SEEK_CUR);
    }
    /* Process input */
    else {
      fread(inputbuffer, recordlength, 1, database);
      currpos = 0;
      for (i = 0; i < numfields; i++) {
	strncpy(fieldbuffer, &inputbuffer[currpos], fieldlengths[i]);
	/* If string is not terminated properly, do so */
	fieldbuffer[fieldlengths[i]] = '\0';
	
	/* Strip whitespace from fieldbuffer */
	StripWhitespace(fieldbuffer);
	
	printf("%s ", fieldbuffer);
	if (i < numfields)
	  currpos += fieldlengths[i];
      }
      printf("\n");
    }
  }
    
    
  /* Close database */
  fclose(database);
}

/* Scan database for a record whose specified attribute matches the
 * given value.  Print each record, and return the number of matches 
 * found. If attrNumber refers to a non-existent field, return 
 * INVALIDFIELDNUMBER 
 */
int RecordScan(char* dbName, int attrNumber, char* value ) {

  FILE* database;
  int fieldlengths[HEADERLENGTH];
  int numfields = 0;
  int maxfield = 0;
  int recordlength = 0;
  char* inputbuffer;
  char* fieldbuffer;
  char delflag;
  char format[HEADERLENGTH];
  int i, j;
  int currpos;
  int found = 0;

  /* Open databse for reading */
  database = fopen(dbName, "r");
  
  /* Read fieldlength data from file */
  fread(format, HEADERLENGTH, 1, database);

  /* Parse file header */
  ParseHeader(format, fieldlengths, &numfields);

  /* Make sure attrNumber refers to a valid field */
  if (attrNumber > numfields - 1)
    return INVALIDFIELDNUMBER;
  
  /* Find longest field length and compute record length */
  for (i = 0; i < numfields; i++) {
    recordlength += fieldlengths[i];
    if (fieldlengths[i] > maxfield)
      maxfield = fieldlengths[i];
  }
  
  /* Initialize input and field buffers */
  inputbuffer = (char*)malloc(recordlength * sizeof(char));
  fieldbuffer = (char*)malloc(maxfield * sizeof(char));

  /* Go through each record and search for attribute */
  /* First, read delete flag of the record, then read record */
  while ((delflag = fgetc(database)) != EOF) {
    /* Skip record */
    if (delflag == '1') {
      fseek(database, recordlength, SEEK_CUR);
    }
    /* Process input */
    else {
      fread(inputbuffer, recordlength, 1, database);
      
      /* Set read to correct position */
      currpos = 0;
      for (i = 0; i < attrNumber; i++)
	currpos += fieldlengths[i]; 
      
      /* Get attribute and format properly */
      strncpy(fieldbuffer, &inputbuffer[currpos], fieldlengths[i]);
      fieldbuffer[fieldlengths[i]] = '\0';
      StripWhitespace(fieldbuffer);
      
      /* Compare fieldbuffer to value and print record if the same */
      if (strcmp(fieldbuffer, value) == 0) {
	found++;
	currpos = 0;
	for (i = 0; i < numfields; i++) {
	  /* Get attribute and format properly */
	  strncpy(fieldbuffer, &inputbuffer[currpos], fieldlengths[i]);
	  fieldbuffer[fieldlengths[i]] = '\0';
	  StripWhitespace(fieldbuffer);
	
	  printf("%s ", fieldbuffer);
	  if (i < numfields)
	    currpos += fieldlengths[i];
	}
	printf("\n");
      }
    }
  }
  
  return found;
} 

/* Scan database for records whose key is equal to 
 * value.  Prints out matching records, and returns 
 * number of matches found
 */  
int KeyScan(char* dbName, char* value ) {
  int i;

  i = RecordScan(dbName, 0, value);
  return i;
}

/* Print error code associated with given int */
void error(int rc) {
  switch (rc) {
  case -1: 
    printf("Input file not found\n");
    break;
  case -2:
    printf("Invalid field number\n");
    break;
  case -3:
    printf("Duplicate Keys\n");
    break;
  default:
    printf("%d is not a valid MyDB error code\n");
  }
}


/* Parse database header to obtain field length info */
void ParseHeader(char* header, int lengths[], int* numfields) {
  
  int num;
  int fieldlengths[HEADERLENGTH / 2];
  char* strtemp;
  
  num = 0;
  strtemp = strtok(header, HEADERDELIMITER);
  lengths[num++] = atoi(strtemp);
  
  while((strtemp = strtok(NULL, HEADERDELIMITER)) != NULL) {
    lengths[num++] = atoi(strtemp);

  }
  
  *numfields = num;
    
}

/* Strip whitespace from the end of the given array of characters,
 * and set end of string to '\0'
 */
void StripWhitespace(char* input) {

  int len;
  int i;

  len = strlen(input);
  
  for (i = 0; i < len; i++) {
    if (input[i] == ' ' && (input[i+1] == ' ' || input[i+1] == '\0'))
      input[i] = '\0';
  }
}

int InsertRecord(char* dbName, char *value){

    FILE* database;
    int fieldlengths[HEADERLENGTH];
    int maxfield = 0;
    int numfields;
    int recordlength = 0;
    char delflag;
    char format[HEADERLENGTH];
    int i, j, k;
    int keyexist=0, keydeleted=0, record=0;
    char *inputbuffer;
    char *fldbuffer;
    int currpos;
    char** flds;
    
    /* Open databse for writing */
    database = fopen(dbName, "r+w");

    /* Read fieldlength data from file */
    fread(format, HEADERLENGTH, 1, database);

    /* Parse header data */
    ParseHeader(format, fieldlengths, &numfields);
    
    maxfield = fieldlengths[0];
    /* Compute record lentgh */
    for (i = 0; i < numfields; i++) 
      recordlength += fieldlengths[i];
        
    fldbuffer = (char*)malloc(fieldlengths[0] * sizeof(char));
    inputbuffer = (char*)malloc(recordlength * sizeof(char));
    /* Initialize fields array */
    flds = (char*)malloc(10);
    for (i = 0; i < numfields; i++) {
      flds[i] = (char*)malloc(fieldlengths[i] * sizeof(char));
    }                            
    
    flds[0] = strtok(value, "&");
    for (i = 1; i < numfields; i++)
      flds[i] = strtok(NULL, "&"); 
    
    /* Determine if previous record exists */
    keyexist = KeyScan(dbName, flds[0]);
   
    /* Return error if key exists already in database */
    if(keyexist!=0)
        return KEYINTEGRITYERROR;
 
    while ((delflag = fgetc(database)) != EOF) {
      /* Skip record */
      if (delflag != '1') {
	fseek(database, recordlength, SEEK_CUR);
      }
      /* Process input */
      else {
	fread(inputbuffer, recordlength, 1, database);
      
	/* Get attribute and format properly */
	strncpy(fldbuffer, &inputbuffer[currpos], fieldlengths[0]);
	fldbuffer[fieldlengths[0]] = '\0';
	StripWhitespace(fldbuffer);
	
	keydeleted=0;
	/* Compare fieldbuffer to value and print record if the same */
	if (strncmp(fldbuffer, flds[0], fieldlengths[0]) == 0) {
	  keydeleted=1;
	}
      }
      /* break out of loop if deleted record exists */
      if(keydeleted==1)
	break;
    }
  
    if(keydeleted==1){
      fseek(database, (recordlength+1)*-1, SEEK_CUR);
      /* Output each record to database file */
      /* First character output is delete flag (0 represents not deleted) */
      fputc('0', database);

    /* Now write fixed length fields */
    for (i = 0; i < numfields; i++) {
      j = strlen(flds[i]);
      if (j > fieldlengths[i]) 
	j = fieldlengths[i];
      
      for (k = 0; k < j; k++) 
	fputc(flds[i][k], database);
      
      for (k = j; k < fieldlengths[i]; k++)
	fputc(' ', database);
      
    }
    }
    else{
      fseek(database, 0, SEEK_END);
      
      /* Output each record to database file */
      /* First character output is delete flag (0 represents not deleted) */
    
      fputc('0', database);
      
      /* Now write fixed length fields */
    for (i = 0; i < numfields; i++) {
      j = strlen(flds[i]);
     if (j > fieldlengths[i]) 
	j = fieldlengths[i];
     
      for (k = 0; k < j; k++) 
	fputc(flds[i][k], database);
      
      for (k = j; k < fieldlengths[i]; k++)
	fputc(' ', database);
    }
    }
    free(flds);
    free(inputbuffer);
    free(fldbuffer);
   /* Close database */
    fclose(database); 
    return 0;
}

int DeleteRecord(char* dbName, char* keyValue)
{
    FILE* database;
    int fieldlengths[HEADERLENGTH];
    int numfields;
    int recordlength = 0;
    char delflag;
    char format[HEADERLENGTH];
    int i;
    int keyexist=0;
    char *inputbuffer;
    char *fieldbuffer;
    int currpos;
    char** fields;
    
    /* Open databse for reading and writing */ 
    database = fopen(dbName, "r+w");

    /* Read fieldlength data from file */ 
    fread(format, HEADERLENGTH, 1, database);

    /* Parse header data */
    ParseHeader(format, fieldlengths, &numfields);

    /* Compute record lentgh  */
    for (i = 0; i < numfields; i++) 
        recordlength += fieldlengths[i];

    fieldbuffer = (char*)malloc(fieldlengths[0] * sizeof(char));
    inputbuffer = (char*)malloc(recordlength * sizeof(char));
  
    /* Determine if previous record exists */
    keyexist = KeyScan(dbName, keyValue);
    
    /* Return if key does not exist already in database  */
    if(keyexist==0){
      free(fieldbuffer);
      free(inputbuffer);
      fclose(database);
      return NORECORDERROR;
    }
    
    while ((delflag = fgetc(database)) != EOF) {
    
      /* Skip record   */
      if (delflag != '0') {
	fseek(database, recordlength, SEEK_CUR);
      }
      /* Process input */
      else {
	fread(inputbuffer, recordlength, 1, database);

	/* Get attribute and format properly */
	strcpy(fieldbuffer, &inputbuffer[0]);
	fieldbuffer[fieldlengths[0]] = '\0';
	StripWhitespace(fieldbuffer);
	
	/* Compare fieldbuffer to key and change delete flag if the same */
	if (strncmp(fieldbuffer, keyValue, fieldlengths[0]) == 0) {
	  fseek(database, (recordlength+1)*-1, SEEK_CUR);
 	  fputc('1', database);
	  free(inputbuffer);
	  free(fieldbuffer);
	  fclose(database);
	  return 0;
	}
      }
    }
    
    free(fieldbuffer);
    free(inputbuffer);
    /* Close database  */
    fclose(database); 
    return 0; 

    }

int ModifyRecord(char* dbName, char* keyValue, int attrNumber, char* newValue)
{
  FILE* database;
  int fieldlengths[HEADERLENGTH];
  int numfields = 0;
  int maxfield = 0;
  int recordlength = 0;
  char* inputbuffer;
  char* fieldbuffer;
  char delflag;
  char format[HEADERLENGTH];
  int i, j;
  int currpos;
  int found = 0;

  /* Open databse for reading */
  database = fopen(dbName, "r+w");
  
  /* Read fieldlength data from file */
  fread(format, HEADERLENGTH, 1, database);
  
  /* Parse file header */
  ParseHeader(format, fieldlengths, &numfields);
  
  /* If attrNumber refers to key, make sure it doesn't cause an error */
  if(attrNumber==0){
    if(KeyScan(dbName, newValue)!=0){
      fclose(database);
      return KEYINTEGRITYERROR;
      }
  }
  /* Make sure attrNumber refers to a valid field */
  if (attrNumber > numfields - 1)
    return INVALIDFIELDNUMBER;
  
  /* Make sure key exists */
  if(KeyScan(dbName, keyValue)==0){
    fclose(database);
    return 0;
  }
    
  /* Find longest field length and compute record length */
  for (i = 0; i < numfields; i++) {
    recordlength += fieldlengths[i];
    if (fieldlengths[i] > maxfield)
      maxfield = fieldlengths[i];
  }
  
  /* Initialize input and field buffers */
  inputbuffer = (char*)malloc(recordlength * sizeof(char));
  fieldbuffer = (char*)malloc(maxfield * sizeof(char));

  /* Go through each record and search for attribute */
  /* First, read delete flag of the record, then read record */
  while ((delflag = fgetc(database)) != EOF) {
    /* Skip record */
    if (delflag == '1') {
      fseek(database, recordlength, SEEK_CUR);
    }
    /* Process input */
    else {
      fread(inputbuffer, recordlength, 1, database);
      
      /* Get attribute and format properly */
      strncpy(fieldbuffer, &inputbuffer[0], fieldlengths[0]);
      fieldbuffer[fieldlengths[0]] = '\0';
      StripWhitespace(fieldbuffer);
      
      /* Compare fieldbuffer to value and print record if the same */
      if (strcmp(fieldbuffer, keyValue) == 0) {
	fseek(database, (recordlength)*-1, SEEK_CUR);
	currpos=fieldlengths[0];
	for (i = 1; i < attrNumber; i++)
	  currpos += fieldlengths[i];
	fseek(database, currpos, SEEK_CUR);
	for(i=0; i<strlen(newValue); i++)
	  fputc(newValue[i], database);
	for(j=i; j<fieldlengths[attrNumber]; j++)
	  fputc(' ', database);
	free(inputbuffer);
	free(fieldbuffer);
	fclose(database);
	return 0;
      }
    }
  }
  free(inputbuffer);
  free(fieldbuffer);
  fclose(database);
  return 0;
}


























