/**************************************************/
/**** 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 MAX_CHARACTERS 16384
#define MAX_QUOTES 1024
char data[MAX_CHARACTERS]; //Storage for input data.

//dataEnd is the index into data[] of the first unused location.
int dataEnd = 0;

//dataQuote[i] is an index into data[] of the start of a quote.
int dataQuote [MAX_QUOTES];

//dataAuthor[i] is an index into data[] of the start of an author.
int dataAuthor [MAX_QUOTES];

//quoteNext[i] and quotePre[i] are the indexes into dataQuote[]
// of the alphabetically next and previous quote.
// Additionally, if i is an unused location, then quoteNext[i] is
// the index of the next unused location.
int quoteNext[MAX_QUOTES], quotePre[MAX_QUOTES];

//quoteStart is the index into dataQuote[] of the alphabetically
// first author/quote pair.
int quoteStart = -1;

//quoteFree is an index into dataQuote[], dataAuthor[],
// quoteNext[], and
int quoteFree = 0;

//==============================================================================
// cmdPrintQuoteList() 
//==============================================================================
cmdPrintQuoteList()
{ int i=quoteStart;
  printf("       Next   Previous  Author    Quote\n");
  printf("index  index   index     Data      Data            Author       Quote\n");

  while(i>=0)
  { int qIdx = dataQuote[i];
    int aIdx = dataAuthor[i];

    //This commented out version of the print follows the specs 
    //   However, a student showed me a better way which is not only shorter,
    //   It also avoids the bug of printing characters past the end of 
    //   quotes shorter than 5 characters.
    //printf("%5d %5d %7d %8d %8d    [%20s] [%c%c%c%c%c]\n", 
    //       i, quoteNext[i], quotePre[i], aIdx, qIdx, &data[aIdx], 
    //       data[qIdx], data[qIdx+1], data[qIdx+2],data[qIdx+3],data[qIdx+4]);
    printf("%5d %5d %7d %8d %8d    [%20.20s] [%5.5s]\n", 
           i, quoteNext[i], quotePre[i], aIdx, qIdx, &data[aIdx], &data[qIdx]);

    i = quoteNext[i];
  }
}

//==============================================================================
// 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 initLinkedList()
{ quoteFree = 0;
  quoteStart = -1; //there is nothing in the data list.

  int k;
  for (k=0; k<MAX_QUOTES; k++)
  { quoteNext[k] = k+1;
  }
  quoteNext[MAX_QUOTES-1] = -1;
  int dataEnd = 0;    
}

//==============================================================================
// cmdRemove() 
//   This us called only after the first 8 characters of the line 
//   were found to be ":remove:".
//   The start of the index number must, therefore, be at linePt+8.
//==============================================================================
void cmdRemove(char* linePt)
{ char* quoteNum = linePt+8;
  //atoi converts a string to an integer. It ignores leading white space and 
  //stops reading when it finds white space after the first digit.
  //Thus, the linefeed character at the end is no problem.
  int delCount = atoi(quoteNum); 
  if (DEBUG) printf("delCount=%d\n", delCount);
  if (delCount == 1) 
  { int tmp = quoteFree;
    quoteFree = quoteStart;
    quoteStart = quoteNext[quoteStart];
    quotePre[quoteStart] = -1;
    quoteNext[quoteFree] = tmp;
  }
  else
  { int delIdx = quoteStart;
    while (delCount > 1)
    { if (delIdx == -1) 
      { printf("Error: attempted to remove nonexistent record.\n");
        exit(EXIT_FAILURE);
      } 
      delIdx = quoteNext[delIdx];
      delCount--;
      if (delIdx == -1) 
      { printf("Error: attempted to remove nonexistent record.\n");
        exit(EXIT_FAILURE);
      } 
    }
    quoteNext[quotePre[delIdx]] = quoteNext[delIdx];
    quotePre[quoteNext[delIdx]] = quotePre[delIdx];
    quoteNext[delIdx] = quoteFree; 
    quoteFree = delIdx;  
  }
} 


//==============================================================================
// insertRecord
// Returns a pointer to the next empty location in data[].
// This is the location where the next data file record is read into.
//==============================================================================
char* insertRecord(char* linePt, char* colinPt, int lineLength)
{ int colinIdx = dataEnd + (colinPt - linePt);
  data[colinIdx] = '\0';
      
  int quoteNew = quoteFree;
  quoteFree = quoteNext[quoteFree];

  dataAuthor[quoteNew] = dataEnd;
  dataQuote[quoteNew] = colinIdx+1;
  dataEnd += lineLength;
    
  //replace trailing linefeed character with '\0'
  data[dataEnd-1] = '\0';
  linePt = &data[dataEnd];

  //Insert in ordered list
  if (quoteStart <0) 
  { //The list is currently empty.
    quoteStart = quoteNew;
    quoteNext[quoteNew] = -1;
    quotePre[quoteNew] = -1;
  }
  else 
  { //The list is not empty.
    //Find the correct place to insert by walking the list from
    //quoteStart until either a record is found that needs to 
    //be after quoteNew or the end of the list is reached.
    int i=quoteStart;
    int done = 0;
    while(!done)
    { char* a1 = &data[dataAuthor[quoteNew]];
      char* a2 = &data[dataAuthor[i]];
      int comp = strcmp(a1, a2);
      if (comp == 0)
      { //The authors are the same, so check the quotes.
        a1 = &data[dataQuote[quoteNew]];
        a2 = &data[dataQuote[i]];
        comp = strcmp(a1, a2);
      }
      if (comp <0)
      { done = 1;
        if (i==quoteStart)
        { quoteStart = quoteNew;
          quoteNext[quoteNew] = i;
          quotePre[quoteNew] = -1;
          quotePre[i] = quoteNew;
        }
        else
        { quoteNext[quotePre[i]] = quoteNew;
          quoteNext[quoteNew]  = i;
          quotePre[quoteNew]   = quotePre[i];
          quotePre[i]          = quoteNew;
        } 
      }
      else if (comp >= 0)
      { if (quoteNext[i] < 0)
        { //Reached the end of the list, so quoteNew must be come the new end. 
          quoteNext[i] = quoteNew;
          quoteNext[quoteNew] = -1;
          quotePre[quoteNew] = i;
          done = 1;
        }
      }
      i = quoteNext[i];
    }
  }
  return linePt;
}


//==============================================================================
// main 
//==============================================================================
int main(int argc, char *argv[])
{ if (argc != 2)
  { printf("Error: linkedList expects exactly 2 arguments.\n");
    return -1;
  }
      
  /* 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;
  }

  initLinkedList();
    
  char* linePt = data;
  int remainingBuffer = MAX_CHARACTERS;  

  //linePt is the address of the first empty location in data[].
  //Each data file record is read directly into its proper location in data[].
  //Thus, there is no buffer needed and the data read does not need to be
  //moved. 
  //If a :remove: record is read, it is to is placed in data[], but 
  //linePt is not increased. Therefore, the next read writes over it.
  //The remainingBuffer size is all of the unused space at the end of data[].
  while (fgets(linePt, remainingBuffer, inFile)) 
  { int lineLength = strlen(linePt);
    if (DEBUG) 
    { printf("read line: %s, idx=%d, remainingBuff before read=%d\n", 
           linePt, linePt-data, remainingBuffer);
    }
    if(lineLength < 1) break;

    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
      { printf("Error: Unrecognized Command.\n");
        exit(EXIT_FAILURE);
      }
    }
    else
    { //insertRecord returns a pointer to the next free space in data[].
      linePt = insertRecord(linePt, colinPt, lineLength);
      remainingBuffer = MAX_CHARACTERS - dataEnd;
    }

    if (DEBUG) cmdPrintQuoteList(); 

  }  
  fclose(inFile);
  cmdPrintQuoteList(); 
  return 0;
}
