/***************************** 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 <setjmp.h>
#include <string.h>
#include <strings.h>
#include <signal.h>

#ifdef NO_CHEADERS
# include <sys/time.h>
#else
# include <ctime>
#endif

#include "Event.h"


Service *Service::gHead = 0;
Service *Service::gMonitor =0;

//==============================================================================

typedef int (*pvoid) ();

Service::Service(int soc)
{
	next_ = gHead;
	gHead = this;

	soc_ = soc;
	
	Event::AddInput(soc);

//#if defined(sun) || defined(__alpha) ...and Linux/GNU...
	xdrrec_create(&x_, 0,0,(char*)&soc_,(mxdrproc)readtcp,(mxdrproc)writetcp);
//#else
// 	xdrrec_create(&x_, 0,0,(void*)&soc_,(mxdrproc)readtcp,(mxdrproc)writetcp);
//#endif

	name_          = 0;
	host_          = 0;
	user_          = 0;
	ref_           = -1;
	timeout_       = 0;
	alive_         = 0;
	outstanding_   = 0;
	want_progress_ = 0;
	want_message_  = 0;
	valid_         = 0;
	maximum_       = 1;

#ifdef USE_THREADS

	pthread_mutex_init(&mutex_,0);
	pthread_cond_init(&cond_,0);

	stop_          = 0;
	running_       = 0;
	threaded_      = 0;


#endif
}

#ifdef USE_THREADS
void* Service::StartThread(void *self)
{
	((Service*)self)->ThreadProc();
	return 0;
}

void Service::ThreadProc()
{
	// Notify start

	pthread_mutex_lock(&mutex_);

	marslog(LOG_INFO,"Thread started for %s",name_);


	running_ = 1;
	pthread_cond_signal(&cond_);
	pthread_mutex_unlock(&mutex_);

	int more = 1;
	while(more)
	{
		QElem* q = 0;
		pthread_mutex_lock(&mutex_);
		while(!stop_ && queue_.Empty())
		{
			marslog(LOG_INFO,"Thread pthread_cond_wait for %s",name_);
			pthread_cond_wait(&cond_,&mutex_);
		}


		if(stop_)
		{
			marslog(LOG_INFO,"Thread stop received %s",name_);
			more = 0;
		}
		else {
			q = queue_.Pop();
			if( q->Request() )
			   marslog(LOG_INFO,"Thread work for %s %p %s",name_,q,q?q->Request()->name:"");
			else
			{
			   marslog(LOG_INFO,"Thread work for %s %p [empty request => discarded!!!]",name_,q);
			   q = 0;
			}
		}

		pthread_mutex_unlock(&mutex_);

		if(q) 
		{

			marslog(LOG_INFO,"Thread --> encode_request for %s %p",name_,this);

//print_all_requests(q->Request());

			request* r = q->Request();

			encode_request(r,&x_);

			marslog(LOG_INFO,"Thread <-- encode_request for %s",name_);

			pthread_mutex_lock(&mutex_);
			// Because libmars is not reentrant, don;t delete the requests here
			done_.Append(q);
			pthread_mutex_unlock(&mutex_);
		}
	}
	
	
}

#endif

//==============================================================================

int Service::Busy()
{
	return maximum_ != 0 && outstanding_ >= maximum_;
}

//==============================================================================

void Service::HandleRegister(request *r)
{
	static char *service = strcache("service");
	const char *name = get_value(r,"NAME",0);

	if(Find(name))
	{
		marslog(LOG_WARN,"Service %s is already registered",name);
		return;
	}

	ref_       = Event::UniqueID();
	name_      = strcache(get_value(r,"NAME",0));
	user_      = strcache(get_value(r,"USER",0));
	host_      = strcache(get_value(r,"HOST",0));
	pid_       = atol(get_value(r,"PID",0));
	lastcall_  = time(0);
	valid_     = 1;

	marslog(LOG_INFO,"Register: %s %s %s %d as ref %d",
		name_,
	    user_,
	    host_,
	    pid_,
		ref_);

	/* Check for options */
	
	request *u = mars.setup;
	while(u)
	{
		if(u->name == service)
		{
			const char *nme = get_value(u,"name",0);
			if(nme && (name == nme))
			{
				const char *alive   = get_value(u,"alive",0);
				const char *timeout = get_value(u,"timeout",0);
				const char *maximum = get_value(u,"maximum",0);

				if(timeout)  timeout_ = atol(timeout)*60;
				if(alive)    alive_   = atoi(alive);
				if(maximum)  maximum_ = atoi(maximum);

				marslog(LOG_INFO,"%s : Timeout = %d Alive = %d, Max = %d",
					name_,timeout_,alive_,maximum_);

			}
		}
		u = u->next;
	}

