/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <new>
#include <cstdio>
#include <cctype>
#include "script.h"
#include "MvDebugPrintControl.h"

typedef struct data {
	boolean serve;
	boolean module;
	char    *name;
	char    *script;
	char    *args;
} data;

static option opts[] = {

	{"serve",0,"-serve","0",
	t_boolean,sizeof(boolean),OFFSET(data,serve),},

	{"module",0,"-module","0",
	t_boolean,sizeof(boolean),OFFSET(data,module),},

	{"name",0,"-name","0",
	t_str,sizeof(char*),OFFSET(data,name),},

	{"script",0,"-script",0,
	t_str,sizeof(char*),OFFSET(data,script),},

	{"args",0,"-args",0,
	t_str,sizeof(char*),OFFSET(data,args),},
};

static data setup;
svc   *service = 0;

static void out_of_memory(void)
{
	marslog(LOG_EXIT,"Out of memory");
}

// This class redirect cout to Script::PutMessage

class sbuf : public streambuf { // 76830942

	char line[1024];
	int  n;


// Oops, SUN says streambuf::seekoff(long,ios::seek_dir,int) undefined ????
//#ifdef sun
//	virtual long seekoff(long,ios::seek_dir,int) { return 0;};
//#endif

	virtual int sync(void)
	{
		if(n) {

			line[n] = 0;
			marslog(LOG_INFO,"%s",line);
			n = 0;
		}
		return 0;
	}

	virtual int overflow(int c=EOF)
	{
		if(n >= sizeof(line)-2) sync();
		if( (line[n++] = c) == '\n' ) { n--; sync(); }
		return 0;
	}


public:
	sbuf() : n(0) { };
};


static Value *args_from_file(char *file,int& n)
{
	n = 0;
	if(!file) return 0;

	FILE *f = fopen(file,"r");
	int max = 20;

	if(f == 0)
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s",file);
		return 0;
	}

	// Waring unlink file
	unlink(file);

	Value *v = new Value[max];
	char line[1024];

	while(fgets(line,sizeof(line),f))
	{
		if(*line) line[strlen(line) - 1] = 0;
		if(isdate(line))
			v[n] = Value(Date(line));
		else if(is_number(line))
			v[n] = Value(atof(line));
		else
			v[n] = Value(line);

		n++;

		if(n == max )
		{
			max += n / 2;
			Value *w = new Value[max];
			for(int i = 0;i < n; i++)
				w[i] = v[i];

			delete[] v;
			v = w;
		}
	}

	fclose(f);
	return v;
}

