/**************************************************/
/**** Author:       Joel Castellanos           ****/
/**** Course:       CS-241                     ****/
/**** Updated:      02/25/2009                 ****/  
/**************************************************/

//***************************************************************
// This program reads a text data file of authors and quotes.
// The data is stored in an array of characters.
// A doubly linked list is used to index each author/quote pair
// in alphabetical order. 
//**************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DEBUG 0
#define HEAP_SIZE 196000
//#define MAX_CHARACTERS 57 
#define MAX_QUOTES 2400

//these structures assume sizeof(int)<=4 
struct listRecord
{ int author; //index into heap.data[] of this record's author
  int quote;  //index into heap.data[] of this record's quotation.
  int size;   //size of author and quote data including \0s.
  int next;   //if this record is in the start list, then next is
              //   the index into list.rec[] of alphebatacaly next record.
              //if this record in in the free list, then next is
              //   the index into list.rec[] of the next unused record.
  int previous; //index into list.rec[] of alphebatacaly previous record.
}; 

struct linkedListOfQuotations 
{ int start;  //index into list.rec[] of first record
  int free;   //index into list.rec[] of first unused record.
  struct listRecord rec[MAX_QUOTES]; 
} list;

struct heapRecord
{ int garbage; //index into heap.data[] of start of free memroy block.
  int next; //index into heap.garbage[] of next block of memory sorted smallest to largest. 
  int size;  //size of this record's free block. 
};

struct heapStructure
{ char data[HEAP_SIZE]; //Storage for input data.
  int start; //index into heap.garbage[] of first record of free memory blocks. 
  int free; //index into heap.garbage[] of first unused record.
  int biggestBlock;
  struct heapRecord rec[MAX_QUOTES];  
} heap;  //even though this version it is not yet a heap.
     


//==============================================================================
// Initialize doubly linked list 
//   quoteFree (the start of the free list) is set to 0 and all nodes are 
//   linked into the free list with quoteNext[k] = k+1; 
//==============================================================================
void initializeLinkedListOfRecords()
{ list.free = 0;
  list.start = -1; //there is nothing in the data list.

  int i;
  for (i=0; i<MAX_QUOTES; i++)
  { list.rec[i].next = i+1;
  }
  list.rec[MAX_QUOTES-1].next = -1;
}

void initializeHeap()
{ //Initially, the heap contains a single block of memory of size HEAP_SIZE.
  heap.start = 0; //Index into heap.rec[]
  heap.rec[heap.start].garbage = 0; //index into heap.data[]
  heap.rec[heap.start].size = HEAP_SIZE;   
  heap.rec[heap.start].next = -1;
  heap.biggestBlock = heap.start; 
 
  heap.free = 1;
  int i; 
  for (i=heap.free; i<MAX_QUOTES; i++)
  { heap.rec[i].next = i+1;
  }
  heap.rec[MAX_QUOTES-1].next = -1;
}


//==============================================================================
//  cmdPrintHeapList()
//==============================================================================
void cmdPrintHeapList()
{ int i=heap.start;
  printf("=========================== Heap Structure ==========================\n");
  printf("Index    Next   Garbage     Size \n");

  while(i>=0)
  { printf("%5d %5d %9d %9d\n", 
           i, heap.rec[i].next, heap.rec[i].garbage, heap.rec[i].size);
    i = heap.rec[i].next;
  }
  printf("\n");
}



void linkHeapRecord(int i)
{ int k=heap.start;
  int afterMe = -1;
  while (k > -1)
  { if (heap.rec[i].size < heap.rec[k].size) break;
    else if (heap.rec[i].size == heap.rec[k].size)
    { if (heap.rec[i].garbage < heap.rec[k].garbage) break; 
    }
    afterMe = k;
    k = heap.rec[k].next;
  }

  //printf("=========> i=%d, k=%d, afterMe=%d\n", i, k, afterMe);
  heap.rec[i].next = k;
  //if (k == -1) heap.biggestBlock = i;
 
  if (afterMe < 0) heap.start = i;
  else
  { heap.rec[afterMe].next = i;
  }
}


int createHeapRecord(int dataIdx, int size)
{ int newIdx = heap.free;
  heap.free = heap.rec[heap.free].next;
  heap.rec[newIdx].garbage = dataIdx;
  heap.rec[newIdx].size = size;
  linkHeapRecord(newIdx);
}



