#include <stdio.h>
#include <stream.h>
#include <fstream.h>
#include <strstream.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "ObjProGen/mkstr.h"
#include "ObjProDSP/portable.h"
#include "ObjProGen/cpyrght_exe.h"

ostream * val_out ;

int verb = 0 ;

class ValFile {
	ifstream file ;
	int line ;
	const max_line;
	char * buf ;
	void check_max(int i, int max_parts);
public:
	ValFile(const char * nm);
	const char * const name ;
	void error(const char * msg);
	char * remove_quote(char * file ,const char * what);
	void remove_last(char * file ,const char * what, char c);
	void remove_to(char * file ,const char * what, char c);
	char ** get_parts(int max) ;
	char ** split(int max_parts);
};

ValFile::ValFile(const char * nm):name(nm),file(nm),line(0),max_line(8192),
	buf(0)
{
	if (!file.good()) {
		cerr << "Cannot read file `" << name << "'.\n" ;
		*val_out << "Cannot read file `" << name << "'.\n" ;
		exit(1) ;
	}
	buf = new char[max_line] ;
}

void ValFile::error(const char * msg)
{
	cerr << "Error: " << msg << " at line " << line << " in\n`" <<
		name << "'.\n" ;
	*val_out << "Error: " << msg << " at line " << line << " in\n`" <<
		name << "'.\n" ;
	exit(1);
}

void ValFile::check_max(int i, int max_parts)
{
	char buf[32];
	sprintf(buf,"%d",max_parts);
	if (i >= max_parts) error(Concatenate("more than ", buf,
		" words in line"));
}

char ** ValFile::get_parts(int max_count)
{
	static const char * end_line = "Tests completed at" ; 
	static int length = 0 ;
	if (!length) length = strlen(end_line);
	if (!buf) return 0 ;
	while (file.good()) {
		buf[0] ='\0' ;
		file.getline(buf,max_line);
		line++ ;
		if (!strncmp(buf,end_line,length)) {
			if (verb) *val_out << "Test from `" << name  << "' completed normally.\n" ;
			delete buf ;
			buf = 0 ;
			return 0 ;
		}
		if (buf[0]) {
			if (strlen(buf)==max_line-1) {
				char xbuf[32] ;
				sprintf(xbuf,"%d",max_line-1);
				error(Concatenate("line longer then ", xbuf, " characters")) ;
			}
	 		return split(max_count);
		}
	}
	*val_out << "***Test from `" << name  << "' did not complete normally.\n" ;
	return 0 ;
}

char ** ValFile::split(int max_parts)
{
	char ** parts = new char *[max_parts] ;
	int i = 0 ;
	char * tok = strtok(buf," ");
	while (tok) {
		check_max(i,max_parts);
		parts[i++] = Concatenate(tok);
		tok = strtok(0," ");
	}
	return parts ;
}

struct ValEntry ;

class ValTest {
	const char * test_name ;
	const char * dir_name ;
	ValEntry ** entries ;
	static const max_parts ;
	int the_comparisons ;
	int the_errors ;
	int the_differences ;
	int count ;
	int min_file_comparisons ;
	int null_file_tests;
public:
	ValTest(char ** parts, char ** new_parts,ValFile& f);
	const char * test() const {return test_name;}
	int size() const {return count;}
	int compare(ValTest& prev);
	int number_comparisons() const {return the_comparisons;}
	int number_errors() const {return the_errors;}
	int differences() const {return the_differences;}
	int get_min_file_comparisons() const {return min_file_comparisons;}
	int get_null_file_tests() const {return null_file_tests;}
};

const int ValTest::max_parts = 30 ;

void ValFile::remove_to(char * file ,const char * what, char c)
{
	char dum[2] ;
	dum[0]=c ;
	dum[1]='\0' ;

	for (char * pt = file; *pt ; pt++) if (*pt == 'c') break ;
	
	if (!*pt) error(Concatenate(Concatenate("missing `",dum,
		"' after (to) ",what), " in `",file,"'"));
	*pt = '\0' ;
}