static void serve_macro(svcid *id,request *r,void *data)
{
	int   forked = fork_service(id);
	if(forked > 0) return;

	if(forked == -1 && mars.nofork == false)
	{
		send_later(id);
		return;
	}

	request *param   = (request*)data;
	const char     *path   = get_value(r,"PATH",0);
	const char     *name   = get_value(r,"_NAME",0);
	const char     *action = get_value(r,"_ACTION",0);
	const char     *trace  = get_value(r,"_TRACE",0);
	const char     *wait   = get_value(r,"_WAITMODE",0);
	const char     *lines  = get_value(r,"_SENDLINES",0);
	const char     *pause  = get_value(r,"_PAUSE",0);
	const char     *print  = get_value(r,"_RETURN",0);

	request  *output = get_subrequest(r,"_DEVICE_DRIVER",0); //-- uPlot/PlotMod
	request  *device = get_subrequest(r,"_DEVICE",0);        //-- VisMod
	
	//Arguments to be passed to the Macro (not allowed in param mode!)
	int argNum=count_values(r,"_ARGUMENTS");	
	Value *argVal=0;
	if(!param && argNum > 0 && argNum < 32)
	{	  
		argVal=new Value[argNum];
		for(int i=0; i < argNum; i++)
		{
			const char *t = get_value(r,"_ARGUMENTS",i);
			argVal[i] = Value(t); 
		}
	} 
	else
	{
	  	argNum=0;
	}	
	
	request  *reply  = 0;
	Value    w;

	marslog(LOG_INFO,"Starting %s %s %s",path,name,action);

	Script::SvcId = id;
	mars.outproc  = Script::PutMessage;

	char save[1024];
	getcwd(save,sizeof(save)-1);


//	chdir(getenv("METVIEW_USER_DIRECTORY"));
//	chdir(dirname(name)+1);

	chdir(dirname(path));  // to ensure we always get the dir that the
                           // macro is in, even if run from the editor
	setenv("PWD",dirname(path),1); // change env variable too



	start_timer();

	Script *s = new Script(mbasename(name));

	if(output)                             //-- uPlot/PlotMod
	{
		Script::Output = Value(output);
		free_all_requests(output);
	}
	if(device)                             //-- VisMod
	{
		Script::Device = Value(device);
		free_all_requests(device);
	}

	if(s->Compile(path) == 0)
	{

		Value v(param);
	        	
		// Because the macro editor uses a temp file,
		// we need to set the MacroPath here again

		char buf[1024];
		sprintf(buf,"%s/%s",getenv("METVIEW_USER_DIRECTORY"),name);
		Script::MacroPath(buf);    //It can be updated according to the
		                           // last function compiled
		Script::MacroMainPath(buf);

		s->SetRunMode(action);
		Context::Trace(trace?atoi(trace):0);
		Context::WaitMode(wait?atoi(wait):0);
		Context::SendLines(lines?atoi(lines):0);
		Context::PauseBetweenLines(pause?atoi(pause):0);

		int handler = s->HasHandler(action,param?1:0,&v);

		if(strcmp(action,"syntax") == 0) // check only
			marslog(LOG_INFO,"# Syntax is OK");

		else if( (strcmp(action,"edit") == 0) && !handler)
			marslog(LOG_EROR,"Macro has no 'edit' handler");

		else if( (strcmp(action,"check") == 0) && !handler)
			;

		else {

			if(param) {
				// Support for new MetviewUI
				print_all_requests(param);
			}

			if(!param && argNum > 0)
			{
				w = s->Run(action,argNum,argVal);	  	
			}
			else
			{
			  	w = s->Run(action,param?1:0,&v);
			}
			
			w.Sync();

			if(print)
			{
				cout << "# Macro returns: ";
				w.Print();
				cout << '\n';
			}

			w.GetValue(reply);
		}
	}

	// Flush previous plot commands
	if ( Script::PlotReq )
	{
		// Concatenate Output Driver and Plot request
		request* reqdrive;
		Script::Output.GetValue(reqdrive);
		MvRequest req(reqdrive);
		req = req + Script::PlotReq;

		// Flush
		Value v(PlotterFunction::Plotter(),req);
		v.Sync(); // Force sync

		// empty request
		Script::PlotReq.clean();
	}
	
	set_svc_err(id,s->GetError());

	delete s;
	if(argVal) delete [] argVal;

	mars.outproc  = 0;
	Script::SvcId  = 0;

	char info[1024];
	stop_timer(info);

	marslog(LOG_INFO,"Compile+run = %s",info);
	marslog(LOG_INFO,"End of %s %s %s",path,name,action);

	send_reply(id,reply);
	// Don't free reply, it's go when the Value is destroyed

	chdir(save);
	if(forked != -1)
	{
		destroy_service(id->s);
		marsexit(0);
	}
}

static void serve_param(svcid *id,request *r,void*)
{
	request  *macro = get_subrequest(r,"MACRO",0);
	set_value(macro,"_ACTION","%s",get_value(r,"_ACTION",0));

	request  *output = get_subrequest(r,"_DEVICE_DRIVER",0);
	if (output) {
		set_subrequest(macro, "_DEVICE_DRIVER", output); //-- uPlot/PlotMod
		free_all_requests(output);
	}
	else
	{
		request  *device = get_subrequest(r,"_DEVICE",0);
		if (device) {
			set_subrequest(macro, "_DEVICE", device); //-- VisMod
			free_all_requests(device);
		}
	}

	serve_macro(id,macro,(void*)r->next);
	free_all_requests(macro);
}

static void serve_compute(svcid *id,request *r,void*)
{
	int   forked = fork_service(id);
	if(forked > 0) return;

	if(forked == -1 && mars.nofork == false)
	{
		send_later(id);
		return;
	}


	request  *reply  = 0;
	Value    w;

	const char *name = get_value(r,"_NAME",0);

	if( (reply = pool_fetch(id->s,name,r->name)) )
	{
		send_reply(id,reply);
		free_all_requests(reply);
		if(forked != -1)
		{
			destroy_service(id->s);
			marsexit(0);
		}
	}

	Script::SvcId         = id;
	mars.outproc  = Script::PutMessage;

	Compute s(name,r);

	if(s.GetError() == 0)
	{
		w = s.Run();
		w.GetValue(reply);
	}

	set_svc_err(id,s.GetError());


	if(s.GetError() == 0)
	{
		set_value(reply,"_NAME","%s",name);
		set_value(reply,"_CLASS","%s",r->name);
		pool_store(id->s,name,r->name,reply);
		pool_link_objects(id->s,r);
	}

	send_reply(id,reply);

	mars.outproc  = 0;
	Script::SvcId         = 0;

	if(forked != -1)
	{
		destroy_service(id->s);
		marsexit(0);
	}


}

