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

 Copyright 2018 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 "inc_iostream.h"
#include <stdarg.h>
#include "Metview.h"
#include "MvException.h"

svcid *_id  = NULL;

static void test_build_request(MvRequest& mode, MvRequest& out);

//! Internal class [used by MvService::addModeService()]
class MvModeService : public MvService {
	MvService *MainService;
	char     *Param;
//
	virtual	void _call_serve(svcid *,request *);
	virtual void serve(MvRequest&,MvRequest&);
public:
	//! Constructor
	MvModeService(char *name,char *param,MvService *main);
};


MvService::MvService(const char* name)
	:MvProtocol(name),
	 HasModes(false),
	 saveToPool_(true)
{
	// Limit to one request at a time (we fork to handle more)
	MvApplication::setMaximum(1);
	mvSetMarslogLevel(); //-- if "quiet log"
}

MvModeService::MvModeService(char *name,char *param,MvService *main)
	: MvService(name)
{
	MainService = main;
	Param       = strcache(param);
}

void MvModeService::serve(MvRequest& in,MvRequest& out)
{
	// Get original request

	MvRequest clean("CLEAN");
	MvRequest mode = getMode();

	if (mars.debug) mode.print();
	// add the new parameter to the previous one.

	MvRequest orig;
	mode.getValue(orig, Param);
	orig = orig + in;
	mode.setValue(Param,orig);

	if (mars.debug) mode.print();

	// Call the original service

	MainService->modeServe(mode,out,Id);


	if (out) {
		out = clean + out;
		test_build_request(mode, out);
		out("_MODE") = mode;
	}


}
static void override(MvRequest& r, MvRequest& t)
{
	int   i = 0;
	const char *val;

	t.unsetParam("_APPLICATION_OVERRIDES");
	while ( val = r("_APPLICATION_OVERRIDES"), i) {
		t(val) = r(val);
		t("_APPLICATION_OVERRIDES") += val;
		i++;
	}

}
static request* getVisdefs(MvRequest& in, MvRequest& ref)
{
	request *visdefs = NULL;
	while (ref) {
		Cached ref_verb(ref.getVerb());
		Cached in_verb(in.getVerb());
		if (in_verb == ref_verb) {
			Cached id1 = (const char*) in("_ID");
			Cached id2 = (const char*) ref("_ID");
			if (id1 == id2) {
				visdefs = ref("_VISDEFS");
				break;
			}
		}
		ref.advance();
	}
	ref.rewind();
	return visdefs;
}

static void test_build_request(MvRequest& mode, MvRequest& out)
{
	MvRequest new_out;
	MvRequest visdefs;
	MvRequest visdef;

	while (out) {
		if (visdefs) {
			Cached visdef_verb(visdefs.getVerb());
			Cached out_verb(out.getVerb());

			if (visdef_verb == out_verb) {
				visdef = visdefs.justOneRequest();
				override(out, visdef);
				new_out = new_out + visdef;
				visdefs.advance();
			}
			else new_out = new_out + visdefs;
		}
		else {
			visdefs = getVisdefs(out, mode);
			new_out = new_out + out.justOneRequest();
		}
		out.advance();
	}

	out = new_out;

}

static void link_objects(request *r)
{
	pool_link_objects(MvApplication::getService(),r);
}

static void _out(int lvl,const char *msg)
{
	if(_id) {
		if(lvl == LOG_EROR || lvl == LOG_EXIT )
			set_svc_msg(_id,"%s",msg);
		else
			send_progress(_id,msg,NULL);
	}
}

//Sends a special progress message back to Desktop to indicate that the
//GUI app started up. Desktop then turns the icon green. Otherwise the icon would stay
//orange and all other actions would be blocked on it until the GUI finishes!!
//Ideally we should send a reply back to Desktop but this CAN ONLY BE SENT when
//the forked process (the GUI) has finished!!!
void MvService::acknowledgeGuiStartup()
{
    MvRequest a("GUI_STARTED");
    send_progress(_id,NULL,a);
}

void MvService::addModeService(char *name,char *param)
{
	HasModes = true;
	new MvModeService(name,param,this);
}