void ValFile::remove_last(char * file ,const char * what, char c)
{
	char dum[2] ;
	dum[0]=c ;
	dum[1]='\0' ;

	int len = strlen(file);
	if (file[len-1] != c) error(Concatenate(Concatenate("missing `",dum,
		"' after ",what), " in `",file,"'"));
	file[len-1] = '\0' ;
}

char * ValFile::remove_quote(char * file ,const char * what)
{
	if (file[0] != '`') error(Concatenate("missing first quote in ",what));
	int len = strlen(file);
	if (file[len-1] != '\'') error(Concatenate("missing last quote in ",what));
	file[len-1] = '\0' ;
	return file + 1 ;
}


struct ValEntry {
	const char * node_name ;
	const char * file_name ;
	int32 comparisons ;
	int32 errors ;
	double tolerance ;
	static const max_parts ;
	ValEntry(char ** parts, ValFile &f);
	const char * test() const { return file_name ;}
	int compare(ValEntry& pref);
	void display();
};
const int ValEntry::max_parts = 30 ;

void ValEntry::display()
{
	*val_out << "    Node_name: " << node_name << ", file_name " << file_name << "\n";
	*val_out << "    " << comparisons << " comparisons with " << errors << " errors at " <<
		tolerance << " tolerance \n" ;
}

int ValEntry::compare(ValEntry& prev)
{
	int err = 0 ;
	if (strcmp(node_name,prev.node_name)) err++  ;
	if (strcmp(file_name,prev.file_name)) err++ ;
	if (comparisons != prev.comparisons) err++;
	if (errors != prev.errors) err++ ;
	if (tolerance != prev.tolerance) err++ ;
	if (err) {
		*val_out << "    **Test descriptios were different in " << err << " field" ;
		if (err > 1) *val_out << "s"  ;
		*val_out << "\n    Current:\n" ;
		display();
		*val_out << "    Previous;\n" ;
		prev.display();
	}
	return err ;
}

ValEntry::ValEntry(char ** parts, ValFile& f)
{
	int ct = 0 ;
	// skip initial string of `***...*'.
	for (char * pt = parts[0]; *pt; pt++) if (*pt != '*') break ;
	if (!*pt) ct++ ;
	node_name					= Concatenate(parts[ct++]) ;
	char *& compared			= parts[ct++] ;
	char *& file				= parts[ct++] ;
	char *& cmp_count			= parts[ct++] ;
	char *& comparisons_str		= parts[ct++] ;
	char *& with				= parts[ct++] ;
	char *& error_count 		= parts[ct++] ;
	char *& err					= parts[ct++] ;
	char *& at					= parts[ct++] ;
	char *& tol					= parts[ct++] ;
	char *& tol_val				= parts[ct++] ;

	
	if (strcmp(compared,"compared")) f.error("missing `compared'");
	
		
	f.remove_last(file,"file name",',');
	file_name =Concatenate(f.remove_quote(file,"file name"));
	{
		istrstream str(cmp_count);
		comparisons = -1 ;
		str >> comparisons ;
		if (comparisons < 0) f.error("bad comparison count field");
	}

	if (strcmp(comparisons_str,"comparisons")) f.error("missing `comparisons'");

	if (strcmp(with,"with")) f.error("missing `with'");


	if (!strcmp(error_count,"no")) errors = 0 ;
	else {
		istrstream str(error_count);
		errors = -1 ;
		str >> errors ;
		if (errors < 1) f.error("bad error count field");
	}

	if (strcmp(err,"errors")) f.error("missing `errors'");
	if (strcmp(at,"at")) f.error("missing `at'");
	if (strcmp(tol,"tolerance")) f.error("missing `tolerance'");

	int len = strlen(tol_val);
	if (len) if (tol_val[len-1] == '.') tol_val[len-1] = 0 ;
	{
        istrstream str(tol_val);
        tolerance = -1 ;
        str >> tolerance ;
        if (tolerance < 0) f.error("bad tolerance field");
    }
}

