#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/copyright.h"
#include "ObjProGen/cpyrght_exe.h"
#include "ObjProComGui/cgidbg.h"

ostream * val_out = 0 ;
int verb = 0 ;

struct ValEntry {
	const char * node_name ; // if null then we have a general test
	const char * test_type ;
	const char * file_name ;
	ValEntry(const char * f, const char * nd, const char * tst):node_name(nd),
		test_type(tst),file_name(f) {}
	ValEntry(const char * f,const char * tst):node_name(0),test_type(tst),
		file_name(f)   {}
	void out(ValEntry * prev);
	const char * arith_type() ;
	static void node_head();
	static void other_head();
	static void space(int n);

	static max_node_size ;
	static max_type_size ;
	static max_other_size ;
	static max_arith_size ;
};


int ValEntry::max_node_size = 0 ;
int ValEntry::max_other_size = 0 ;
int ValEntry::max_type_size = 0 ;
int ValEntry::max_arith_size = 0 ;

static int is_tail(const char * name, const char * tail)
{
	int tail_length = strlen(tail);
	int length = strlen(name);
	if (length < tail_length) return 0 ;
	return !strcmp(name + length - tail_length, tail);
}

const char * ValEntry::arith_type()
{
	const char * ret = "all" ;
	if (!file_name) ret = "NO FILE NAME" ;
	if (is_tail(file_name,"int16.rec")) ret = "int16" ;
	if (is_tail(file_name,"float.rec")) ret = "float" ;
	int len = strlen(ret);
	if (len > ValEntry::max_arith_size) ValEntry::max_arith_size = len ;
	return ret ;
}


void ValEntry::space(int n)
{
	for (int i = 0 ; i < n; i++) *val_out << ' ' ;
}

void ValEntry::node_head()
{
	const char * nn = "Node name" ;
	int len_node = strlen(nn) ;
	if (max_node_size < len_node) max_node_size = len_node ;
	const char * done = "Test" ;
	int len_done = strlen(done);
	if (max_type_size < len_done) max_type_size = len_done ;
	const char * arith = "Arithmetic " ;
	int len_arith = strlen(arith);
	if (max_arith_size < len_arith)  max_arith_size = len_arith ;
	*val_out << nn;
	space(max_node_size + 1 - len_node);
	*val_out << done ;
	space(max_type_size + 1 - len_done);
	*val_out << arith ;
	space(max_arith_size + 1 - len_arith);
	*val_out << "Validation file\n" ;
	
}

void ValEntry::other_head()
{
	const char * other = "Generic test" ;
	int len_other = strlen(other);
	if (max_other_size < len_other) max_other_size = len_other ;
	const char * arith = "Arithmetic " ;
	int len_arith = strlen(arith);
	if (max_arith_size < len_arith)  max_arith_size = len_arith ;
	*val_out << "\n" << other ;
	space(max_other_size + 1 - len_other);
	*val_out << arith ;
	space(max_arith_size + 1 - len_arith);
	*val_out << "Validation file\n" ;
}

void ValEntry::out(ValEntry * prev)
{
	if (node_name) {
		*val_out << node_name ;
		space(max_node_size + 1 - strlen(node_name));
		*val_out << test_type ;
		space(max_type_size + 1 - strlen(test_type)) ;
	} else {
		*val_out << test_type ;
		space(max_other_size + 1 - strlen(test_type)) ;
	}
	*val_out << arith_type();
	space(max_arith_size + 1 - strlen(arith_type())) ;
	*val_out << file_name << "\n" ;
}

class ValEntries {
	int count ;
	int next ;
public:
	ValEntries();
	void add(ValEntry * ent);
	void check_space();
	ValEntry ** entries ;
	int size() const {return next ;}
};

ValEntries::ValEntries():entries(0),count(0),next(0)
{
}

void ValEntries::add(ValEntry * ent)
{
	check_space();
	entries[next++] = ent ;
}


void ValEntries::check_space()
{
	if (count - next >= 2) return ;
	int new_count = count + (count >> 2) + 10 ;
	ValEntry ** temp = new ValEntry * [new_count] ;
	for (int i = 0 ; i < next ; i++) temp[i] = entries[i];
	for (;i< new_count;i++) temp[i] = 0 ;
	delete entries ;
	count = new_count ;
	entries = temp ;
}


class ValFile {
	ifstream file ;
	const char * file_name ;
	static const char * end_line ;
	char * buf ;
	static max_line ;
	int node_mode ;
	void check_space();
	int line ;

	void skip_notice(ifstream& str);
public:
	static int max_parts ;
	static ValEntries all_entries ;
	ValFile(const char * file_name);
	const char * name() const {return file_name;}
	void check_max(int i, int max_parts);
	char ** get_parts(int max_count) ;
	static const char * node_first_token ;
	static const char * other_feature_first_token ;
	static char * combine(const char ** pts);
	void error(const char * msg);
	char ** split(int max);
};

ValEntries ValFile::all_entries;

int ValFile::max_parts = 30 ;
int ValFile::max_line = 8192 ;

const char * ValFile::end_line = 
"######################################################## END OF VALIDATION LIST";

const char * ValFile::node_first_token = "#" ;
const char * ValFile::other_feature_first_token = "##" ;

ValFile::ValFile(const char * nm):file_name(nm),file(nm),
	buf(new char[max_line+1]),node_mode(1),line(0)
{
	if (!file.good()) {
		cerr << "Cannot read `" << file_name << "'.\n" ;
		SystemErrorMessage();
		exit(1);
	}
	skip_notice(file);
	char ** parts ;
	while (parts = get_parts(max_parts)) {
		line++ ;
		if (parts[0][0] != '#') error("invalid line");
		if (!parts[1]) continue ;
		if (!strcmp(parts[0],node_first_token)) {
			if (!node_mode) error("node entries out of order");
			all_entries.add(new ValEntry(file_name,parts[1],combine(
				(const char **) parts+2)));
			continue ;
		}
		if (!strcmp(parts[0],other_feature_first_token)) {
			node_mode = 0 ;
			all_entries.add(new ValEntry(file_name,combine(
				(const char **) parts + 1)));
			continue ;
		}
		error("invalid line");
	}
	
}