void unlinkHeapRecord(int i)
{ if (DEBUG) printf("unlinkHeapRecord(%d)\n", i);
  if (heap.start == i)
  { heap.start = heap.rec[i].next;
    return;
  }

  int lastk = heap.start;
  int k = heap.rec[heap.start].next;
  while (k >= 0)
  { if (k == i)
    { if (DEBUG) printf("unlinkheapRecord::lastk=%d, k=%d, i=%d\n", lastk, k, i);
      break;
    } 
    lastk = k;
    k = heap.rec[k].next;
  }
  heap.rec[lastk].next = heap.rec[i].next;
  //printf("lastk=%d, k=%d, i=%d, heap.rec[i].next=%d, heap.start=%d\n", lastk, k, i, heap.rec[i].next, heap.start);
  //if (DEBUG) cmdPrintHeapList();

}

int charCmpCaseInsensitive(char c1, char c2)
{ const int lowerCaseOffset = 'A' - 'a';
  if (c1 >= 'a' && c1 <= 'z') c1 += lowerCaseOffset;
  if (c2 >= 'a' && c2 <= 'z') c2 += lowerCaseOffset;
  return c1 == c2;
}

char *findSubstringCaseInsensitive(char *haystack, char *needle) 
{ int len = strlen(needle);
  int matchCount = 0;
  while (*haystack)
  { if ( charCmpCaseInsensitive( 
              *(needle+matchCount), *haystack))
    { matchCount++;
      if (matchCount == len) 
      { char *startPt = (haystack - len)+1;
        return startPt;
      }
    } 
    else {haystack -= matchCount; matchCount = 0;}
    haystack++;
  }
  return NULL;
}



void deleteHeapRecord(int i)
{ unlinkHeapRecord(i);
  heap.rec[i].next = heap.free;
  heap.free = i;
}

int getMemory(int size)
{ /*
i.	Save tddhe record’s garbage index into heap.data[] in a temporary variable.
ii.	Update the new garbage index by adding size to it.
iii.	Subtract size from the record’s size (heap.rec[i].size).
iv.	Return the original value of the record’s garbage index saved in the temporary variable.
b.	If the smallest block that is at least as large as size, is equal to size, then this function will need to:
i.	Remove the record from the heap.start linked list.
ii.	Add the record to the start of the heap.free linked list.
iii.	Return the record’s garbage index.
  
  */
  int lasti = -1;
  int i = heap.start;
  while (heap.rec[i].size < size)
  { lasti = i;
    i = heap.rec[i].next;
  }

  int freeMemoryIdx = heap.rec[i].garbage;
  if (heap.rec[i].size == size)
  { deleteHeapRecord(i);
    return freeMemoryIdx; 
  }
  
  heap.rec[i].garbage += size;
  heap.rec[i].size -= size;
  if (lasti < 0) return freeMemoryIdx; 
  
  if (heap.rec[i].size > heap.rec[lasti].size) return freeMemoryIdx;

  //need to move this heap record closer to heap.start
  unlinkHeapRecord(i);
  linkHeapRecord(i);
  return freeMemoryIdx;
}