void MvModeService::_call_serve(svcid *id,request *r)
{
	int   forked = fork_service(id);
	if(forked > 0) return;

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


    Id = _id = id;
	mars.outproc = _out;

	MvRequest in(r);
	MvRequest out;

	Error = 0;

	serve(in,out);

	if (mars.debug) out.print();
	svc *id_s = id->s; // send_reply will free id so keep a ptr to id->s
	send_reply(id,out);

	if(forked != -1)
	{
		destroy_service(id_s);
		marsexit(0);
	}

	_id          = NULL;
	mars.outproc = NULL;
}

MvRequest MvService::buildMode(MvRequest& in)
{
	return in;
}

void MvService::_call_serve(svcid *id,request *r)
{
	int   forked = fork_service(id);
	if(forked > 0) return;

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

	Id = _id = id;
	mars.outproc = _out;

	request *u;

	const char *name = iconName();

	if ( saveToPool_ )
	  {
	    if( (u = pool_fetch(MvApplication::getService(),name,r->name)) )
	      {
		svc *id_s = id->s; // send_reply will free id so keep a ptr to id->s
        send_reply(id,u);
		free_all_requests(u);
		if(forked != -1)
		  {
		    destroy_service(id_s);
		    marsexit(0);
		  }
		mars.outproc = NULL;
		return;
	      }
	  }

	MvRequest in(r);
	MvRequest out;

	Error = 0;

	// if the request contains a directive to change the current working
	// directory, then do so
	const char* cwd = in("_CWD");
	if (cwd)
	{
		int cdret = chdir(cwd);
		if (cdret)  // may not be important in most cases
			cout << "WARNING: Could not cd to " << cwd << endl;
		in.unsetParam("_CWD");  // for efficiency
	}

	try
	  {
	    serve(in,out);
	  }
	catch( MvException& e )
	  {
	    Error = 1;
	    cout << "Oops, caught MvException: " << e.what() << endl;
	    marslog(LOG_EROR,"%s",e.what());
	    sendProgress( e.what() );
	  }
	catch(...)
	  {
	    Error = 1;
	    cout << "Something failed: caught unspecified C++ exception..." << endl;
	    sendProgress( "serve function failed (a C++ exception cought)" );
	  }

	if( Error != 0 )
	  setError( Error );

	if(Id == 0) // it was redispatched
	{
		if(forked != -1)
		{
			marsexit(0);
		}
		//_id          = NULL;
		//mars.outproc = NULL;
		//return;
	}


	if(HasModes && out) {
		MvRequest clean("CLEAN");
		out = clean + out;
		out.setValue("_MODE",buildMode(in));
	}

	if(Error == 0 && saveToPool_ )
		pool_store(MvApplication::getService(),name,r->name,out);

	// Look for related objects
	if ( saveToPool_ )
	  link_objects(r);


	if (mars.debug) out.print();

	_id          = NULL;
	mars.outproc = NULL;

	svc *id_s = id->s; // send_reply will free id so keep a ptr to id->s
	send_reply(id,out);

	if(forked != -1)
	{
		destroy_service(id_s);
		marsexit(0);
	}
}


err MvService::callService(char *name,MvRequest& in,MvRequest& out)
{
	err e;
	MvRequest reply(wait_service(Id->s,name,in,&e),false);
	out = reply;
	return e;
}


void MvService::linkTo(char *name)
{
	pool_link(Id->s,iconName(),name);
}

void MvService::modeServe(MvRequest& in,MvRequest& out, svcid *id)
{
	svcid *_id =  Id;
	Id = id;

	try
	  {
	    serve(in,out);
	  }
	catch( MvException& e )
	  {
	    cout << "Oops, caught MvException: " << e.what() << endl;
	    marslog(LOG_EROR,"%s",e.what());
	    sendProgress( e.what() );
	  }
	catch(...)
	  {
	    cout << "Something failed: caught unspecified C++ exception..." << endl;
	    sendProgress( "serve function failed (a C++ exception cought)" );
	  }
	Id = _id;
}

MvServiceFactory* MvServiceFactory::head_ = 0;

MvServiceFactory::MvServiceFactory():
	next_(head_)
{
	head_ = this;
}

void MvServiceFactory::installServices()
{
	MvServiceFactory* x = head_;
	while(x)
	{
		x->installService();
		x = x->next_;
	}
}