#ifdef USE_THREADS
	pthread_attr_t attr;
	pthread_attr_init(&attr);

	const size_t size = 8*1024*1024;
	pthread_attr_setstacksize(&attr,size);

	if(pthread_create(&thread_,&attr,StartThread,this))
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot start thread");
		threaded_ = 0;
	}
	else
	{
		// Wait for thread to be running
		pthread_mutex_lock(&mutex_);
		while(!running_)
			pthread_cond_wait(&cond_,&mutex_);
		pthread_mutex_unlock(&mutex_);

		threaded_ = 1;
		marslog(LOG_INFO,"Thread ok for %s",name_);
	}
#endif

	NotifyMonitor("REGISTER",r);
	u = empty_request("BIRTH");
	reqcpy(u,r);
	BroadcastMessage(u);
	free_all_requests(u);


}

//==============================================================================

Service::~Service()
{

#ifdef USE_THREADS
	
	marslog(LOG_INFO,"Delete service %s",name_);

	if(threaded_)
	{
		pthread_mutex_lock(&mutex_);
		stop_ = 1;
		pthread_cond_signal(&cond_);
		pthread_mutex_unlock(&mutex_);

		void *data = 0;
	marslog(LOG_INFO,"pthread_join service %s",name_);
		pthread_join(thread_,&data);
	}

	pthread_mutex_destroy(&mutex_);
	pthread_cond_destroy(&cond_);

	queue_.RemoveAll();
	done_.RemoveAll();

#endif
	
	
	Service *s = gHead;
	Service *t = 0;

	// Remove self for the list
	while(s)
	{
		if(s == this)
		{
			if(t) t->next_ = next_;
			else  gHead    = next_;
			break;
		}
		t = s;
		s = s->next_;
	}

	// Check if monitor

	if(this == gMonitor)
		gMonitor = 0;

	marslog(LOG_INFO,"Closing service %s",name_?name_:"(null)");
	marslog(LOG_INFO,"%d outstanding replies",outstanding_);

	// Send missing replies

	ProcessingQueue::RemoveAll(this);

	// Kill Output requests
	OutputQueue::RemoveAll(this);

	if(name_)
	{
		NotifyMonitor("EXIT",0);
		request *r = empty_request("DEATH");
		set_value(r,"NAME","%s",name_);
		set_value(r,"USER","%s",user_);
		set_value(r,"HOST","%s",host_);
		set_value(r,"PID","%ld",pid_);
		BroadcastMessage(r);
		free_all_requests(r);
		Event::EndOfModule(name_);
	}

	xdr_destroy(&x_);
	strfree(name_);
	strfree(host_);
	strfree(user_);
	Event::RemoveInput(soc_);
	close(soc_);

}

//==============================================================================

void Service::DispatchInput(fd_set& fds)
{
	Service *s = gHead;
	while(s)
	{
		Service *n = s->next_;
		if(FD_ISSET(s->soc_,&fds))
			s->ProcessIncoming();
		s = n;
	}
}

//==============================================================================

void Service::HandleService (request *r)
{
	const char *target = get_value(r,"TARGET",0);

	// Append request info
	if(get_value(r,"SOURCE",0) == 0)
	{
		set_value(r,"REQ_ID",    "%ld",Event::UniqueID());
		set_value(r,"SOURCE",    "%s", name_);
		set_value(r,"SOURCE_REF","%ld",ref_);
	}


	Service *s = Find(target);
	if(s && !s->Busy()) // Pass to the target service
		s->CallService(r);
	else {
		// Put in waiting queue
		NotifyMonitor("WAIT",r);
		WaitingQueue::Append(r,target);
	}
}

//==============================================================================

static jmp_buf env;

//#if (defined(__cplusplus) || defined(c_plusplus) || !defined(__alpha))
//static void catch_alarm(...)
//#else
//static void catch_alarm(int)
//#endif

