#include <iostream.h>
#include <fstream.h>
#include <stdio.h>
#include <string.h>
#ifdef __GNUC__
#include <dirent.h>
#endif 
#include <time.h>
#include <ctype.h>

#include <iterator.h>
#include <vector.h>
#include <map.h>
#include <set.h>
#include <algorithm>
#include <functional>

#include <string>

#include "tabber.h"

#define PAT_EXTENSION ".pat"

typedef unsigned long Ref;
typedef vector<string> sList;
typedef map<Ref, string> RefMap;
typedef pair<Ref, string> RefMapEntry;

class code {
public:
	string m_sCref;
	string m_sCdate;	
};

typedef vector<code> CodeList;

class patient {
public:
	string m_sUid;
	string m_sLname;
	string m_sFname;
	string m_sRref;
	string m_sLref;
	string m_sCref;
	string m_sBdate;
	string m_sAdate;
	string m_sDdate;
	string m_sRefer;
	string m_sStatus;
	string m_sDref1;
	string m_sDref2;
	CodeList m_codes;
};

class CodeEntry {
public:
	string m_sRef;
	string m_sCode;
	string m_sNumber;
};

typedef vector<patient> PatList;
typedef map<string, string> DiagList;
typedef map<string, string> LocList;
typedef map<string, CodeEntry> CodeMap;

void break_out_files(sList &i_list, sList &o_pat, sList &o_log, sList &o_dmp);
void build_code_table(tabber &, CodeMap &);
void build_lookup_tables(sList &, PatList &, DiagList &, LocList &, CodeMap &);
void build_pat_table(tabber &, PatList &);
void build_strstr_table(tabber &, map<string, string> &);
static unsigned long days_in_month(long month, long year);
int lookup_rword(const char *cp);
sList make_file_list(string);
unsigned long myatol(const char *foo);
long num_to_date(unsigned long i_datenum, long &o_year, long &o_monp, long &o_mdayp);
string numstr_to_string(string &i_sRef);
void output_report(PatList &, DiagList &, LocList &, CodeMap &);

/* ---------------------------------------------------------------------- */
int main()
{
     sList files = make_file_list(string("./data"));
     sList pat, log, dmp;
     RefMap rvDiags;
     PatList vPatList;
     DiagList mDiagMap;
     LocList mLocMap;
     CodeMap mCodeMap;

	try {
	     break_out_files(files, pat, log, dmp);

     	/*
	       Now pat contains the deleted patient files, 
	           log contains the activity logs
	           dmp contains the data dump
     	*/

	     build_lookup_tables(dmp, vPatList, mDiagMap, mLocMap, mCodeMap);	     
	     cout << "Successful table build...";
	     
	     output_report(vPatList, mDiagMap, mLocMap, mCodeMap);
	     cout << "report dumped." << endl;
	} catch (char *str) {
		cerr << "Exception: " << str << endl;
	} catch (string &str) {
		cerr << "Exception string: " << str << endl;
	}
     return 0;
}

/* ---------------------------------------------------------------------- */
void 
output_report(PatList &vPatList, DiagList &mDiagMap, LocList &mLocMap, CodeMap &mCodeMap)
{
	for (PatList::iterator i = vPatList.begin(); i < vPatList.end(); ++i) {
		cout << i->m_sLname << endl;
		for (CodeList::iterator j = i->m_codes.begin(); j < i->m_codes.end(); ++j) {
			string &ref = j->m_sCref;
			CodeEntry &ceCode = mCodeMap[j->m_sCref];
			cout << "\t" << numstr_to_string(j->m_sCdate) 
			     << "\t" << ceCode.m_sCode << "(" << ceCode.m_sNumber << ")" << endl;
		}
	}
}

const long SECS_PER_DAY = 24 * 60 * 60;
const long DATE_CORRECTION_FACTOR = 365 * 4 + 2;

unsigned long
myatol(const char *foo)
{
     unsigned long ret = 0;
     while (isdigit(*foo)) {
	  ret = 10 * ret + (*foo++ - '0');
     }
     return ret;
}

string
numstr_to_string(string &i_sRef)
{
     char buf[40];
     unsigned long lDays = myatol(i_sRef.c_str());
     long lYear, lMon, lMday;

     num_to_date(lDays + DATE_CORRECTION_FACTOR, lYear, lMon, lMday);

     sprintf(buf, "%.2d/%.2d/%4.4d", lMon + 1, lMday, lYear);

     return string(buf);
}

