#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream.h>
#include <fstream.h>
#include "MyDB.h"

////////////////////////
// Project:           //
//    lab1            // 
// Filename:          //
//    MyDB.c          //
// Authors:           //  
//    Anthony Andrade //
//    Elizabeth Cash  //
// Date:              //
//    March 26, 2001  //
////////////////////////


#define CONFIG_FILE "db.cfg"

bool debug = false;

int getOffsetOfKey(char* dbName, char* key, int keySize,int recordSize, int keyOffset = 0, long prevOffset = 0)
     /*
      * input dbname: the name of the database to search
      *       key: key to search for
      *       keySize: size of the key
      *       recordSize: size of the complete record
      *       keyOffset: (optional) offset of key (used in record sort)
      *       prevOffset:(optional) can be used to find multiple occurances of the key
      *output the offset of the record dthat contains the key
      *notes  There is a problem with this function when searching for multiple keys.
      *       It will find the 1st key correctly, but not others.
      */
{
  FILE *fp;
  char lkey[keySize+1], fkey[keySize+1];
  lkey[0] = ' ';
  lkey[1]= '\0';
  long offset = 0;
  strcpy(fkey, key);
  fkey[keySize]='\0';
  lkey[keySize]='\0';
  if((fp= fopen(dbName,"r"))==NULL);
  if(prevOffset>0) fseek(fp, prevOffset*sizeof(char),SEEK_SET);  
  fseek(fp, keyOffset*sizeof(char),SEEK_CUR);
  while(!feof(fp))
    {
      int ret = fread(lkey, sizeof(char),keySize,fp);
      if(ret<keySize)
	{
	  fclose(fp);
	  return-1;
	}
      if(debug) cout << lkey << endl;
      offset+=keySize;

      if(0==strcmp(fkey,lkey))
	{
	  fclose(fp);
	  return (offset-(keySize)+prevOffset);
	
	}
      else
	{
	  int space = (recordSize-keySize)+1;
	  fseek(fp, space*sizeof(char),SEEK_CUR);
	  offset+=space;
	
	}
    }
  fclose(fp);
  return -1;
}

int schemaSize(char* dbName)
     /*
      *input: the name of the database
      *output: the size of a database record from that database
      *notes: this function returns the size of a record
      */
{
  int count=0;
  ifstream configFile;
  configFile.open(CONFIG_FILE);
  if(!configFile)return 0;
  char line[80], *lptr;
  do
    {
      configFile >> line;
      lptr = strtok(line, "&");
    }while(0!=strcmp(lptr,dbName)&&(lptr));
  configFile.close();
  //end of file
  if(!lptr) return -1;
  else
    {
      //found the dbName
      lptr = strtok(NULL, "&");
      while(lptr)
	{
	  count += atoi(lptr);
	  lptr = strtok(NULL, "&");
	}
      return count;
    }
}

int getSchema(char* dbName, char* schema)
     /*
      *input dbName: the name of the database
      *      schema: an array to hold the schema
      *output returns 0 if the function executes correctly and -1 otherwise
      *       the schema line from the db.cfg file is copied to schema(from input
      *notes: this function gets the schema line in db.cfg
      */
{
  ifstream configFile;
  configFile.open(CONFIG_FILE);
  if(!configFile) return -1;
  char line[80], *lptr, tempLine[80];
  do
    {
      configFile >> line;
      strcpy(tempLine,line);
      lptr = strtok(line, "&");
    }while(0!=strcmp(lptr,dbName)&&(lptr));
  configFile.close();
  //end of file
  if(!lptr) return -1;
  else
    {
      strcpy(schema,tempLine);
      return 0;
    }
}


