/***************************** 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 "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);
	}
}

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();
	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)) )
	      {
		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;

	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_;
	}
}