// Sun, HP and Alpha have (int) as parameter!
#ifdef sgimips
static void catch_alarm(...)
#else
static void catch_alarm(int)
#endif
{
	marslog(LOG_WARN,"Too many requests : alarm");
	longjmp(env,1);
}

//==============================================================================

#ifdef USE_THREADS

int Service::EncodeRequest(QElem *q)
{
	pthread_mutex_lock(&mutex_);
	marslog(LOG_INFO,"--> Thread EncodeRequest for %s",name_);

	done_.RemoveAll();
	queue_.Append(q);

	pthread_cond_signal(&cond_);
	pthread_mutex_unlock(&mutex_);

	marslog(LOG_INFO,"<-- Thread EncodeRequest for %s",name_);
		
	return 0;
}

#else
int Service::EncodeRequest(request* r)
{
	fd_set fds;
	int ret = 0;
	
	FD_ZERO(&fds);
	FD_SET(soc_,&fds);
	static struct timeval timeout = { 0,10,};

	if(setjmp(env)==0)
	{
		signal(SIGALRM,catch_alarm);
		alarm(20);

//
// 1) Commented out G.A. Gcc does not want this, on HP.
// 2) feedback from UkMet/SGI:
//     - HP should now be happy with "standard" parameters!
// 3) but at ECMWF we do need this ifdef...??!!
//
#if defined(hpux) && !defined(__GNU__)
		switch(select(FD_SETSIZE,0,(int*)&fds,NULL,&timeout))
#else
		switch(select(FD_SETSIZE,0,&fds,NULL,&timeout))
#endif
		{
			case 0:
				// this can be if the module does not listen, especialy messages
				marslog(LOG_EROR,"Timeout sending to %s",name_);
				ret = 1;
				break;

			case -1:
				marslog(LOG_EROR|LOG_PERR,"Error sending to %s",name_);
				ret = 1;
				break;

			default:
				encode_request(r,&x_);
				ret = 0;
				break;
		}
			
		alarm(0);
	} else ret = 1;
	return ret;
}
#endif

//==============================================================================

void Service::SendRequest(request *r)
{
	static char *MESSAGE  = strcache("MESSAGE");
	static char *PROGRESS = strcache("PROGRESS");
	/* check if the module is interested ... */

	if(r)
	{
	
		if((r->name == MESSAGE)  && (want_message_ == 0))
			return;
	
		if((r->name == PROGRESS) && (want_progress_ == 0))
			return;

	}
	OutputQueue::Append(r,this);
}

//==============================================================================

void Service::GotReply(request *r)
{
	SendRequest(r);
}

//==============================================================================

void Service::HandleReply(request *r)
{
	const char *x  = get_value(r,"SOURCE_REF",0);
	long ref = x?atol(x):-1;

	NotifyMonitor("REPLY",r);

	Service *s = Find(ref);
	if(s) s->GotReply(r);

	outstanding_--;

	lastcall_ = time(0);

	// Remove from queue
	
	ProcessingQueue::Remove(r);

}

//==============================================================================

void Service::HandleTimeout(request *r)
{
	const char *t = get_value(r,"TIMEOUT",0);
	timeout_ = (t?atol(t):0) * 60;
	marslog(LOG_INFO,"Timeout for %s : %d seconds",name_,timeout_);
}

//==============================================================================

void Service::HandleAlive(request *r)
{
	const char *t = get_value(r,"ALIVE",0);
	alive_ = t?atoi(t):0;
	marslog(LOG_INFO,"Alive value for %s : %s ", name_,alive_?"on":"off");
}

//==============================================================================

void Service::HandleStop(request *r)
{
	const char *t = get_value(r,"STOP",0);
	int stop = t?atoi(t):0;
	marslog(LOG_INFO,"Stop %d requested by service %s",stop,name_);
	marslog(LOG_INFO,"%s",get_value(r,"INFO",0));
	marsexit(stop);
}

//==============================================================================
void Service::NotifyMonitor(const char *mode,request *r,long ref,const char *name)
{
	static request *u = 0;
	if(!u) u = empty_request("PROGRESS");

	if(gMonitor)
	{
		u->next = r;
		set_value(u,"NAME","%s", name?name:"?");
		set_value(u,"REF", "%ld",ref);
		set_value(u,"MODE","%s",mode);
		gMonitor->SendRequest(u);
	}
	
}