int bookKeeping(char* line, char* dbName)
     /*
      *input line: line describing the schema of the database
      *      dbName: the name of the corresponding database
      *output returns 0 on success
      *notes:this function updates the lisst of known schemas stored in the db.cfg file
      */
{
  fstream configFile;
  char line2[80], *l;

  // make sure database does not already exist
  configFile.open(CONFIG_FILE, ios::in);
  if(!configFile) configFile.open(CONFIG_FILE, ios::out);
  else
    {
      while(!configFile.eof())
     	{
	  configFile >> line2;
	  l = strtok(line2,"&");
	  if(l != NULL) if(0==strcmp(l,dbName))return(CREATE_ERROR);
	  if(debug)cout <<" test for db " << l << endl;
	}
      configFile.close();
    }

  // write schema line to config file
  configFile.open(CONFIG_FILE, ios::app);
  if(!configFile) return CREATE_ERROR;
  // strcpy(line2,strcat("&",line));
  configFile << line << endl;
  configFile.close();
  return 0;
}
void pad(char * source, char* dest, int newSize)
     /*input source: the original string 
      *      dest: buffer were the string is to be copied to 
      *      newSize: new size of the string
      *output padded string is copied to dest;
      *notes: this function pads a string with spaces
      */
{
  int len = strlen(source);
  strcpy(dest, source);
  for(int x=len;x<newSize;x++)dest[x]=' ';
  dest[newSize]='\0';
}

int CreateDB(char* dbName, char* datafile)
     /*
      *input dbName: the name of the database to create
      *      dtatfile the file with info to popluate the database
      *output returns o on success, CREATE_ERROR otherwise
      *notes: this function creates a database and adds its schema to the db.cfg file.
      */
{
  int items[20], recordSize= 0, itemsCount = 0;
  char line[80], cfgline[80], lline[100], *lptr;

  strcpy(cfgline,dbName);
  strcat(cfgline, "&");

  ifstream inputFile;
  ofstream outputFile;

  // get schema line
  inputFile.open(datafile);
  if(!inputFile)return CREATE_ERROR;
  inputFile >> line;
  inputFile.close();

  outputFile.open(dbName, ios::trunc);
  outputFile.close();

  strcat(cfgline, line);
  if(bookKeeping(cfgline, dbName)) return CREATE_ERROR;

  inputFile.open(datafile);
  if(!inputFile)return CREATE_ERROR;
  inputFile >> line;

  lptr = line;
  // process first line
  lptr = strtok(line, "&");
  for(int x=0;(x<20)&&(lptr!=NULL);x++)
    {
      itemsCount++;
      items[x] = atoi(lptr);
      recordSize += atoi(lptr);
      lptr = strtok(NULL, "&");
    }
  if(debug)cout << "first line processed\n";

  while(!inputFile.eof())
    {
      inputFile.getline(line, 80, '\n');
      if(debug)cout << "recordSize="<<recordSize<<endl << "recordCount="<<itemsCount<<endl << "item[0] = " <<items[0]<<endl;
      if(strlen(line)>1)
	{	
	  char recordOut[recordSize], *l;
	  strcpy(recordOut,"");
	  l = line;
	  l = strtok(line, "&");
	  for(int x=0;(x<itemsCount)&&(l);x++)
	    {
	      char buffer2[items[x]+1];
	      pad(l, buffer2, items[x]);
	      strcat(recordOut, buffer2);
	      l = strtok(NULL, "&");
	    }
	  if(debug)cout <<"recordout =" << recordOut<<endl;
	  outputFile.open(dbName, ios::app);
	  outputFile << recordOut <<endl;
	  outputFile.close();
	}
    }
  return 0;
}

int PrintDB(char* dbName)
     /*
      * input dbName: name of database to be printed
      * output prints the database to the screen
      *        returns 0 on success, PRINT_ERROR otherwise
      */
{
  ifstream dbFile;
  int recordSize = schemaSize(dbName);
  if(recordSize==-1)return PRINT_ERROR;

  char schema[80], *sptr;
  int item[80], itemCount=0;
  int code = getSchema(dbName, schema), keyLength;
  if(code==-1)return PRINT_ERROR;

  sptr = schema;
  sptr = strtok(schema, "&");
  sptr = strtok(NULL, "&");
  for(int x=0;(x<80)&&(sptr!=NULL);x++)
    {
      itemCount++;
      item[x] = atoi(sptr);
      sptr = strtok(NULL, "&");
    }

  if(debug)cout << "recordSize="<<recordSize<<endl << "recordCount="<<itemCount<<endl << "item[0] = " <<item[0]<<endl;
  char line[recordSize+1];
  for(int x=0;x<(recordSize+1);x++)line[x]='#';
  line[recordSize]='\0';
  cout << line << endl;

  dbFile.open(dbName);
  while(!dbFile.eof())
    {
      dbFile.getline(line, 80,'\n');
      cout << line << endl;
    }
  dbFile.close();

  for(int x=0;x<(recordSize+1);x++)line[x]='#';
  line[recordSize]='\0';
  cout << line << endl;
  return 0;
}