static int ValEntry_compare(const void * a, const void *b)
{
    ValEntry ** A = (ValEntry **) a ;
    ValEntry ** B = (ValEntry **) b ;
    if (!A) if (!B) return 0 ; else return -1 ;
    if (!B) return 1 ;

    ValEntry *AA = * A ;
    ValEntry *BB = * B ;
    if (!AA) if (!BB) return 0 ; else return -1 ;
    if (!BB) return 1 ;

    const char * Aa = AA->test();
    const char * Bb = BB->test();
    if (!Aa) if (!Bb) return 0; else return -1 ;
    if (!Bb) return 1 ;
    return strcmp(Aa,Bb);

}

static int ValTest_compare(const void * a, const void *b)
{
    ValTest ** A = (ValTest **) a ;
    ValTest ** B = (ValTest **) b ;
    if (!A) if (!B) return 0 ; else return -1 ;
    if (!B) return 1 ;

    ValTest *AA = * A ;
    ValTest *BB = * B ;
    if (!AA) if (!BB) return 0 ; else return -1 ;
    if (!BB) return 1 ;

    const char * Aa = AA->test();
    const char * Bb = BB->test();
    if (!Aa) if (!Bb) return 0; else return -1 ;
    if (!Bb) return 1 ;
    return strcmp(Aa,Bb);

}


ValTest::ValTest(char ** parts,char ** new_parts,ValFile& f):
	entries(0),the_errors(0),count(0),the_comparisons(0),the_differences(0),
	min_file_comparisons(-1),null_file_tests(0)
{
	int ct = 0 ;
	char *& doing =		parts[ct++] ;
	char *& what =		parts[ct++] ;
	char *& in =		parts[ct++] ;
	char *& dir = 		parts[ct++] ;

	if (strcmp(doing,"Doing")) f.error("missing `Doing'");
	test_name = f.remove_quote(what,"command or `.dpp' file name");
	if (strcmp(in,"in")) f.error("missing `in'");
	// f.remove_to(dir+1,"directory name",' ');
	dir_name = f.remove_quote(dir,"directory name");

	int space = 0 ;
	count = 0 ;
	parts = new_parts ;
	for(;;) {
		
		if (!strcmp("Did",parts[0])) {
			// f.remove_to(parts[1]+1,"command done",' ');
			char * this_test_name = f.remove_quote(parts[1],"command done");
			if (strcmp(this_test_name,test_name)) f.error("`Did' does not match `Doing'");
			break ;
		}
		int skip_error_line = 0 ;
		if (!strcmp("Errors",parts[0]) && !strcmp("are",parts[1]) &&
			!strcmp("recorded",parts[2])) skip_error_line = 1 ;
		if (!skip_error_line) {
			if (space < count + 2) {
				ValEntry ** old_entries = entries ;
				entries = new ValEntry * [space = space + (space >> 2) + 4] ;
				for (int i = 0 ; i < count ; i++) entries[i] = old_entries[i] ;
				delete old_entries ;
			}
			ValEntry * ent = entries[count++] = new ValEntry(parts,f);
			delete parts ;
			int comp = ent->comparisons;
			the_comparisons += comp ;
			if (!comp) null_file_tests++ ;
			if (min_file_comparisons < 0) min_file_comparisons = comp ;
			else if (comp < min_file_comparisons) min_file_comparisons = comp ;
			the_errors += ent->errors;
		}
		if (!(parts = f.get_parts(max_parts))) f.error("`No `Did' found");
	}
	if (entries) {
		entries[count] = 0 ;
		qsort((char *) entries,count,sizeof(entries[0]),ValEntry_compare);
	}
	if (verb) *val_out << test() << " " << count << "\n" ;
}

