#include "ObjProGen/cpyrght_exe.h"
#ifdef __NT_VC__
#include <io.h>
#include <process.h>
#endif
#include <fstream.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include "portable.h"
#include "mkstr.h"
#include "environ.h"

#include <strstream.h>

enum DbxDrivers {Dbx_gui = 1, Dbx_dsp = 2} ; // these must be powers of 2
int use_xxgdb = 1 ;

int fork();


#define GraphicsDriver "GRAPHICS_DRV"
#define CGIDriver "gui"

void explain_fork_failure(const char * process, int flt, const char *exe)
{
	cerr << "\n\n" ;
	if (!strcmp(process,"DSP")) {
	cerr << "Cannot execute the DSP process.\n" ;
		cerr << 
		"You may not have installed the version you are attempting to execute.\n";
		cerr << 
		"You are attempting to execute the " <<
			(flt ? "floating point" : "16 bit integer")
			<< " version.\n" ;
	} else cerr << "The user interface process could not be executed.\n" ;
	cerr << "This requires file `" << exe << "'.\n" ;
	cerr << "Please check the installation.\n" ;
	perror("The system error message is");
}

EnvironVar TheVars[] = {

	{"OPD_ROOT","/usr/local/lib/opd_root","ObjectProDSP root directory"},
	// This must be first member if this array!!

	{"OPD_TEMP","/tmp","system directory for temporary files"},
	{"OPD_SHELL","/bin/sh","executable for the ANSI standard shell"},
	{"OPD_X_BIN","/usr/bin/X11","directory for X-windows executables"},
	{"HOME",0,"user home directory"},
	{0}
};

class MasterEnvironment: public Environment {
public:
	MasterEnvironment(EnvironVar * var):Environment(var){}
	char * WriteShellExecute(const char ** Args, DbxDrivers drv);
};

MasterEnvironment TheEnvironment(TheVars) ;



/*
 * char * ForkAI = "/usra/paul/develop/dsp_gui/dsp/dsp" ;
 * char * ForkAF = "/usra/paul/develop/dsp_gui/dsp/dspf" ;
 * char * ForkB = "/usra/paul/develop/dsp_gui/gui/gui" ;
 * char * Banner = "/usra/paul/develop/dsp_gui/init.dsp" ;
 */

static const char * myhex(int val)
{
	const BufSize = 128 ;
	static char Buf[BufSize];
	for (int i = 0 ; i < BufSize;i++) Buf[i] = '\0' ;
	ostrstream(Buf, sizeof(Buf)) << hex << val ;
	// cerr << "myhex(" << val << ") = `" << Buf << "'\n" ;
	return Buf ;
}



class Directories {
	const char * ForkARoot ;
	const char * ForkAFRoot ;
	const char * ForkBRoot ;
	const char * BannerRoot ;
	const char * ExeDirectory ;
	ifstream * BannerStream ;
	const char * Base ;
	int IsValidDppDirectory(const char * base) ;
public:
	Directories();
	int SetUp(const char * Argv0);
	void CopyOpeningBanner() ;
	char * GetForkA();
	char * GetForkAF();
	char * GetForkB();
	char * GetForkDbX(DbxDrivers drv);
};

Directories::Directories():
	ForkARoot("opd_dsp16_exe"),
	ForkAFRoot("opd_dsp_exe"),
	ForkBRoot("opd_gui_exe"),
	BannerRoot("banner"),
	ExeDirectory("bin"),
	BannerStream(0),
	Base(0)
{
}

char * Directories::GetForkA()
{
	return Concatenate(Base, "/", ExeDirectory, "/", ForkARoot);
}

char * Directories::GetForkAF()
{
	return Concatenate(Base, "/", ExeDirectory, "/", ForkAFRoot);
}

const char * dbx_cmd = "dbxtra.cmd" ;
int Float = 1;

