#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#ifndef __NT_VC__
#include <utime.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fstream.h>
#include "ObjProGen/cpyrght_exe.h"
#include "ObjProGen/mkstr.h"
#include "ObjProComGui/cgidbg.h"
#include "copyright.h"


static ofstream * log_file ;
class LogStream : public ostream {
	public:
	virtual LogStream& operator<<(const char * string);
	virtual LogStream& operator<<(const char & c);
	LogStream(){}
	~LogStream(){delete log_file;}
};

LogStream& LogStream::operator<<(const char & c)
{
	cerr << c ;
	if (log_file) *log_file << c ;
	return *this;
}

LogStream& LogStream::operator<<(const char * string)
{
	cerr << string ;
	if (log_file) *log_file << string ;
	return *this ;
}

static LogStream Cerr ;

class AddCopyrightFile: public AddCopyright {
public:
	AddCopyrightFile(AddCopyright& a_c);
	int filter(istream& in, ostream& o,const char * path_name, off_t size,
		int user_flag);
};

AddCopyrightFile::AddCopyrightFile(AddCopyright& a_c):
	AddCopyright(a_c.mountain_math,a_c.mountain_math_short,a_c.user)
{
}

static const char * prog_name = "cp_update_legal" ;
static void usage(int opt=0)
{
	ostream * out = 0 ;
	if (opt&1) {
		char * msg = 0;
		SystemErrorMessage(0,&msg);
		Cerr << msg << "\n" ;
		delete msg ;
	}
	if (opt&2) out = &cout ; else out = &cerr ;
	*out << "\n" << prog_name <<  " -e  : give detailed program description\n" ;
	*out << prog_name << " [-l] log_file [-u] [-m] ( f1 f2 | f1 f2 ... fn dir) \n" ;
	*out << prog_name << " [-u] ( -c | -C) new_file\n" ;
	*out << "   Copy a file to another or multiple files to a directory\n" ;
	*out << "   while adding or updating a copyright notice.\n" ;
	*out << "   -u adds a user coyright notice.\n" ;
	*out << "   -m sets the destination modification time to that of the soruce.\n" ;
	*out << "   -c writes the short notice (-C long notice) to a new file `new_file'.\n" ;
	*out << "   -l APPENDS a record of all actions to `log_file'.\n" ;
	if (opt&2) {
	*out << "\n\n`" << prog_name << 
		"' adds or replaces an existing copyright notice\n" ;
	*out << "in the process of copying a file. It can copy a single file to\n" ;
	*out << "a new destination or multiple files to a directory. It will\n" ;
	*out << "NOT do an in place update of an existing file.\n" ;
	*out << "The mode of the new file is the same as the source file.\n" ;
	*out << "The notices to be added or replaced must be compiled into `cp_update_legal'.\n" ;
	*out << "A user copyrgight notice `$OPD_ROOT/scripts/user_copyright_text'\n" ;
	*out << "is automatically compiled in when affected programs are rebuilt.\n";
	*out << "Please read `user_copyright_text' for instruction on updating it.\n\n" ;
	*out << "\nUse this program ONLY to update copyright notices.\n" ;
	*out << "If you remove copyright notices from source code for which\n";
	*out << "you are not the SOLE copyright holder and that code is subsequently\n";
	*out << "distributed to others (even if you did not intend to distribute it)\n";
	*out << "you could be liable for damages.\n" ;
		exit(1);
	}
	exit(1);
}

static void err_stat(const char *file_name, struct stat& st)
{
    int err = stat(file_name,&st);
    if (err) {
        Cerr << "Cannot get status of `" << file_name << "'.\n" ;
        usage(1);
    }

}