void releaseMemory(int dataIdx, int size)
{ /*
a.	If the end of this block is adjacent to the start of a block already in the heap.start linked list, with index i, then this function must merge the two blocks into a single block: 
i.	Add size to heap.rec[i].size.
ii.	Update heap.rec[i].garbage to equal idx.
iii.	Since heap.rec[i] has increased its size, it might need to be moved farther down the heap.start list (which is sorted by size, and for blocks of the same size, sorted by garbage index).
b.	If the start of this block is adjacent to the end of a block already in the heap.start linked list, with index k, then this function must merge the two blocks into a single block: 
i.	Add size (heap.rec[i].size if it already merged in step a) to heap.rec[k].size.
ii.	Since heap.rec[k] has increased its size, it might need to be moved farther down the heap.start list (which is sorted by size, and for blocks of the same size, sorted by garbage index).
c.	If the new block is not adjacent to an existing block, then insert a new heap record in the heap.start linked list. The new record must be inserted in the correct sorted order in the list: from smallest block size to largest. For blocks of the same size, the block with the smallest garbage index must come first. Note: this is called memory fragmentation. 
d.	If the new (or merged) block is the largest, then update heap.biggestBlock.
  
  */
  int merged = 0;
  int mergedIdx;
  int startNextBlock = dataIdx + size;
  int i = heap.start;
  //printf("releaseMemory():: dataIdx=%d, size=%d\n", dataIdx, size);
  while (i>=0)
  { if (startNextBlock == heap.rec[i].garbage)
    { //merge
      merged = 1;
      mergedIdx = i;
      heap.rec[i].size += size;
      heap.rec[i].garbage = dataIdx;
      size = heap.rec[i].size;
      if (DEBUG) printf("Merge Heap record: idx=%d startNextBlock=%d\n", i, startNextBlock);
      break;
    }
    i = heap.rec[i].next;
  }

  int endLastBlock = dataIdx;
  i = heap.start;
  while (i>=0)
  { if (endLastBlock == heap.rec[i].garbage + heap.rec[i].size)
    { //merge
      if (merged)
      { deleteHeapRecord(mergedIdx);
      } 
      heap.rec[i].size += size;
      merged = 1;
      mergedIdx = i;
      if (DEBUG) printf("Merge Heap record: idx=%d endLastBlock=%d\n", i, endLastBlock);
      break;
    }
    i = heap.rec[i].next;
  }
 
  if (merged)
  { if (heap.rec[heap.start].next >= 0)
    { unlinkHeapRecord(mergedIdx);
      linkHeapRecord(mergedIdx);
    }
  }
  else
  { createHeapRecord(dataIdx, size);
  }


  //set biggestBlock.
  i=heap.start;
  while (heap.rec[i].next > -1) { i = heap.rec[i].next; }
  heap.biggestBlock = i;
  //printf("=========> heap.biggestBlock=%d, size=%d\n", i, heap.rec[i].size );
}

//==============================================================================
//  cmdPrintQuoteList()
//==============================================================================
cmdPrintQuoteList()
{ int i=list.start;
  printf("===================== Linked List of Quotations =====================\n");
  printf("Index  Next   Previous  Author    Quote            Author       Quote\n");

  while(i>=0)
  { int qIdx = list.rec[i].quote;
    int aIdx = list.rec[i].author;
    printf("%5d %5d %7d %8d %8d    [%20.20s] [%10.10s]\n", 
           i, list.rec[i].next, list.rec[i].previous, aIdx, qIdx, 
           &heap.data[aIdx], &heap.data[qIdx]);
    i = list.rec[i].next;
  }
    printf("\n");
}


//==============================================================================
//  cmdPrintData()
//==============================================================================
void cmdPrintData(char* linePt)
{ char* idxStr = linePt+12;
  int idx = atoi(idxStr);
  //printf("heap idx =[%s] %d\n", idxStr, idx);
  printf("============================ heap.data[] ============================\n");

  int i;
  for (i=0; i<=idx; i++) 
  { char c = heap.data[i];
    if (c == '\0') c='_'; 
    printf("%c",c); 
  }
  printf("\n\n");
}




//==============================================================================
//  cmdQueryAuthor()
//==============================================================================
void cmdQueryAuthor(char* linePt, int lineLength)
{ if (linePt[lineLength-1]==10) linePt[lineLength-1]='\0';
  char* authorSubstr = linePt+14;
  int i=list.start;
  printf("-----> Author Query: [%s]\n", authorSubstr);

  while(i>=0)
  { if (findSubstringCaseInsensitive(&heap.data[list.rec[i].author], authorSubstr))
    { printf("%s:%s\n\n", &heap.data[list.rec[i].author], 
                        &heap.data[list.rec[i].quote]);
    }
    i = list.rec[i].next;
  }
    printf("<----- End Author Query\n\n");
}


//==============================================================================
//  cmdQueryQuote()
//==============================================================================
void cmdQueryQuote(char* linePt, int lineLength)
{ if (linePt[lineLength-1]==10) linePt[lineLength-1]='\0';
  char* quoteSubstr = linePt+13;
  int i=list.start;
  printf("-----> Quote Query: [%s]\n", quoteSubstr);

  while(i>=0)
  { if (findSubstringCaseInsensitive(&heap.data[list.rec[i].quote],quoteSubstr))
    { printf("%s:%s\n\n", &heap.data[list.rec[i].author], 
                        &heap.data[list.rec[i].quote]);
    }
    i = list.rec[i].next;
  }
    printf("<----- End Quote Query\n\n");
}