char * Directories::GetForkDbX(DbxDrivers drv)
{
#ifdef __linux__
	const char * root = getenv("OPD_ROOT") ;
	const char * drive = Float ? "dsp" : "dsp16" ;
	if (drv == Dbx_gui) drive = "gui" ;
	return Concatenate(root,"/bin/opd_",drive,"_exe");
#else
	char * LocBase = Concatenate(Base, "/", ExeDirectory, "/");
	char * Return = 0 ;
	const char * Driver = getenv(GraphicsDriver) ;
	// if (Driver) cerr << "Driver = `" << Driver <<"'\n" ;
	if (drv == Dbx_gui) if (Driver) if (strcmp(Driver,CGIDriver)) {
		delete LocBase ; 
		LocBase = FindDirectory(Driver);
	} else if (drv != Dbx_dsp) { 
		cerr << "Directories::GetForkDbX bad option.\n" ;
		exit(1);
	}
	if (!Return) Return = Concatenate(LocBase , dbx_cmd);
	// cerr << "Directories::GetForkDbX - executing:\n`" << Return << "'\n" ;
	delete LocBase ;
	return Return ;
#endif
}


char * Directories::GetForkB()
{
	char * Return = 0 ;
	const char * Driver = getenv(GraphicsDriver) ;
	// if (Driver) cerr << "Driver = `" << Driver <<"'\n" ;
	if (Driver) if (strcmp(Driver,CGIDriver)) Return = Concatenate(Driver) ; 
	if (!Return) Return = Concatenate(Base, "/", ExeDirectory, "/", ForkBRoot);
	// cerr << "Executing `" << Return << "'\n" ;
	return Return ;
}

void Directories::CopyOpeningBanner()
{
	int Error = 1 ;
	if (BannerStream) if (BannerStream->good()) Error = 0 ;
	if (Error) {
		cerr << "Problems starting execution.\n" ;
		exit(1);
	}
	while (BannerStream->good()) {
		char Buf[1024] ;
		BannerStream->read(Buf,sizeof(Buf));
		cout.write(Buf,BannerStream->gcount());
	}
	cout.flush();
	delete BannerStream ;
	BannerStream = 0 ;
}

int Directories::IsValidDppDirectory(const char * base)
{
	char * Temp = Concatenate(base);
	int length = strlen(Temp);
	if (Temp[length-1] == '/') Temp[length-1] = '\0' ;
	char * ToOpen = Concatenate(Temp,"/",ExeDirectory,"/",
		BannerRoot);
	// cerr << "Looking for directory `" << ToOpen << "'.\n" ;
	BannerStream = new ifstream(ToOpen);
	int Return = BannerStream->good();
	if(!Return) {
		delete BannerStream ;
		BannerStream = 0 ;
	}
	delete Temp ;
	delete ToOpen ;
	return Return ;
}

int Directories::SetUp(const char * Argv0)
{
	const char * base = getenv(TheVars[0].Name);
	if (base) if (IsValidDppDirectory(base)) {
		Base = base ;
		return 1 ;
	}
	// cerr << "Argv0 = " << Argv0 << "\n" ;
	char * ArgvDir = FindDirectory(Argv0);
	// cerr << "ArgvDir = " << ArgvDir << "\n" ;
	if (!ArgvDir) return 0 ;
	int length = strlen(ArgvDir);
	if (!length) return 0 ;
	if (ArgvDir[length-1] == '/') ArgvDir[--length] = '\0' ;
	const char * ArgvSuf = FindSuffix(ArgvDir,ExeDirectory);
	if (!ArgvSuf) return 0 ;
	int Return = 0 ;
	if (!strcmp(ArgvSuf,ExeDirectory)) {
		char * Test = RemoveSuffix(ArgvDir,ExeDirectory) ;
		if(IsValidDppDirectory(Test)) {
			Base = Test ;
			Return = 1 ;
		} else delete Test ;
	}
	delete ArgvDir ;
	return Return ;
}

Directories TheDirectories ;