main(int argc, char ** argv)
{
	int reset_time = 0 ;
	int user_flag = 0 ;
	int long_flag = 0 ;
	char * optstring = "umec:C:l:" ;
	const char * log_file_name ;
	const char * write_file = 0 ;
	int ltr ;
    while ( ltr = getopt(argc, argv, optstring)) switch (ltr) {
case 'l' : log_file_name = optarg ;
		break ;
case 'C':
		long_flag = 1 ;
case 'c':
		write_file = optarg ;
		break ;
case 'u':
		user_flag = 1 ;
		break ;
case 'm':
		reset_time = 1 ;
		break ;
case 'e':
		usage(2);
		break ;
case EOF:
		goto exit ;
default:
		Cerr << "Bad option `-" << (char) ltr << "'.\n" ;
		usage();
	}
exit :
	AddCopyrightFile filter(AddCopyright::add_copyright());
	if (write_file) {
		if (argc > optind) usage();
		if (reset_time || log_file) usage();
		ofstream out(write_file);
		if (!out.good()) {
			Cerr << "Cannot create `" << write_file << "'.\n" ;
			usage(1);
		}
		if (long_flag) filter.write(out,write_file,user_flag);
		else filter.write_short(out,write_file,user_flag);
		exit(0);
	}
	if (argc - optind < 2) {
		Cerr << "Two or more arguments are required.\n" ;
		usage();
	}
	if(long_flag) usage();
	if (log_file_name)  {
		log_file = new ofstream(log_file_name,ios::app);
		if (!log_file->good()) {
			cerr << "Cannot create or append to log file `" << log_file_name
				<< "'.\n" ;
			usage(1);
		}
	}	
	// see if last argument is a directory
	const char * last_name = argv[argc-1] ;
	struct stat last_status ;
	int last_is_directory = 0 ;
	int err = stat(last_name,&last_status);
	if (!err) last_is_directory = (last_status.st_mode & S_IFDIR) ? 1 : 0 ;
	if (!last_is_directory && (argc -optind > 2)) {
		Cerr << "The last argument must be a directory when more then\n" ;
		Cerr << "two arguments are given.\n";
		usage();
	}
	for (int i = optind ; i < argc - 1 ; i++) {
		struct stat this_status ;
		const char * this_name = argv[i] ;
		err_stat(this_name,this_status);
		off_t size = this_status.st_size ;
		if (this_status.st_mode & S_IFDIR) {
			Cerr << prog_name << ": " << this_name << ": omitting directory\n";
			continue ;
		}
		if (!last_is_directory) if (this_status.st_ino == last_status.st_ino) {
			Cerr << "Source and destination files are identical.\n" ;
			exit(1);
		}
			
		{
			ifstream in(this_name);
			if (!in.good()) {
				Cerr << "Cannot open `" << this_name << "'.\n" ;
				usage(1);
			}
			const char * out_name = last_name ;
			char * to_delete = 0 ;
			if (last_is_directory) out_name = to_delete =
				ReplaceDirectory(last_name,this_name);
			{
				ofstream out(out_name);
				if (!out.good()) {
					Cerr << "Cannot create `" << out_name << "'.\n" ;
					usage(1);
				}
				int err_code ;
				if (log_file) {
					const char * u = "" ;
					if (user_flag) u = " User" ;
					const char * t = "" ;
					const char * this_time = "" ;
					if (reset_time) {
						t = " Time" ;
						this_time = ctime(&(this_status.st_mtime));
					}
					const char * l = "" ;
					if (filter.type(out_name))
						if (size > AddCopyright::byte_boundary) l = " Long" ;
						else l = " Short" ;
					*log_file << "`" << this_name << "' to `" <<
						out_name << "' " << size << " " << this_time 
						<< u << t << l << "\n" ;
				}
				if (err_code = filter.filter(in,out,out_name,size,
					user_flag)) {
					Cerr << "Error in copying `" << this_name << "'\n" ;
					Cerr << "to `" << out_name << "'.\n" ;
					usage((err_code < 0) ? 1 : 0);
				}
				out.close();

				// change mode of destination file to that of source file
#ifdef linux
				umode_t source_mode = this_status.st_mode;
#else
				mode_t source_mode = this_status.st_mode;
#endif
#ifndef __NT_VC__
				if (chmod(out_name,source_mode)) {
					Cerr << "Cannot change mode of `" << out_name << "'\n" ;
					cerr << "to 0x " << hex << source_mode << dec << ".\n" ;
					if (log_file) *log_file << "to 0x " << hex << source_mode
						<< dec << ".\n" ;
					SystemErrorMessage();
					exit(1);
				}
				// if requested change access and modification times
				if (reset_time) {
					struct utimbuf old_time = {
						this_status.st_atime, this_status.st_mtime
					};
					if (utime(out_name,&old_time)) {
						Cerr << "Cannot change times of output file `" <<
							out_name << "'.\n" ;
						usage(2);
					}
				}
#endif
			}
			delete to_delete ;
			in.close();
		}
	}
	return 0 ;
}