class ValFileContents {
	const char * name ;
	ValFile  file ;
	ValTest ** tests ;
	int the_errors ;
	int the_comparisons ;
	int the_files_compared ;
	int the_differences ;
	int count ;
	int null_file_tests ;
	int min_file_comparisons ;
public:
	ValFileContents(const char * nm);
	int size() const {return count;}
	int compare(ValFileContents& prev);
	void summary();
	int differences() const {return the_differences;}
	const char * file_name() const {return name;}
	int errors() const {return the_errors;}
	static void count_out(int count, const char * type);
};


void ValFileContents::count_out(int count, const char * type)
{
	if (count < 0) {
		cerr << "Negative count (" << count <<
			") in `ValFileContents::count_out' for `" << type << "'.\n" ;
		// exit(1);
	}
	if (count) *val_out << count ;
	else *val_out << "no" ;
	*val_out << type ;
	if (count != 1) *val_out << "s" ;
}

void ValFileContents::summary()
{
	*val_out << "Did " ;
	count_out(count," test");
	*val_out  << " comparing " ;
	count_out(the_files_compared," file");
	*val_out << ".\n" ;
	count_out(the_comparisons," value");
	*val_out << " were compared with " ;
	count_out(the_errors," error");
	*val_out << ".\n" ;
	if (!null_file_tests && (min_file_comparisons > -1)) {
		*val_out << "The fewest compares for one file checked " ;
		count_out(min_file_comparisons, " value");
		*val_out << ".\n" ;
	} else {
		*val_out << "There output of " ;
		count_out(null_file_tests," file");
		*val_out << " was not checked (no comparisons).\n" ;
	}
}

ValFileContents::ValFileContents(const char * nm):file(nm),name(nm),tests(0),
	count(0),the_errors(0),null_file_tests(0),min_file_comparisons(-1),
	the_comparisons(0),the_files_compared(0),the_differences(0)
{
	char ** parts ;
	int space = 0 ;
	while (parts = file.get_parts(30)) {
		if (!strcmp(parts[0],"Doing")) {
			char ** new_parts = file.get_parts(30);
			if (!strcmp(new_parts[0],"Did")) {
				delete new_parts ;
				continue ;
			}
			if (space < count + 1) {
				ValTest ** old_tests = tests ;
				tests = new ValTest * [space =  space + (space >> 2) + 50 ] ;
				for (int i = 0; i < count; i++) tests[i] = old_tests[i] ;
				delete old_tests ;
			}
			ValTest * ts = tests[count++] = new ValTest(parts,new_parts,file);
			the_errors += ts->number_errors();
			the_comparisons += ts->number_comparisons();
			null_file_tests+= ts->get_null_file_tests() ;
			int min_comp = ts->get_min_file_comparisons() ;
			if (min_comp > -1)
				if (min_file_comparisons < 0) min_file_comparisons = min_comp ;
				else if (min_comp < min_file_comparisons) min_file_comparisons =
					min_comp ;
			the_files_compared += ts->size();
		}
		delete parts ;
	}
	if (tests) {
		tests[count] = 0 ;
		qsort((char *) tests,count,sizeof(tests[0]),ValTest_compare);
	}
	if (verb) *val_out << name << " " << count << "\n" ;
}

int ValTest::compare(ValTest& prev)
{
	int ret = 0 ;
	ValEntry ** tst = entries ;
	ValEntry ** tst_against = prev.entries ;
	ValEntry * test  ;
	ValEntry * test_against  ;
	for (;;) { 
		test = tst ? *tst : 0;
		test_against = tst_against? *tst_against :0;
		if (!test && !test_against) break ;
		int cmp = -1 ;
		if (test && test_against) cmp = strcmp(test->test(),
			test_against->test()) ;
		else if (test_against) cmp = 1 ;
		if (!cmp) {
			int err =  test->compare(*test_against) ;
			if (err) {
				*val_out << "    The above discrepancies were in file `" ;
				the_differences+=err ;
				ret = 1 ;
			}
			if (verb && ! err) *val_out << "    Both validations compared `" ;
			if (verb || err) *val_out << test->test() << "'.\n\n";
			tst++ ;
			tst_against++;
			continue ;
		}
		the_differences++ ;
		ret = 1 ;
		if (cmp < 0) { // missing element from test_against
			*val_out << "    ++Previous validation `" << prev.test_name <<
				"' did not test file `" << test->test() << "'.\n\n" ;
			tst++;
			ret = 1 ;
			continue ;
		}
		// missing current validation
		*val_out << "    --Current validation does not test file `" <<
				test_against->test() << "'.\n\n" ;
      	tst_against++;
		ret = 1 ; 
       continue ;
	}
	return ret ;
}