char * MasterEnvironment::WriteShellExecute(const char ** Args, DbxDrivers drv)
{
	const char * flg ;
	switch (drv) {
case Dbx_dsp:
		flg = "dsp" ;
		break ;
case Dbx_gui:
		flg = "gui" ;
		break ;
default:
		cerr << "Environment::WriteShellExecute bad case.\n" ;
		exit(1);
	}
	const char * Template_head = "/tmp/exec_dsp_" ;
	const char * Template_tail = ".XXXXXX" ;
	char * Template = Concatenate(Template_head,flg,Template_tail);
	mktemp(Template);
	ofstream Exec(Template);
	if (!Exec.good()) {
		cerr << "Cannot create shell script `" << Template << "'.\n" ;
		exit(1);
	}
	char * indirect_file =  0 ;
	ofstream * indirect = 0 ;
#if	defined( __linux__)
	if (!use_xxgdb) { 
		indirect_file =  Concatenate(Template_head,flg,Template_tail);
		mktemp(indirect_file);
	 	indirect = new ofstream(indirect_file);
		if (!indirect->good()) {
			cerr << "Cannot create shell script `" << indirect_file << "'.\n" ;
			exit(1);
		}
	}
#endif
	for (const EnvironVar * var = TheVariables; var->Name; var++) {
		if (var->LocalFlag) continue ;
		const char * VarValue = getenv(var->Name) ;
		if (!VarValue) {
			cerr << "Warning: environmental varaible `" << VarValue <<
				"' not set.\n" ;
			continue ;
		}
		Exec << var->Name << "=" << VarValue << "\n" ;
	}
	const char * argv_val = "arg_val" ;
	Exec << argv_val << "=\"" ;
	for (const char ** ptr = Args; *ptr ; ptr++) {
		if (ptr != Args) Exec << " " ;
		if (**ptr) Exec << *ptr ;
	}
	Exec << "\"\n" ;
	Exec << "export" ;
	for (var = TheVariables; var->Name; var++) 
		Exec << " " << var->Name ;
	Exec << " " << argv_val << "\n" ;
	const char * dbx_cmd = TheDirectories.GetForkDbX(drv) ;
#ifdef __linux__
	if (!use_xxgdb) {
		*indirect << "gdb " << dbx_cmd << "\n" ;
		Exec << "xterm -T \"gdb " << OpdRemoveDirectory(dbx_cmd)
			<< "\" -e bash " << indirect_file << "\n" ;
		*indirect << "rm " << indirect_file << "\n" ; 
		delete indirect ;
		indirect = 0 ;
	} else Exec << "xxgdb " << dbx_cmd << "\n" ;
#else
	ifstream DbXFile(dbx_cmd) ;
	// cerr << "Reading `" << dbx_cmd << "'\n" ;
	while (DbXFile.good()) {
		const max_line = 8192 ;
		char Buf[max_line] ;
		DbXFile.get(Buf,max_line,'\n');
		if (DbXFile.good()) Exec << Buf << "\n" ;
		int line_feed = DbXFile.get();
	}
#endif
	Exec << "rm " << Template << "\n" ; 
/*
 *	cerr << "Environment::WriteShellExecute - command execute:\n`" <<
 *		dbx_cmd << "'\n" ;
 */
	return Template ;
}

// Forked - `exec'ed processes will have the following argv array
/*
 *	0 	-	path name of file execed to (standard convention)
 *	1	-	hex string for Graphics input channel (Dsp out)
 *	2	-	hex string for Dsp input channel (Graphics out)
 *	3	-	hex string for Parent process ID
 *  4	- 	file name for initial state read
 *  5	- 	file name for initial action read
 * 
 */

#define InitName "dsppp" ;

static char * ActDefault = ".opdinit" ; 
static char * ActFileName =  ActDefault ;
static char * LogFileName = "" ;
static char * InitFileName = InitName ;
static int FileSet = 0 ;
static int ActionFileSet = 0 ;
static int LogFileSet = 0 ;

static int ShellExecuteForDbX = 0 ;
static int SuppressExecute = 0 ;