void ValFile::skip_notice(ifstream& str)
{
	char * buf = new char[AddCopyright::max_line_length] ;
	AddCopyright& cp = AddCopyright::add_copyright();
	FileType * type = cp.type(file_name);
	if (!type) return ;
	for (;;) {
		str.getline(buf,AddCopyright::max_line_length);
		if (str.fail()) break ;
		if (type->may_be_comment_terminate(buf)) return ;
	}
	str.clear();
	str.seekg(0,ios::beg);
	delete buf ;
}

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


char *  ValFile::combine(const char ** pts)
{
	int length = 0 ;
	for (const char ** s = pts ; *s ; s++) length += 1 + strlen(*s);
	char * ret = new char[length] ;
	ret[0] = '\0' ;
	int first = 1 ;
	for(s=pts; *s; s++) {
		if (!first) strcat(ret," ");
		first = 0 ;
		strcat(ret,*s);
	}
	return ret ;
}


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 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 << "End of validation list found in `"
				<< file_name  << "'.\n" ;
			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 << "***Cannot find end of validation list in `" <<
		file_name <<"'.\n" ;
	return 0 ;
}

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

static void usage(char * pgm)
{
	cerr << "Usage: " << pgm << " [ -v ] -l log_file rec-files.\n" ;
    cerr << "`rec-files' are the `.rec' files used to create tests\n" ;
	cerr << "with annotation as comments at the beginning that describe\n";
	cerr << "what is tested. `log_file' is the validation log the report\n";
	cerr << "will be appended to. `-v' is verbose mode.\n" ;
    exit(1);
}

static int empty_compare(const void *a, const void *b)
{
	if (!a) if (!b) return 0 ;
    else return -1 ;
    if (!b) return 1 ;
	return 2 ;
}

static int str_compare(const char *a, const char *b)
{
	int ret = empty_compare(a,b) ;
#ifdef __NT_VC__
	if (ret == 2) return stricmp(a,b);
#else
	if (ret == 2) return strcasecmp(a,b);
#endif
	return ret ;
}

static int ValEntry_compare(const void * a, const void *b)
{
    ValEntry ** A = (ValEntry **) a ;
    ValEntry ** B = (ValEntry **) b ;
	int ret = empty_compare(A,B);
	if (ret < 2) return ret ;

    ValEntry *AA = * A ;
    ValEntry *BB = * B ;
	ret = empty_compare(AA,BB);
	if (ret < 2) return ret ; 

    const char * Aa = AA->node_name ;
    const char * Bb = BB->node_name ;
	int did_arith = 0 ;
	if (Aa || Bb) {
		ret = str_compare(Aa,Bb);
		if (ret && !Aa || !Bb) return -ret ; // want null node name entries lsit
		if (ret) return ret ; 
		if (Aa && Bb) {
			Aa = AA->arith_type();
			Bb = BB->arith_type();
			ret = str_compare(Aa,Bb);
			if (ret) return ret ;
			did_arith = 1 ;
		}
	}

	Aa = AA->test_type ;
	Bb = BB->test_type ;
	ret = str_compare(Aa,Bb) ;
	if (ret) return ret ;
	if (!did_arith) {
		Aa = AA->arith_type();
		Bb = BB->arith_type();
		ret = str_compare(Aa,Bb);
		if (ret) return ret ;
	}
	Aa = AA->file_name ;
	Bb = BB->file_name ;
	return str_compare(Aa,Bb) ;
}



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

	if (argc - optind < 1) {
		cerr << "No `.rec' files specified.\n" ;
		usage(argv[0]);
	}
	ofstream out_stream(log_file,(ios::ate  | ios::app | ios::out));
	val_out = &out_stream ;
	if (!out_stream.good()) {
		cerr << "Cannot append to validation log `" << optind << "'.\n" ;
		exit(1);
	}
	for (int i = optind ; i < argc ; i++) {
		ValFile temp(argv[i]) ;
	}
	ValEntries &all_entries = ValFile::all_entries ;
	ValEntry ** entries = all_entries.entries ; ;
	if (entries) {
		qsort((char *) entries,all_entries.size(),sizeof(entries[0]),
			ValEntry_compare);
		ValEntry * prev = 0 ;
		int node_head_out = 0 ;
		int other_head_out = 0 ;
		for (ValEntry ** ent = entries ; *ent ; ent++)
		  if ((*ent)->node_name) {
			int len = strlen((*ent)->node_name) ;
			if (len > ValEntry::max_node_size) ValEntry::max_node_size = len ;
			len = 0 ;
			if ((*ent)->test_type) len = strlen((*ent)->test_type);
			if (len > ValEntry::max_type_size)
				ValEntry::max_type_size = len ;
		} else {
			int len = 0 ;
			if ((*ent)->test_type) len = strlen((*ent)->test_type);
			if (ValEntry::max_other_size < len)
				ValEntry::max_other_size = len ;
		}
		*val_out << "\n                Summary of tests done\n" ;
		for (ent = entries ; *ent ; ent++) {
			if (!node_head_out && (*ent)->node_name) {
				ValEntry::node_head();
				node_head_out = 1 ;
			}
			if (!other_head_out && !(*ent)->node_name) {
				ValEntry::other_head();
				other_head_out = 1 ;
			}
			(*ent)->out(prev);
		}
		*val_out << "\n" ;
	}
}