//==============================================================================

void Service::NotifyMonitor(const char *mode,request *r)
{
	NotifyMonitor(mode,r,ref_,name_);
}

//==============================================================================

void Service::HandleMonitor(request *r)
{
	gMonitor = this;
	marslog(LOG_INFO,"%s is a monitor",name_);


	// Loop through services

	r = empty_request("REGISTER");

	Service *s = gHead;
	while(s)
	{
		set_value(r,"NAME","%s", s->name_);
		set_value(r,"HOST","%s", s->host_);
		set_value(r,"USER","%s", s->user_);
		set_value(r,"PID", "%ld",s->pid_);
		set_value(r,"REF", "%ld",s->ref_);
		s->NotifyMonitor("REGISTER",r);
		s = s->next_;
	}
	
	free_all_requests(r);

	// Loop through queues.
	WaitingQueue::NotifyMonitor();
	ProcessingQueue::NotifyMonitor();
}

//==============================================================================

void Service::HandleWant(request *r)
{
	static char *MESSAGE  = strcache("MESSAGE");
	static char *PROGRESS = strcache("PROGRESS");
	const char *kind = get_value(r,"TYPE",0);
	
	if (kind == MESSAGE)  want_message_++;
	if (kind == PROGRESS) want_progress_++;
}

//==============================================================================

void Service::HandleProgress(request *r)
{
	const char *x = get_value(r,"SOURCE_REF",0);
	long ref = x?atol(x):-1;

	NotifyMonitor("PROGRESS",r);
	Service *s = Find(ref);
	if(s) s->SendRequest(r);
}

//==============================================================================

void Service::HandleLater(request *r)
{
	r = r->next;

	marslog(LOG_INFO,"Module %s refuses request",name_);

	// Remove from queue
	outstanding_--;
	ProcessingQueue::Remove(r);

	// Re queue
	NotifyMonitor("WAIT",r);
	WaitingQueue::Later(r,name_);
}

//==============================================================================

void Service::Broadcast(request *r)
{
	Service *s = gHead;
	while(s)
	{
		s->SendRequest(r);
		s = s->next_;
	}
}

//==============================================================================

void Service::BroadcastMessage(request *r)
{
	static request *u = 0;
	if(!u) u = empty_request("MESSAGE");
	u->next = r;
	Broadcast(u);
}

//==============================================================================

void Service::CallService(request *r)
{
	NotifyMonitor("SERVICE",r);
	SendRequest(r);
	ProcessingQueue::Append(r,this);
	outstanding_++;
}

//==============================================================================

void Service::HandleDrop(request *r)
{
	const char *target = get_value(r,"TARGET",0);

	if(get_value(r,"SOURCE",0) == 0)
	{
		set_value(r,"SOURCE",    "%s",name_);
		set_value(r,"SOURCE_REF","%ld",ref_);
	}

	Service *s = Find(target);
	if(s) s->SendRequest(r);
}

//==============================================================================

void Service::HandleMessage(request *r)
{
	if(get_value(r,"SOURCE",0) == NULL)
	{
		set_value(r,"SOURCE",    "%s",name_);
		set_value(r,"SOURCE_REF","%ld",ref_);
	}
	Broadcast(r);
}

//==============================================================================

void Service::HandleGetInfo(request *)
{
	static request *u = NULL;
	if(u == NULL) u = empty_request("REPLY");
	u->next = mars.setup;
	SendRequest(u);
}

//==============================================================================

void Service::ReplyFail(const char *fmt,const char *name,request *r)
{
   const char    *x = get_value(r,"SOURCE_REF",0);
   long   ref = x?atol(x):-1;

   Service *a = Service::Find(ref);
   
   request *u = empty_request("REPLY"); 
   reqcpy(u,r); 
   set_value(u,"ERR_CODE","-42"); 
   set_value(u,"ERROR", fmt,name);

   if(a) {
	   a->NotifyMonitor("REPLY",u); 
	   a->GotReply(u);
   }
   else NotifyMonitor("REPLY",u,ref,get_value(r,"SOURCE",0));

   free_all_requests(u); 
}

//==============================================================================