static void ProcessArgs(int argc, char * argv[])
{

/*
 *	cout << "In ProcessArgs, argc = " << argc << "\n" ;
 *	for (int i = 1; i < argc; i++) cout << "argv[" << i <<
 *		"] = \"" << argv[i] << "\"\n" ;
 */
	int NoExecute = 0 ;

	int ltr ;
	char * optstring = "Ttdegicr:a:l:" ;
	while ( ltr = getopt(argc, argv, optstring)) switch (ltr) {
case 'i':
		Float = 0 ;
		break ;
case 't':
		SuppressExecute = 1 ;
		break ;
case 'c':	
		// clear - do no read any state file
		if (!FileSet) InitFileName = "" ;
		if (!ActionFileSet) ActFileName = "" ;
		break ;
case 'r':
		// read specifed state file
		if (FileSet) goto error;
		FileSet = 1 ;
		InitFileName = optarg ;
		break ;
case 'a':
		// read action file
		if (ActionFileSet) goto error ;
		ActionFileSet = 1 ;
		ActFileName = optarg ;
		break ;
case 'l':
		if (LogFileSet) goto error ;
		LogFileSet = 1 ;
		LogFileName = optarg ;
		break ;
case 'e':
		TheEnvironment.Display();
		NoExecute = 1 ;
		break ;
case 'g':
		ShellExecuteForDbX |= Dbx_gui ;
		break ;
case 'd':
		ShellExecuteForDbX |= Dbx_dsp ;
		break ;
case 'T':
		use_xxgdb = 0 ;
		break ;
case EOF:	
		if (NoExecute) {
			cout.flush();
			exit(0);
		}
		return ;
default:
		cerr << "Invalid command line option `" << (char) ltr << "' \n" ;
error:
		cerr << "Execution aborted.\n" ;
case '?':
		cerr << "Usage: opd [-i] [-r FILE]  [-a FILE] [-c] [-e] [-d] [-g] [-T]\n" 
			<< "    -i        use 16 bit integer simulator (floating point is the default)\n"
			<< "    -r FILE   read state file `FILE' (Default " << "`" << InitFileName << ".N')\n"
			<< "              (N is a version number. The SMALLEST number is the most recent.)\n" 
			<< "    -a FILE   read action file `FILE' (Defualt " << ActDefault << ")\n"
			<< "              (The state file is read before the action file.)\n" 
			<< "    -c        only read specifed state and action files (no defaults)\n"
			<< "    -l FILE   log file for validation\n"
			<< "    -e        display environmental variables used by ObjProDSP\n"
			<< "    -d        debug dsp process\n"
			<< "    -g        debug graphics interface process\n"
			<< "    -T        use `gdb' instead  of `xgdb' for debugging\n" ;
		exit(1) ;
	}
}