int RecordScan(char* dbName, int attrNumber, char* value)
     /*
      *input dbName: name of database to work with
      *      attrNumber: number of attribute starting with 1
      *      value: value of attribute to search for
      *output 0 on success RECORD_SCAN_ERROR otherwise
      *notes: scans through records in the given database for and returns those records which have the attribute value specified.
      * this only works for single occurrences, for now; 
      */ 
{
  int recordSize = schemaSize(dbName);
  if(recordSize==-1)return RECORD_SCAN_ERROR;

  char schema[80], *sptr;
  int item[80], itemCount=0, recOffset =0, attrLength;
  int code = getSchema(dbName, schema), keyLength;
  if(code==-1)return RECORD_SCAN_ERROR;
  else
    {
      sptr = schema;
      sptr = strtok(schema,"&");
      sptr = strtok(NULL, "&");
      keyLength = atoi(sptr);
      for(int x=1;x<attrNumber;x++)
	{
	  recOffset += atoi(sptr);
	  sptr = strtok(NULL, "&");
	}
      attrLength=atoi(sptr);

      int offset = 0, prevOffset=0;
      while(offset!=-1)
	{
	  offset = getOffsetOfKey(dbName, value, strlen(value), recordSize, recOffset, prevOffset);
          prevOffset =offset+recordSize+1;
	  if(offset!=-1)
          {
	      if(debug)cout << offset<<endl;
	      FILE *fp;
	      char record[recordSize+1];
	      record[recordSize]='\0';
	      fp= fopen(dbName,"r+");
   	      fseek(fp, offset*sizeof(char),SEEK_SET);
	      fread(record, sizeof(char),recordSize,fp);
	      fclose(fp);
	      cout << record << endl;
	    }
	}
      return 0;
    }
}
int KeyScan(char*dbName, char* value)
     /*
      *input dbName: name of database
      *      value : value of key to search for
      *output returns 0 on success KEY_SCAN_ERROR otherwise
      *      prints the record with the key value to the screen
      *notes: searches for record with specified key value and prints the record
      */
{
  int recordSize = schemaSize(dbName), code;
  if(recordSize==-1)return KEY_SCAN_ERROR;

  int offset = getOffsetOfKey(dbName, value, strlen(value), recordSize);
  if(offset==-1)return KEY_SCAN_ERROR;
  else
    {
      FILE *fp;
      char record[recordSize+1];
      record[recordSize]='\0';
      fp= fopen(dbName,"r+");
      fseek(fp, offset*sizeof(char),SEEK_SET);
      fread(record, sizeof(char),recordSize,fp);
      //print out record
      cout << record << endl;
      fclose(fp);
      return 0;
    }
}

int InsertRecord(char* dbName, char* value)
     /*
      *input dbName: name of database
      *     value: record to insert
      *output 0 on success, else INSERT_ERROR
      *notes: function searches for a deleted record to insert over. 
      *    If none are available, it appends the end of file
      */
{
  int recordSize = schemaSize(dbName);
  if(recordSize==-1)return INSERT_ERROR;

  char schema[80], *sptr;
  int code = getSchema(dbName, schema), keyLength;
  if(code==-1)return INSERT_ERROR;
  else
    {
      sptr = schema;
      sptr = strtok(schema,"&");
      sptr = strtok(NULL, "&");
      keyLength = atoi(sptr);

      char temp[keyLength+1];
      for(int x =0;x<keyLength;x++)temp[x]='#';
      temp[keyLength]='\0';

      int offset = getOffsetOfKey(dbName, temp, keyLength, recordSize);

      FILE *fp;
      if(offset==-1)
	{
	  fp= fopen(dbName,"a+");
   	  fwrite(value, sizeof(char),recordSize,fp);
   	  fclose(fp);
   	  return 0;
	}
      else
	{
	  fp= fopen(dbName,"r+");
   	  fseek(fp, offset*sizeof(char),SEEK_SET);
   	  fwrite(value, sizeof(char),recordSize,fp);
   	  fclose(fp);
   	  return 0;
	}
    }
}