int ValFileContents::compare(ValFileContents& prev)
{
	ValTest ** tst = tests ;
	ValTest ** tst_against = prev.tests ;
	ValTest * test  ;
	ValTest * test_against  ;
	int error = 0 ;
	for (;;) { 
		test = tst ? *tst : 0;
		test_against = tst_against? *tst_against :0;
		if (!test && !test_against) break ;
		int cmp = -1 ;
		if (test && test_against) cmp = strcmp(test->test(),
			test_against->test()) ;
		else if (test_against) cmp = 1 ;
		if (!cmp) {
			int err = test->compare(*test_against);
			if (err) {
				error++ ;
				*val_out << "The above discrepencies were in test `" ;
				if (test) the_differences += test->differences();
				else the_differences++ ;
			}
			if (verb && !err) *val_out << "Both validations did test `" ;
			if (verb || err) *val_out << test->test() <<
				"'.\n___________________________________________\n\n" ;
			tst++ ;
			tst_against++;
			continue ;
		}
		the_differences++ ;
		if (cmp < 0) { // missing element from test_against
			*val_out << "+++Previous validation `" << prev.name <<
				"' did not do test `" << test->test() << "'.\n\n" ;
			tst++;
			error++ ;
			continue ;
		}
		// missing current validation
		*val_out << "---Current validation does not do test `" <<
			test_against->test() << "'.\n\n" ;
            tst_against++;
			error ;
            continue ;
	}
	return error ;
}

static void usage(char * pgm)
{
	cerr << "Usage: " << pgm << " [ -v ] current previous.\n" ;
    cerr << "Compares tests logged on the two files, `-v' is verbose.\n" ;
    exit(1);
}

void main(int argc, char ** argv)
{
	char c ;
	while (( c = getopt(argc,argv,"v")) != EOF) switch(c) {
case 'v' :  verb = 1 ;
        break ;
default:
		cerr << argv[0] << ": bad option `-" << c << "'.\n" ;
        usage(argv[0]);
    }

	if (argc - optind != 2) {
		cerr << argv[0] << ": wrong number of parameters.\n" ;
		usage(argv[0]);
	}
	ofstream out_stream(argv[optind],(ios::ate  | ios::app | ios::out));
	val_out = &out_stream ;
	if (!out_stream.good()) {
		cerr << "Cannot append to validation log `" << optind << "'.\n" ;
		exit(1);
	}
	ValFileContents this_test(argv[optind++]) ;
	ValFileContents previous_test(argv[optind]) ;
	*val_out << "\n" ;
	this_test.compare(previous_test);
	*val_out << "\n___ Test Summary ___\n" ;
	this_test.summary();
	if (this_test.differences() == 1) *val_out << "There is " ;
	else *val_out << "There are " ;
	ValFileContents::count_out(this_test.differences()," difference");
	*val_out << " between the base line validation log\n`" ;
	*val_out << previous_test.file_name() << "' and this log\n`" <<
		this_test.file_name() << "'.\n\n" ; 
	if (verb || this_test.differences() || this_test.errors()) {
		*val_out << "Differences in validation logs show different tests\n" ;
		*val_out << "were done or different results were obtained.\n" ;
		*val_out << "`Errors' is a count of differences in test results.\n" ;
		*val_out << "All differences between logs and the names of\n" ;
		*val_out << "files containing other differences are recorded in\n`" ;
		*val_out << this_test.file_name() << "'.\n\n";
	}
}
