#include <stream.h>
#include <stdio.h>
#ifdef __NT_VC__
#include <io.h>
#endif
#include <fstream.h>
#include <strstream.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "ObjProGen/mkstr.h"
#include "ObjProDSP/portable.h"
#include "ObjProDSP/mpacket.h"
#include "ObjProDSP/pltenm.h"
#include "ObjProGen/cpyrght_exe.h"
#include "ObjProGen/copyright.h"
#include "ObjProComGui/cgidbg.h"

static ostream * val_out ;
static int verb = 0 ;
static int file_a = -1 ;
static int file_b = -1 ;
static const char * file_a_name ;
static char * file_b_name ;
static const char * test_name ;

int no_ignore_flag ;

static int32 file_size(const char *file_name)
{
	struct stat this_status ;
    int err = stat(file_name,&this_status);
    if (err) {
        cerr << "Cannot get status of `" << file_name << "'.\n" ;
		SystemErrorMessage();
		cerr << "\n" ;
		exit(1);
    }
    return this_status.st_size ;
}


static void file_error(const char * file_name)
{
	cerr << "Error reading file `" << file_name << "'.\n" ;
	SystemErrorMessage();
	cerr << "\n" ;
	exit(1);
}

static int32 byte_errors = 0 ;
static total_compare = 0 ;

static int length_a =  -1 ;
static int length_b =  -1 ;

static void close_all()
{
	close(file_a);
	close(file_b);
}

static void unequal()
{
	close_all();
	length_a = file_size(file_a_name);
	length_b = file_size(file_b_name) ;
}

static date_length = 0 ;

static int find_before_date(char * str)
{
	if (!date_length) date_length = strlen("Mon Sep  5 13:46:11 1994") ;
	char * written = strstr(str,"' written by `");
	if (!written) return 0 ;
	const char * on_token = "' on " ;
	char * on = strstr(written,on_token);
	char * start_date = on + strlen(on_token) ;
	if (!on) return 0 ;
	int length = strlen(str);
	if (length != (start_date - str + date_length)) return 0 ;
	return length - date_length ;
}

static int check_date(char * a, char * b, int max)
{
	char * a_new_line = strchr(a,'\n');
	char * b_new_line = strchr(b,'\n');
	if (!a_new_line || !b_new_line) return 0 ;
	int length = a_new_line - a ;
	if (length != (b_new_line - b)) return 0 ;
	a[length] = b[length] = '\0' ;
	int to_date = find_before_date(a);
	if (to_date != find_before_date(b)) return 0 ;
	if (strncmp(a,b,to_date)) return 0 ;
	return to_date + date_length + 1 ;
}

#define COPY_INC(a,b,end)  { \
	if (b + sizeof(a) > end) return 0 ; \
	memcpy((char *)&a,b,sizeof(a)); \
	b+=sizeof(a) ; \
}

static PacketHeader * next_packet(char *& a_pt, char *& b_pt, char *a, char *b,int max)
{
	PacketHeader& a_head = *new PacketHeader ;
	PacketHeader b_head ;
	COPY_INC(a_head,a_pt,a+max);
	COPY_INC(b_head,b_pt,b+max);	
	if (a_head.ThePacketType != b_head.ThePacketType) return 0 ;
	if (a_head.Identifier != b_head.Identifier) return 0 ;
	if (a_head.DataSize != b_head.DataSize) return 0 ;
	a_pt += a_head.DataSize;
	b_pt += b_head.DataSize;
	return &a_head ;
}

static int valid_next_head(PacketHeader * head)
{
	static the_plot_id = 0 ;
	static PacketHeader * last_head = 0 ;
	static PlotSpecificControlOptions last_control = (PlotSpecificControlOptions) 0 ;
	if (head->ThePacketType != PacketWindowPlotControl) return 0 ;
	uint32 id = head->Identifier ;
	int plot_id = id >> 16 ;
	PlotSpecificControlOptions plot_control =
		(PlotSpecificControlOptions) (id & 0xffff) ;

	switch (plot_control) {
case PlotControlHeader:
		if (last_head) return 0 ;
		the_plot_id = plot_id ;
		break ;
case PlotControlCaption:
		if (last_control != PlotControlHeader) return 0 ;
		break ;
case PlotControlName:
		if (last_control != PlotControlHeader &&
			last_control != PlotControlCaption) return 0 ;
		break ;
case PlotControlChannel:
		if (last_control != PlotControlName) return 0 ;
		break ;
case PlotControlElement:
		if (last_control != PlotControlChannel &&
			last_control != PlotControlElement) return 0 ;
		break ;
case PlotControlComplete:
		if (last_control != PlotControlElement) return 0 ;
		if (head->DataSize) return 0;
		return -1 ;
case PlotControlLabelParameter:
case PlotControlNewSampleRate:
case PlotControlRaiseWindow:
default:
		return 0 ;
	}
	if (plot_id != the_plot_id) return 0 ;
	last_control = plot_control ;
	delete last_head ;
	last_head = head ;
	return 1 ;
}