static void serve_help(svcid *id,request *r,void*)
{
	static int n = 0;
	print_all_requests(r);
	r = empty_request("HELP");
	set_value(r,"TEXT","%s%d", " Some {text} form macro...\n"
		"%text=moretext@xxx"
	,n++);
	send_reply(id,r);
	free_all_requests(r);
}

static void serve_formula(svcid *id,request *r,void*)
{
	int   forked = fork_service(id);
	if(forked > 0) return;

	if(forked == -1 && mars.nofork == false)
	{
		send_later(id);
		return;
	}

	request  *reply  = 0;
	Value    w;

	const char *name = get_value(r,"_NAME",0);

	if( (reply = pool_fetch(id->s,name,r->name)) )
	{
		send_reply(id,reply);
		free_all_requests(reply);
		if(forked != -1)
		{
			destroy_service(id->s);
			marsexit(0);
		}
	}

	Script::SvcId         = id;
	mars.outproc  = Script::PutMessage;

	Formula s(name,r);

	if(s.GetError() == 0)
	{
		Value v(r);
		w = s.Run(0,1,&v);
		w.GetValue(reply);
	}

	set_svc_err(id,s.GetError());

	if(s.GetError() == 0)
	{
		set_value(reply,"_NAME","%s",name);
		set_value(reply,"_CLASS","%s",r->name);
		pool_store(id->s,name,r->name,reply);
		pool_link_objects(id->s,r);
	}

	send_reply(id,reply);

	if(forked != -1)
	{
		destroy_service(id->s);
		marsexit(0);
	}

	mars.outproc  = 0;
	Script::SvcId = 0;

}

int main(int argc,char **argv)
{
	extern int zzdebug;

	marsinit(&argc,argv,&setup,NUMBER(opts),opts);

	mvSetMarslogLevel();  //-- if "quiet log"

	std::set_new_handler(out_of_memory);

	CNumber::SetPrecision( CNumber::GetDefaultPrecision() ); //-- print precision in digits

	if(setup.serve)  //interactive mode
	{
		// Send prints to caller

		sbuf buf;
		ostream out(&buf);
		cout.rdbuf( out.rdbuf() );

		service = create_service("macro"); // progname());
		add_service_callback(service,"HELP",      serve_help,     0);
		add_service_callback(service,"MACRO",     serve_macro,    0);
		add_service_callback(service,"WMACRO",    serve_macro,    0);
		add_service_callback(service,"MACROPARAM",serve_param,    0);
		add_service_callback(service,"COMPUTE",   serve_compute,  0);
		add_service_callback(service,"SAMPLE_FORMULA_DOD",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_DON",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_NOD",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_NON",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_FD",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_FN",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_FDD",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_FDN",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_FND",serve_formula,0);
		add_service_callback(service,"SAMPLE_FORMULA_FNN",serve_formula,0);

		// One macro at a time, for more we use fork()
		set_maximum(service,1);

		service_run(service);

	}
	else if(!setup.script && isatty(0))
	{
		marslog(LOG_INFO,"Terminal mode");
		Script *s = new Terminal("Terminal mode");
		if(s->Compile() == 0)
			s->Run();
		delete s;
	}
	else //batch mode
	{
	  //Script *s = setup.module ? (Script*)new Module(setup.name)
	  //			     : (Script*)new Batch(setup.name);

		//-- Here we rely on $MACRO_STARTUP, set up by Metview startup script:
		//--   argv: 0:'macro' 1:'-script' 2:filename 3:'-args' 4:arglist...
		const char* mname = mbasename(argv[2]);

		Script *s = setup.module ? (Script*)new Module(mname)
					 : (Script*)new Batch(mname);

		int n = 0;
		Value *v = args_from_file(setup.args,n);

		if(s->Compile(setup.script) == 0)
			s->Run(s->GetRunMode(),n,v);

		// Flush previous plot commands
		if ( Script::PlotReq )
		{
			// Concatenate Output Driver and Plot request
			request* reqdrive;
			Script::Output.GetValue(reqdrive);
			MvRequest req(reqdrive);
			req = req + Script::PlotReq;

			// Flush
			Value v(PlotterFunction::Plotter(),req);
			v.Sync(); // Force sync

			// empty request
			Script::PlotReq.clean();
		}
	
		delete s;
		if(v) delete[] v;
	}
	return 0;
}