int AddCopyrightFile::filter(istream& in, ostream& out,
	const char * path_name, off_t size, int user_flag)
{
	int ret = 0 ; // set to 1 on error
	const max_length = 8192 ;
	char * name = OpdRemoveDirectory(path_name);
	FileType * typ = type(name);
	int buffers_read = 0 ;
	int copy_buffers = 0 ;
	const check_lines = 3 ;
	int first_file_line_to_copy = 0;
	next_file_line = 0 ;
	int may_remove_old = 0 ;
	if (!typ) {
		if (!is_not_notice_type(name) && log_file) 
			*log_file << "Unknown file type `" << path_name << "'.\n" ;
	} else {
		if (typ->remove_old) {
			may_remove_old = 1 ;
			int line_length[check_lines] ;
			for (int i = 0 ; i < check_lines;i++) {
				if ((line_length[i] = read_line(in)) <0) {
					copy_buffers = 1 ;
					break ; 
				}
				if (line_length[i] > max_line_length - 2) {
					Cerr << "Line length greater than " <<
						max_line_length - 2 << ".\n" ;
					return -1 ;
				} 
			}
			if (!copy_buffers) {
				static const char * start_line[check_lines] = {
					"/*  ",
					"/*  Copyright ",
					"/*  All Rights Reserved "
				};
				static int start_length[check_lines] ={0} ;
				static const char * end_line[check_lines] = {
					"*/",
					" Mountain Math Software  */",
					"*/"
				};
				static int end_length[check_lines] ={0} ;
				if (!end_length[0]) for (i = 0 ; i < check_lines; i++) {
					start_length[i]=strlen(start_line[i]);
					end_length[i]  =strlen(end_line[i]);
				}

				for (i = 0 ; i < check_lines; i++) {
					int length = line_length[i] ;
					if (strncmp(file_lines[i],start_line[i],start_length[i]))
						copy_buffers = 1 ;
					if (length > end_length[i])
						if (strncmp(file_lines[i] + length - end_length[i],
							end_line[i],end_length[i])) copy_buffers = 1 ;
					if (copy_buffers) break ;
				}
			} // end do test to remove
		} // end remove old
		if (may_remove_old) if (!copy_buffers && log_file)
			*log_file << "Removed obsolete notice.\n";
		
		// add appropriate copyright notice
		const char ** to_add = (size > byte_boundary) ? mountain_math :
			mountain_math_short ;
		build_temp_lines(to_add,user_flag);
		if (write_base(out,path_name) < 0) return write_error();

		// strip out eny previous comment
		first_file_line_to_copy = copy_buffers ? 0 : next_file_line ;
		int index = first_file_line_to_copy  ;

		const char * l = next_line(index++,in);
		if (l) if (typ->is_first_line(l)) {
			first_file_line_to_copy = index;
			if (log_file) *log_file << "Removed previous first line.\n";
			l = next_line(index++,in);
		}
		if (l) if (typ->may_be_comment_start(l))
		  while (index < max_file_lines) {
			if (!(l = next_line(index++,in))) break ;
			if (typ->may_be_comment_terminate(l)) {
				if (!(l = next_line(index++,in))) break ;
				if (typ->may_be_comment_end(l)) {
					first_file_line_to_copy = index;
					// deleting old notice
					if (log_file) *log_file << "Removed previous notice.\n";
				}
				break ;
			}
			if (!typ->may_be_comment_middle(l)) break ;
		}

	} // done procesing file that we know how to modify

	
	for (int i = 0 ; i < next_file_line; i++) {
		if (i >= first_file_line_to_copy) out << file_lines[i] << "\n" ;
		delete file_lines[i] ;
		file_lines[i] = 0 ;
	}
	next_file_line = 0 ;

	// copy rest of (or entire) file
	for(;;) {
		const buf_size = 8192 ;
		char buf[buf_size];
		in.read(buf,buf_size);
		int read = in.gcount();
		out.write(buf,read);
		if (!out.good()) return write_error();
		if (read < buf_size) break ;
	}
	delete name ;
	return ret ;
}