/* ---------------------------------------------------------------------- */
char *rWords[] = {
	"<not defined>",
	"PATIENTS:",
	"DIAGNOSES:", 
	"LOCATIONS:",
	"LEVELS:",
	"CATEGORIES:",
	"CODES:",
	"PHRASES:"
};

int
lookup_rword(const char *cp)
{
	int i;
	for (i = 0; i < sizeof(rWords)/sizeof(*rWords); ++i) {
		if (!strncmp(rWords[i], cp, strlen(rWords[i]))) break;
	}
	return i;
}

void 
build_lookup_tables(sList &i_slFiles, PatList &vPatList, DiagList &mDiagMap, LocList &mLocMap, CodeMap &mCodeMap)
{
	tabber tDmp(i_slFiles[0].c_str());

	// This enum MUST match the above rWords array.
	enum e_state { e_none, e_pat, e_diags, e_locs, e_levels, e_cats, e_codes, e_phrases, e_bad };
	
	enum e_state state = e_none;
	while (!tDmp.eof()) {
		vector<string> fields;
		
		tDmp.next_line(fields);
		if (fields.size() == 0) continue;  // skip blanks
		
		if (fields[0].size()) {
			state = (enum e_state)lookup_rword(fields[0].c_str());
			if (state == e_bad) {
				string e("Unknown rword: ");
				e += fields[0];
				throw e;
			}
		}
		
		switch(state) {
		case e_none:
		case e_bad:
			throw "In bad state";
			break;
			
		case e_phrases:
			// These are assumed to be last in the dump, and we don't need
			// them in the report, so we quit.
			return;

		case e_levels:
		case e_cats:
			// This is lazy, I should just search for the next keyword.
			{
				map<string,string> tmp;
				build_strstr_table(tDmp, tmp);
			}
			break;
			
		case e_codes:
			build_code_table(tDmp, mCodeMap);
			break;

		case e_locs:
			build_strstr_table(tDmp, mLocMap);
			break;

		case e_diags:
			build_strstr_table(tDmp, mDiagMap);
			break;
			
		case e_pat:
			build_pat_table(tDmp, vPatList);
			break;
			
		default:
			throw "Not done yet";
			break;
		}
	}
}
 
/* ---------------------------------------------------------------------- */
void
build_code_table(tabber &i_tDmp, CodeMap &i_mCodeMap)
{
	while (!i_tDmp.eof()) {
		vector<string> diag;
		
		i_tDmp.next_line(diag);
		
		if (diag[0].length()) {
			// got a code, get out now while we can still say we made a mistake
			i_tDmp.reuse();
			break;
		}
		else {
			CodeEntry ceTmp;
			
			ceTmp.m_sRef = diag[1];
			ceTmp.m_sCode = diag[4];
			ceTmp.m_sNumber = diag[5];
			i_mCodeMap[diag[1]] = ceTmp;
		}
	}
}

/* ---------------------------------------------------------------------- */
void
build_strstr_table(tabber &i_tDmp, map<string, string> &i_Map)
{
	while (!i_tDmp.eof()) {
		vector<string> diag;
		
		i_tDmp.next_line(diag);
		
		if (diag[0].length()) {
			// got a code, get out now while we can still say we made a mistake
			i_tDmp.reuse();
			break;
		}
		else {
			i_Map[diag[1]] = diag[2];
		}
	}
}

/* ---------------------------------------------------------------------- */
void 
build_pat_table(tabber &i_tDmp, PatList &i_vPatList)
{
	patient pNewPat;
	CodeList *clCodes = &pNewPat.m_codes;
	bool bGotOne = false;
	
	while (!i_tDmp.eof()) {
		vector<string> fields;

		i_tDmp.next_line(fields);
		
		if (fields[0].length()) {
			// got a code word, get out now.
			i_tDmp.reuse();
			break;
		}
		else if (fields[1].length()) {
			// Got a patient, save the last one.
			if (bGotOne) {
				i_vPatList.push_back(pNewPat);
				pNewPat = patient();
				clCodes = &pNewPat.m_codes;
			}
			pNewPat.m_sUid = fields[1];
			pNewPat.m_sLname = fields[2];
			pNewPat.m_sFname = fields[3];
			pNewPat.m_sRref = fields[4];
			pNewPat.m_sLref = fields[5];
			pNewPat.m_sCref = fields[6];
			pNewPat.m_sBdate = fields[7];
			pNewPat.m_sAdate = fields[8];
			pNewPat.m_sDdate = fields[9];
			pNewPat.m_sRefer = fields[10];
			pNewPat.m_sStatus = fields[11];
			pNewPat.m_sDref1 = fields[12];
			pNewPat.m_sDref2 = fields[13];
			bGotOne = true;
		}
		else {
			// We're reading codes
			code thisCode;
			
			thisCode.m_sCref = fields[2];
			thisCode.m_sCdate = fields[3];
			
			if (fields[2].length() > 0) clCodes->push_back(thisCode);
		}
	}
}