//==============================================================================
// cmdRemove() 
//==============================================================================
void cmdRemove(char* linePt)
{ char* quoteNum = linePt+8;
  int delCount = atoi(quoteNum);
  if (DEBUG) printf("delCount=%d\n", delCount);
  if (delCount < 1) 
  { printf("Error: attempted to remove nonexistent record.\n");
    exit(EXIT_FAILURE);
  } 

  if (delCount == 1) 
  { int tmp = list.free;
    list.free = list.start;
    list.start = list.rec[list.start].next;
    list.rec[list.start].previous = -1;
    list.rec[list.free].next = tmp;
  }
  else
  { int delIdx = list.start;
    while (delCount > 1)
    { if (delIdx < 0) 
      { printf("Error: attempted to remove nonexistent record.\n");
        exit(EXIT_FAILURE);
      } 
      delIdx = list.rec[delIdx].next;
      delCount--;
      //printf("delIdx=%d,   delCount=%d\n", delIdx, delCount);
    }

    //It is ok to reference list.rec[delIdx].previous because if delIdx is the first record, 
    //then this line will not be reached.
    list.rec[list.rec[delIdx].previous].next = list.rec[delIdx].next;
    if (list.rec[delIdx].next >= 0) 
    { list.rec[list.rec[delIdx].next].previous = list.rec[delIdx].previous;
    }
    list.rec[delIdx].next = list.free; 
    list.free = delIdx; 
    if (DEBUG) printf("cmdRemove():: list.free = %d, list.rec[list.free].next = %d\n", list.free,  
              list.rec[list.free].next);
  }
  releaseMemory(list.rec[list.free].author, list.rec[list.free].size);
} 


//==============================================================================
// insertRecord 
//==============================================================================
void insertRecord(char* linePt, int colinIdx, int lineLength)
{ int newDataIdx = getMemory(lineLength); 
  int bufferDataIdx = linePt - &heap.data[0];
  if (newDataIdx != bufferDataIdx)
  { //if(DEBUG) printf("insertRecord() :: memcpy: %d <- %d (%d)\n", newDataIdx, bufferDataIdx, lineLength-1); 
    memcpy(&heap.data[newDataIdx], linePt, lineLength-1);
  } 

  //if(DEBUG) printf("insertRecord(): linePt=%s, colinIdx=%d, lineLength=%d\n", linePt, colinIdx, lineLength);

  heap.data[newDataIdx + colinIdx] = '\0';
  heap.data[newDataIdx + lineLength-1] = '\0';

  int quoteNew = list.free;
  if (quoteNew < 0)
  { printf("Error: list.free is empty\n"); 
    exit(0);
  }
  list.free = list.rec[list.free].next;

  list.rec[quoteNew].author = newDataIdx;
  list.rec[quoteNew].quote = newDataIdx+colinIdx+1;
  list.rec[quoteNew].size  = lineLength;

  if(DEBUG) 
  { printf("   ==>: author=[%s], quote=[%s]\n", &heap.data[list.rec[quoteNew].author] , 
                                          &heap.data[list.rec[quoteNew].quote]);
  }

    
  //Insert in ordered list
  if (list.start <0) 
  { //The list is currently empty.
    list.start = quoteNew;
    list.rec[quoteNew].next = -1;
    list.rec[quoteNew].previous = -1;
  }
  else 
  { //The list is not empty
    int i=list.start;
    int done = 0;
    while(!done)
    { char* a1 = &heap.data[list.rec[quoteNew].author];
      char* a2 = &heap.data[list.rec[i].author];
      int comp = strcmp(a1, a2);
      if(DEBUG) printf("a1=%s, a2=%s, comp=%d, i=%d\n", a1, a2, comp, i);
      if (comp == 0)
      { a1 = &heap.data[list.rec[quoteNew].quote];
        a2 = &heap.data[list.rec[i].quote];
        comp = strcmp(a1, a2);
        if(DEBUG) printf("a1=%s, a2=%s, comp=%d, i=%d\n", a1, a2, comp, i);
      }
      if (comp <0)
      { done = 1;
        if (i==list.start)
        { if (DEBUG) printf("Insert at front of list: newIdx=%d,  i=%d\n", quoteNew, i); 
          list.start = quoteNew;
          list.rec[quoteNew].next = i;
          list.rec[quoteNew].previous = -1;
          list.rec[i].previous = quoteNew;
        }
        else
        { if (DEBUG) printf("Insert in middle of list: newIdx=%d,  i=%d\n", quoteNew, i); 
          list.rec[list.rec[i].previous].next = quoteNew;
          list.rec[quoteNew].next  = i;
          list.rec[quoteNew].previous   = list.rec[i].previous;
          list.rec[i].previous          = quoteNew;
        } 
      }
      else if (comp >= 0)
      { if (list.rec[i].next < 0)
        { if (DEBUG) printf("Insert at end of list: newIdx=%d,  i=%d\n", quoteNew, i); 
          list.rec[i].next = quoteNew;
          list.rec[quoteNew].next = -1;
          list.rec[quoteNew].previous = i;
          done = 1;
        }
      }
      i = list.rec[i].next;
    }
  }
}


