/*

  odlistcomp

  Program to compare overdue checklists.
  Generates list of new books and of books no longer on list.

  haleyjd 2/28/08

*/

#ifdef _MSC_VER
#pragma warning( disable : 4786 )
#endif

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

class ODBook
{
protected:
   int    circNum;
   string callNum;

public:
   ODBook() : circNum(0), callNum("") {}
   ODBook(int circ, string &call) { circNum = circ; callNum = call; }

   int  getCircNum() const { return circNum; }
   void setCircNum(int cn) { circNum = cn; }

   const string &getCallNum() const { return callNum; }
   void  setCallNum(string &str)    { callNum = str;  }

   bool operator <(const ODBook &b)
   {
      if(callNum < b.callNum)
         return true;
      else if(callNum == b.callNum)
         return (circNum < b.circNum);
      else 
         return false;
   }
  
   bool operator >(const ODBook &b)
   {
      if(callNum > b.callNum)
         return true;
      else if(callNum == b.callNum)
         return (circNum > b.circNum);
      else
         return false;
   }
   
   bool operator ==(const ODBook &b)
   {
      return (circNum == b.circNum && callNum == b.callNum);
   }
};

static vector<ODBook> oldCircNums;
static vector<ODBook> newCircNums;

static vector<ODBook> missingBooks;
static vector<ODBook> newBooks;

static string ReadCallNum(string &str)
{
   size_t tab = str.find_first_of('\t');

   if(tab == string::npos)
   {
      cout << "Critical Error: malformed input file" << endl;
      exit(1);
   }

   return str.substr(0, tab);
}

static int ReadCircNum(string &str)
{
   size_t tab = str.find_first_of('\t');

   if(tab != string::npos)
   {
      char num[33];
      size_t i;
      int j = 0;

      memset(num, 0, sizeof(num));

      for(i = tab + 1; i < str.length(); ++i, ++j)
      {
         try
         {
            if(str.at(i) >= '0' && str.at(i) <= '9')
            {
               num[j] = (char)(str.at(i));
            }
            else
               break;
         }
         catch(...)
         {
            cout << "Critical Error: read outside of string" << endl;
            exit(1);
         }
      }

      return atoi(num);
   }
   else
   {
      cout << "Critical Error: malformed input file" << endl;
      exit(1);
   }

   return 0;
}

static void ReadInput(ifstream &infile, vector<ODBook> &v)
{
   string str;
   char c;
   ODBook newBook;

   while(infile.read(&c, 1))
   {
      switch(c)
      {
      case '\n': // newline
         newBook.setCallNum(ReadCallNum(str));
         newBook.setCircNum(ReadCircNum(str));

         v.push_back(newBook);
         str.erase();
         break;
      default:
         str.append(1, c);
         break;
      }
   }
}

enum
{
   STATE_BEGIN,
   STATE_EQUAL,
   STATE_MISSING,
   STATE_NEW,
   STATE_FINISHED,
   STATE_IDONE,
   STATE_JDONE,
};

static void CompareLists(void)
{
   int i = 0, j = 0, oldsize, newsize;
   int state = STATE_BEGIN;

   oldsize = oldCircNums.size();
   newsize = newCircNums.size();

   do
   {
      if(i == oldsize && j == newsize) // both lists are done? finished.
         state = STATE_FINISHED;

      switch(state)
      {
      case STATE_BEGIN:
      case STATE_EQUAL:
         if(oldCircNums[i] == newCircNums[j]) // equal: skip this one
         {
            ++i;
            ++j;
            
            if(i == oldsize && j == newsize) // both lists are done? finished.
               state = STATE_FINISHED;
            else if(i == oldsize)            // only i is finished?
               state = STATE_IDONE;
            else if(j == newsize)            // only j is finished?
               state = STATE_JDONE;
            else                             // keep scanning
               state = STATE_EQUAL;
         }
         else if(oldCircNums[i] < newCircNums[j]) // i < j - book on i not in j
         {
            state = STATE_MISSING;
         }
         else // i > j - book on j not in i
         {
            state = STATE_NEW;
         }
         break;
      case STATE_MISSING:
         // old list contains books not on new list.
         // scan forward in old list until i >= j
         while(oldCircNums[i] < newCircNums[j] && i != oldsize)
         {
            missingBooks.push_back(oldCircNums[i]);
            ++i;
         }
         if(i == oldsize)
            state = STATE_IDONE; // old is empty, finish off new
         else
            state = STATE_EQUAL; // keep scanning
         break;
      case STATE_NEW:
         // new list contains books not on old list.
         // scan forward in new list until j >= i
         while(newCircNums[j] < oldCircNums[i] && j != newsize)
         {
            newBooks.push_back(newCircNums[j]);
            ++j;
         }
         if(j == newsize)
            state = STATE_JDONE; // new is empty, finish off old
         else
            state = STATE_EQUAL; // keep scanning
         break;
      case STATE_IDONE:
         // i is finished before j - remainder of books in j are new
         while(j < newsize)
         {
            newBooks.push_back(newCircNums[j]);
            ++j;
         }
         state = STATE_FINISHED; // now we're done
         break;
      case STATE_JDONE:
         // j is finished before i - remainder of books in i are missing
         while(i < oldsize)
         {
            missingBooks.push_back(oldCircNums[i]);
            ++i;
         }
         state = STATE_FINISHED; // now we're done
         break;
      default:
         break;
      }
   }
   while(state != STATE_FINISHED);
}

static void PrintODVector(vector<ODBook> &v)
{
   int i = 0;
   int size = v.size();

   while(i != size)
   {
      cout << setw(14) << setiosflags(ios::left) 
           << v[i].getCallNum().c_str() << v[i].getCircNum() << endl;
      ++i;
   }
}

static void PrintMissingBooks(void)
{
   cout << "Books missing from new list:" << endl;

   PrintODVector(missingBooks);
}

static void PrintNewBooks(void)
{
   cout << "\n\nNew books:" << endl;

   PrintODVector(newBooks);
}

//
// Main Program
//
int main(int argc, char *argv[])
{
   ifstream oldinput;
   ifstream newinput;

   const char *oldfn, *newfn;

   if(argc < 3)
   {
      cout << "Usage: odlistcomp <oldfile> <newfile>" << endl;
      return 0;
   }

   oldfn = argv[1];
   newfn = argv[2];

   oldinput.open(oldfn);
   newinput.open(newfn);

   if(!oldinput || oldinput.bad() || !newinput || newinput.bad())
   {
      cout << "Error: could not open one or more input files." << endl;
      return 1;
   }

   // read old input
   ReadInput(oldinput, oldCircNums);

   // read new input
   ReadInput(newinput, newCircNums);

   // done with files
   oldinput.close();
   newinput.close();

   // sort input
   sort(oldCircNums.begin(), oldCircNums.end());
   sort(newCircNums.begin(), newCircNums.end());

   // generate difference lists
   CompareLists();

   // print output
   PrintMissingBooks();
   PrintNewBooks();

   return 0;
}

// EOF