void main(int argc, char * argv[])
{
	ProcessArgs(argc,argv);
	TheEnvironment.Set();
	if (!TheDirectories.SetUp(argv[0])) {
		cerr << "Cannot find ObjectProDSP root directory.\n" ;
		cerr << "Please check the installation.\n" ;
		cerr << "You must set OPD_ROOT to the installation root directory.\n";
		cerr << "To do this go to that directory and enter:\n" ;
		cerr << ". set_root\n" ;
		exit(1);
	}
	TheDirectories.CopyOpeningBanner();
	int32 Id = getpid() ;
	int32 GuiInId = 0x10000 | Id;
	int32 DspInId = 0x20000 | Id;
	int dsp_gui_filedes[2] ;
	int pipe_err = pipe(dsp_gui_filedes) ;
	if (pipe_err) {
		cerr << "Cannot create first pipe.\n" ;
		perror(argv[0]);
		exit(1);
	}
/*
 *	cerr << "dsp_gui_des = " << dsp_gui_filedes[0] << ", " <<
 *		dsp_gui_filedes[1] << "\n" ;
 */
	int gui_dsp_filedes[2] ;
	pipe_err = pipe(gui_dsp_filedes) ;
	if (pipe_err) {
		cerr << "Cannot create second pipe.\n" ;
		perror(argv[0]);
		exit(1);
	}
/*
 *	cerr << "gui_dsp_des = " << gui_dsp_filedes[0] << ", " <<
 *		gui_dsp_filedes[1] << "\n" ;
 */
	char HexGuiInId[12];
	char HexDspInId[12];
	char HexParentId[12];
	char HexChildId[12];
	char HexDspRead[12];
	char HexDspWrite[12];
	char HexGuiRead[12];
	char HexGuiWrite[12];
	strcpy(HexGuiInId,myhex(GuiInId));
	strcpy(HexDspInId,myhex(DspInId));
	strcpy(HexParentId,myhex(Id));
	strcpy(HexChildId,myhex(0));
	strcpy(HexGuiRead,myhex(dsp_gui_filedes[0]));
	strcpy(HexDspWrite,myhex(dsp_gui_filedes[1]));
	strcpy(HexDspRead,myhex(gui_dsp_filedes[0]));
	strcpy(HexGuiWrite,myhex(gui_dsp_filedes[1]));
	
	const ArgStart = 12 ;
	char ** Argv = new char * [ArgStart+1] ;
	Argv[0] = TheDirectories.GetForkA();
	Argv[1] = HexGuiInId;
	Argv[2] = HexDspInId;
	Argv[3] = HexParentId ;
	Argv[4] = HexChildId ;
	Argv[5] = InitFileName ;
	Argv[6] = HexGuiRead ; // for reading pipe (gui)
	Argv[7] = HexDspWrite ; // for writing pipe (dsp)
	Argv[8] = HexDspRead ; // for reading pipe (dsp)
	Argv[9] = HexGuiWrite ; // for writing pipe (gui)
	Argv[10] = ActFileName ;
	Argv[11] = LogFileName ;
	Argv[ArgStart] = 0 ;
	// for (int i = 0 ; i < ArgStart;i++) cerr << i << ":" << Argv[i] << "\n" ;
	// cout << "Before fork\n" ;
	int ChildId = 0 ; 
	if (!SuppressExecute) ChildId = fork();
	// cout << "After fork\n" ;
	strcpy(HexChildId,myhex(ChildId));
	strcpy(HexParentId,myhex(Id));
	int NeverReturn = 0 ;
	if (ChildId) {
		char * ForkA = TheDirectories.GetForkA() ;
		if (Float) ForkA = TheDirectories.GetForkAF() ;
		Argv[0]=ForkA ;

		if (ShellExecuteForDbX&Dbx_dsp) {
			char * file = TheEnvironment.WriteShellExecute(
				(const char **) Argv,Dbx_dsp);
			// cerr << "Enter: bash " << file << "\n" ;
			NeverReturn = execl("/bin/bash","bash",file,0);
		} else NeverReturn = execv(ForkA,(const char **) Argv);
		explain_fork_failure("DSP",Float,ForkA);
		// cerr << "Attempted to execute:\n`" << ForkA << "'.\n" ;
		exit(2);
	} else {
		Argv[0]=TheDirectories.GetForkB() ;
		// cerr << "ForkB execute is:\n`" << Argv[0] << "'.\n" ;
		ChildId = getpid() ;
		strcpy(HexChildId,myhex(ChildId));

		if (ShellExecuteForDbX&Dbx_gui) {
			char * file = TheEnvironment.WriteShellExecute(
				(const char **) Argv,Dbx_gui);
			// cerr << "Enter: bash " << file << "\n" ;
			NeverReturn = execl("/bin/bash","bash",file,0);
		} else {
			if (SuppressExecute) exit(0);
			// cerr << "ForkB =`" << Argv[0] << "'\n" ;
			NeverReturn = execv(Argv[0],(const char **) Argv);
		}
		explain_fork_failure("GUI",0,Argv[0]);
		// cerr << "Attempted to execute:\n`" << Argv[0] << "'.\n" ;
		exit(2);
	}
		
	
	
}