/* ---------------------------------------------------------------------- */
class NotContains : public binary_function<const string, const string, bool> {
public:
	bool operator()(const string &lhs, const string &rhs) const {
		return lhs.find(rhs) == string::npos;
	};
};

void
break_out_files(sList &i_list, sList &i_pat, sList &i_log, sList &i_dmp)
{
     remove_copy_if(i_list.begin(), i_list.end(), back_inserter(i_pat),
     	bind2nd(NotContains(), ".pat"));
     remove_copy_if(i_list.begin(), i_list.end(), back_inserter(i_log),
     	bind2nd(NotContains(), ".log"));
     remove_copy_if(i_list.begin(), i_list.end(), back_inserter(i_dmp), 
     	bind2nd(NotContains(), ".dmp"));
}

/* ---------------------------------------------------------------------- */
/*
  make_file_list - build a vector of filenames, skipping . and ..
*/
sList
make_file_list(string i_sDir)
{
     sList ret;

#ifndef __MWERKS__
     DIR *pDir = opendir(i_sDir.c_str());

     struct dirent *pDirEnt = readdir(pDir);
     while (pDirEnt) {
	  if (strcmp(".", pDirEnt->d_name) && strcmp("..", pDirEnt->d_name)) {
	       ret.push_back(i_sDir + "/" + string(pDirEnt->d_name));
	  }
	  pDirEnt = readdir(pDir);
     }
	  
     closedir(pDir);
#else
#pragma unused(i_sDir)
	ret.push_back("symbol-990205133538.log");
	ret.push_back("symbol-990205133546.dmp");
	ret.push_back("symbol-999-99-99.pat");
#endif
     return ret;
}

/* ---------------------------------------------------------------------- */
#define LAST_VALID_DATE  73048
#define DAYS_IN_YEAR 365

long 
num_to_date(unsigned long i_datenum, long &o_lYear, long &o_lMon, long &o_lMday)
{
	long ret = 0;
	long date, month, year;
	long ldays, dim;
	long i;
	
	/* Subtract off 12/31/1899 */
	i_datenum -= 2;
	
	if (i_datenum < 0 || i_datenum > LAST_VALID_DATE) {
	     o_lYear = o_lMon = o_lMday = 0; 
	     return 1;
	}

	/* figure number of years */
	year = i_datenum / DAYS_IN_YEAR + 1900;
	
	/* Number of leap days in those years */
	ldays = (year - 1900) / 4;  
	
	/* Some even centuries aren't leap years */
	for (i = 2100; i <= year; i += 100) {
		if (i % 100 == 0 && !(i % 400 == 0)) {
			ldays--;
		}
	}
	
	/* Compute day of year */
	date = i_datenum % (DAYS_IN_YEAR);

	/* Don't count this year */
	if (year % 4 == 0 && !(year % 100 == 0 && !(year % 400 == 0))) {
	     ldays--;
	}
	
	date -= ldays;
	while (date < 0) {
		date += DAYS_IN_YEAR;
		year--;

		/* If this is a leap year, then we have to add one more day */
		if (year % 4 == 0 &&
		    !(year % 100 == 0 && !(year % 400 == 0))) {
		     date++;
		}
	}

	for (month = 0; month < 11; month++) {
		dim = days_in_month(month, year);
		if (date < dim) {
			break;
		}
		date -= dim;
	}

	o_lMday = date + 1;
	o_lMon = month;
	o_lYear = year;
	return ret;
}

static unsigned long
days_in_month(long month, long year)
{
	switch(month) {
	case 0: case 2: case 4: case 6: case 7: case 9:	case 11: default:
		return 31;
	
	case 3: case 5: case 8: case 10:
		return 30;
		
	case 1:
		if (year % 4 == 0 && !(year % 100 == 0 && !(year % 400 == 0))) {
			return 29;
		}
		return 28;
	}
	return 0;
}