void Service::HandleStart(request *r)
{
	const char *name = get_value(r,"NAME",0);
	const char *cmd  = get_value(r,"CMD",0);
	if(cmd)
	{
		char buf[1024];
		marslog(LOG_INFO,"Starting command: %s",cmd);
		sprintf(buf,"%s 2>&1 < /dev/null&",no_quotes(cmd));
		system(buf);
	}
	else
		WaitingQueue::StartModule(name);
	SendRequest(NULL);
}

//==============================================================================

void Service::HandleFollowup(request *r)
{
	Service *s = ProcessingQueue::Followup(r,this);

	if(s) s->outstanding_--;
	outstanding_++;
	NotifyMonitor("FOLLOWUP",r);
}

//==============================================================================

Service *Service::Find(const char *name)
{
	Service *s = gHead;
	while(s)
	{
		if(s->name_ == name)
			return s;
		s = s->next_;
	}
	return 0;
}

//==============================================================================

Service *Service::Find(long ref)
{
	Service *s = gHead;
	while(s)
	{
		if(s->ref_ == ref)
			return s;
		s = s->next_;
	}
	return 0;
}

//==============================================================================

void Service::HandleExit(request*)
{
	valid_ = 0;
}

//==============================================================================

void Service::HandleMaximum(request *r)
{
	const char *t = get_value(r,"MAXIMUM",0);
	maximum_ = t?atoi(t):0;
	marslog(LOG_INFO,"Maximum value for %s : %d ", name_,maximum_);
}

//==============================================================================

int Service::CheckTimeout()
{
	time_t now = time(0);
	int alive = 0;

	Service *s = gHead;
	while(s)
	{
		if(s->alive_) alive++;
		
		if(s->outstanding_ == 0 && 
			s->timeout_ != 0 && now - s->lastcall_ > s->timeout_)
		{
			marslog(LOG_INFO,"Service %s has timed out",s->name_);
			delete s;
			return 1;
		}

		s = s->next_;
	}
	return alive;
}

//==============================================================================

void Service::UserMessage(const char* fmt, ...)
{
	va_list al;
	request *r = empty_request("USER_MESSAGE");
	char buf[1024];

	va_start(al, fmt);
	vsprintf(buf, fmt, al);
	va_end(al);

	set_value(r,"INFO","%s",buf);

	BroadcastMessage(r);
	free_all_requests(r);
}

//==============================================================================

typedef void (Service::*reqproc)(request*);

struct {
	const char *name;
	reqproc proc;
} handlers[] = {

	{"EXIT",      &Service::HandleExit,    },
	{"REGISTER",  &Service::HandleRegister,},
	{"FOLLOWUP",  &Service::HandleFollowup,},
	{"SERVICE",   &Service::HandleService, },
	{"MESSAGE",   &Service::HandleMessage, },
	{"REPLY",     &Service::HandleReply,   },
	{"DROP",      &Service::HandleDrop,    },
	{"PROGRESS",  &Service::HandleProgress,},
	{"GETINFO",   &Service::HandleGetInfo, },
	{"MONITOR",   &Service::HandleMonitor, },
	{"TIMEOUT",   &Service::HandleTimeout, },
	{"START",     &Service::HandleStart,   },
	{"WANT",      &Service::HandleWant,    },
	{"LATER",     &Service::HandleLater,   },
	{"ALIVE",     &Service::HandleAlive,   },
	{"STOP" ,     &Service::HandleStop,    },
	{"MAXIMUM",   &Service::HandleMaximum, },

};

//==============================================================================

void Service::Dispatch(request *r)
{
	lastcall_ = time(NULL);
	for(int i=0;i<NUMBER(handlers);i++)
		if(handlers[i].name == r->name)
			(this->*handlers[i].proc)(r);
}

//==============================================================================

void Service::ProcessIncoming()
{
	request *r =  NULL;
	static int first = 1;
	
	if(first)
	{
		for(int i=0;i<NUMBER(handlers);i++)
			handlers[i].name = strcache(handlers[i].name);
		first = 0;
	}

	do{
		r = decode_request(&x_);

		if(r == (void*)-1) /* connection is lost */
			valid_ = 0;
		else {

marslog(LOG_DBUG,"Service::ProcessIncoming(), dispatch request:");
if(mars.debug) print_all_requests(r);

			Dispatch(r);
			free_all_requests(r);
		}
	} while(ref_ >=0 && !xdrrec_eof(&x_));

	if(!valid_)
		delete this;
}