int DeleteRecord(char* dbName, char* keyValue)
/****************************************************************
	Function DeleteRecord()
	input	dbName: name of db file to work with
		keyValue:Value of String KeyValue
	output	return 0 on success, else modify error
	delete record with same key value as KeyValue in 
	dbname
******************************************************************/
{
  int recordSize = schemaSize(dbName), code;
  if(recordSize==-1)return DELETE_ERROR;

  int offset = getOffsetOfKey(dbName, keyValue, strlen(keyValue), recordSize);
  if(offset==-1)return DELETE_ERROR;
  else
    {
      // make tombstone
      char tombstone[recordSize];
      for(int x =0;x<recordSize;x++)tombstone[x] = '#';
    	
      FILE *fp;
      char record[recordSize+1];
      fp= fopen(dbName,"r+");
      fseek(fp, offset*sizeof(char),SEEK_SET);
      fwrite(tombstone, sizeof(char),recordSize,fp);
      fclose(fp);
      return 0;
    }
}
int ModifyRecord(char* dbName, char* keyValue, int attrNumber, char* newValue)
/***************************************************************
	Function ModifyRecord()
	input 	dbName:	Name of db file to work with
		KeyValue:Value of String KeyValue
		attrNumber: column
		newValue: Value to be inserted into 
			attrNumber column of dB	
	output	return 0 on success, else modify error
	Should change attrNumber column's Value to new 
	value , based on KeyValues value.
***************************************************************/
{
  int recordSize = schemaSize(dbName);
  if(recordSize==-1)return MODIFY_ERROR;

  char schema[80], *sptr;
  int code = getSchema(dbName, schema), keyLength, attrLength, recOffset=0;
  if(code==-1)return MODIFY_ERROR;
  else
    {
      sptr = schema;
      sptr = strtok(schema,"&");
      sptr = strtok(NULL, "&");
      keyLength = atoi(sptr);
      for(int x=1;x<attrNumber;x++)
	{
	  recOffset += atoi(sptr);
	  sptr = strtok(NULL, "&");
	}
      attrLength=atoi(sptr);

      int offset = getOffsetOfKey(dbName, keyValue, strlen(keyValue), recordSize);
      if(offset==-1)return MODIFY_ERROR;
      else
	{
	  FILE *fp;
	  char record[recordSize+1];
	  fp = fopen(dbName,"r+");
	  fseek(fp, offset*sizeof(char),SEEK_SET);
	  fread(record, sizeof(char),recordSize, fp);
	  for(int x= recOffset;x<(recOffset+attrLength);x++)
	    {
	      record[x]=newValue[(x-recOffset)];
	    }
	  rewind(fp);     	
	  fseek(fp, offset*sizeof(char),SEEK_SET);
	  fwrite(record, sizeof(char),recordSize,fp);
	  fclose(fp);
	  return 0;
	}
    }
}


void error(int rc)

/***************************************************************
	Function error()
	input	int rc:	error message number
	output	print to screen error message
	Should print errormessage associated with the error
	code rc and terminate the execution of the program
****************************************************************/
{
  if(rc==CREATE_ERROR)printf("error in creating the database\n");
  else if(rc==PRINT_ERROR)printf("error in printing the database\n");
  else if(rc==RECORD_SCAN_ERROR)printf("error in performing a record scan on the database\n");
  else if(rc==KEY_SCAN_ERROR)printf("error in performing a key scan on  the database\n");
  else if(rc==INSERT_ERROR)printf("insert error\n");
  else if(rc==DELETE_ERROR)printf("delete error\n");
  else if(rc==MODIFY_ERROR)printf("modifcation error\n");
  exit(rc);
}