static int check_plot(char * a, char * b, int max)
{
	static int length = 0 ;
	static const char * plot_file_pro = AddCopyright::plot_file_prologue ;
	if (!length) length = strlen(AddCopyright::plot_file_prologue);
	if (strncmp(AddCopyright::plot_file_prologue,a,length)) return 0 ;
	if (strncmp(AddCopyright::plot_file_prologue,b,length)) return 0 ;
	char *a_pt = a + length ;
	char *b_pt = b + length ;
	int32 channel_size ;
	if (memcmp(a_pt,b_pt,sizeof(channel_size))) return 0 ;
	memcpy(&channel_size,a_pt,sizeof(channel_size));
	a_pt += sizeof(channel_size);
	b_pt += sizeof(channel_size);
	for(;;) {
		PacketHeader * head = next_packet(a_pt,b_pt,a,b,max);
		if (!head) return 0 ;
		int ret = valid_next_head(head) ;
		if (!ret) return 0 ;
		if (ret == -1) return a_pt - a ;
	}
}

static int check_ignore(char * a, char * b, int max)
{
	a[max]='\0' ;
	b[max]='\0' ;
	int ret = check_plot(a,b,max);
	if (ret) return ret ;
	return check_date(a,b,max);
}


static void compare()
{
	const buf_size = 16384 ;
	char buf_a[buf_size+1] ;
	char buf_b[buf_size+1] ;
	int rd ;
	int32 total ;
	int first_read = 1 ;
	while ((rd = read(file_a,buf_a,buf_size))>0) {
		int brd = read(file_b,buf_b,rd) ;
		if (brd < 0) file_error(file_b_name);
		int start = 0 ;
		if (first_read && !no_ignore_flag) {
			start = check_ignore(buf_a,buf_b,brd);
			if (start) total_compare -= start ;
		}
		for (int i = start ; i < brd; i++) if (buf_a[i] != buf_b[i]) byte_errors++;
		if (brd != rd) {
			unequal();
			return ;
		}
		total += rd ;
		total_compare += rd ;
		first_read = 0;
	}
	if (rd < 0) file_error(file_a_name);
	int brd = read(file_b,buf_b,buf_size) ;
	if (brd < 0) file_error(file_b_name);
	if (brd) unequal();
	else close_all();
}

const char * program ;

static void usage()
{
	cerr << "Usage: " << program << " [ -v ] [ -n ] file validation_log\n" ;
    cerr << "Compares `file' against `fileb', i. e. the same name with an added `b'.\n" ;
	cerr << "`-v' is verbose mode. `-n' checks every byte in all files.\n" ;
	cerr << "The default is to ignore expected differences in standard files\n" ;
	cerr << "when those files are recognized. `-n' disables this and reports\n" ;
	cerr << "all differences. The standard differences ignored are\n" ;
	cerr << "the date at the end of the header in the first line of a\n" ;
	cerr << "file that appears to be generated by `AsciiFile' and the\n" ;
	cerr << "data structures in the header of what appears to be a saved\n" ;
	cerr << "`Plot' file. These structures contain physical addresses that\n" ;
	cerr << "are ignored when the file is read.\n" ;

    exit(1);
}

static void cannot_open(const char * name)
{
	cerr << "Cannot open `" << name << "'.\n" ;
	usage();
}

void report (ostream& out,int32 cmp, int32 err_total)
{
	if (err_total || !cmp) out  << "***" ;
	out  <<  "Compare_byte compared `" << test_name << "', " << cmp 
		<< " comparisons with " ;
	if (err_total) out << err_total ;
	else out << "no" ;
	out << " errors at tolerance 0.\n" ;
}

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

	if (argc - optind != 2) {
		cerr << argv[0] << ": wrong number of parameters.\n" ;
		usage();
	}
	file_a_name = argv[optind] ;
	file_a = open(file_a_name,O_RDONLY);
	if (file_a < 1) cannot_open(file_a_name);

	const char * find_start = "/validate/" ;
	const char * start = file_a_name ;
	const char * last = 0 ;
	while(last = strstr(start,find_start)) if (last == start) break ;
	else start = last ;
	if (start == file_a_name) {
		cerr << "Compare file `" << file_a_name << "'\n" <<
			"does not contain the substring `" << find_start << "'.\n" ;
		usage();
	}
	test_name = start + strlen(find_start) ;
	int length = strlen(file_a_name) ;
	file_b_name = new char[length + 2] ;
	strcpy(file_b_name,file_a_name);
	strcat(file_b_name,"b");
	file_b = open(file_b_name,O_RDONLY);
	if (file_b < 1) cannot_open(file_b_name);

	ofstream out_stream(argv[optind+1],(ios::ate  | ios::app | ios::out));
	val_out = &out_stream ;
	if (!out_stream.good()) {
		cerr << "Cannot append to validation log `" << optind << "'.\n" ;
		usage();
	}
	compare();
	int32 cmp = total_compare ;
	if (cmp < 0) cmp = 0 ;
	if (cmp < length_a) cmp = length_a ;
	if (cmp < length_b) cmp = length_b ;
	int32 err_total = byte_errors + abs(length_a - length_b) ;
	report(*val_out,cmp,err_total);
	report(cerr,cmp,err_total);
	exit(0);
}