//==============================================================================
// main 
//==============================================================================
int main(int argc, char *argv[])
{ int echo = 0; 
  if(sizeof(int) < 4)
  { printf("Error: This program requires sizeof(int) >= 4\n");
    return -1;
  }

  if (argc != 2)
  { printf("Error: quoteDB expects exactly 2 arguments.\n");
    return -1;
  }
  
  /*
  printf("a a: %d\n", charCmpCaseInsensitive('a', 'a'));
  printf("r r: %d\n", charCmpCaseInsensitive('r', 'r'));
  printf("c a: %d\n", charCmpCaseInsensitive('c', 'a'));
  printf("K K: %d\n", charCmpCaseInsensitive('K', 'K'));
  printf("a A: %d\n", charCmpCaseInsensitive('a', 'A'));
  printf("Z z: %d\n", charCmpCaseInsensitive('Z', 'z'));
  return(0);
  
  findSubstringCaseInsensitive("aBc", "1ab3abc789");


  return(0);


*/
      
  /* open input data file */
  char* fileName = argv[1];
  FILE *inFile = fopen(fileName, "r");
  if (inFile == NULL) 
  { printf("Error: Cannot open file: %s\n",fileName);
    return 1;
  }

   
  initializeLinkedListOfRecords();
  initializeHeap();
 
  char* linePt = &heap.data[heap.rec[heap.biggestBlock].garbage];
  int remainingBuffer = heap.rec[heap.biggestBlock].size;  
  while (fgets(linePt, remainingBuffer, inFile)) 
  { int lineLength = strlen(linePt);
    //printf("read line: %s, bufferIdx=%d, remainingBuff=%d\n", linePt, 
    //                      heap.rec[heap.biggestBlock].garbage, remainingBuffer);
    if(lineLength < 1) break;

    if (echo) printf("echo >>>>[%s]\n", linePt);
    char* colinPt = strchr(linePt,':'); 
    if (colinPt == NULL) 
    { printf("Error: Read nonblank line without colin.\n");
      return 1;
    }


    if (colinPt == linePt)
    { if (strncmp(linePt, ":remove:", 8) == 0) cmdRemove(linePt);
      else if (strncmp(linePt, ":print:quotelist",16) == 0) cmdPrintQuoteList();
      else if (strncmp(linePt, ":print:data:",12) == 0) cmdPrintData(linePt);
      else if (strncmp(linePt, ":print:heaplist",15) == 0) cmdPrintHeapList();
      else if (strncmp(linePt, ":query:author:",14) == 0) cmdQueryAuthor(linePt, lineLength);
      else if (strncmp(linePt, ":query:quote:",13) == 0) cmdQueryQuote(linePt, lineLength);
      else if (strncmp(linePt, ":echo:", 6) == 0) echo=1;
      else
      { printf("Error: Unrecognized Command.\n");
        exit(EXIT_FAILURE);
      }
    }
    else
    { int colinIdx = (colinPt - linePt);
      insertRecord(linePt, colinIdx, lineLength);
    }
    
    linePt = &heap.data[heap.rec[heap.biggestBlock].garbage];
    remainingBuffer = heap.rec[heap.biggestBlock].size;  
  }  
  fclose(inFile);
  return 0;
}
