/***************************** 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 *************************************/

/*

B.Raoult
ECMWF Oct-93

*/

#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <setjmp.h>
#include <string.h>
#include <strings.h>

#include "Event.h"
#include "MvDebugPrintControl.h"


static option opts[] = {
	{"startup",NULL,"-startup","xserv",
	t_str,sizeof(char*),OFFSET(Options,startup),},
	{"exit",NULL,"-exit",NULL,
	t_str,sizeof(char*),OFFSET(Options,exit),},
	{"port","EVENT_PORT","-port","0",
	t_int,sizeof(int),OFFSET(Options,port)},
	{"environment",NULL,"-environment",NULL,
	t_str,sizeof(char*),OFFSET(Options,env)},
};

long    Event::gUniqID = 0;
fd_set  Event::gFiles;
Options Event::gOpt  = { 0,};

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

void Event::NewService(int soc)
{
	struct sockaddr_in from;
	int     snew;
	int     fromlen;

	fromlen = sizeof(from);
#if defined(Sgi) || defined(Hpux) || defined(__alpha) 
	if((snew = accept(soc, (sockaddr*)&from, &fromlen))<0)
#else
	if( (snew = accept(soc, (sockaddr*)&from, (socklen_t *)&fromlen)) < 0 )
#endif
	{
		/* Interrupted system call : got on SIGCHLD signals */
		if(errno != EINTR) marslog(LOG_WARN|LOG_PERR,"accept");
		return;
	}

	marslog(LOG_INFO,"Got connection");
	if(from.sin_family == AF_INET)
	{
		char *net = inet_ntoa(from.sin_addr);
		struct hostent  *remote;

		remote = gethostbyaddr((char*)&from.sin_addr,
		    sizeof(from.sin_addr),
		    from.sin_family);

			if(remote)
				marslog(LOG_INFO,"incoming host is %s (%s)",
					remote->h_name,net);
			else
				marslog(LOG_INFO,"incoming address is %s",net);

	}
	else
	{
		marslog(LOG_INFO,"connection is not from internet");
		close(snew);
		return;
	}

	if(fcntl(snew,F_SETFD,FD_CLOEXEC)<0)
			marslog(LOG_EROR|LOG_PERR,"fcntl");
	Service *s = new Service(snew);
	s->ProcessIncoming();

}

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

void Event::CheckQueues(void)
{
	WaitingQueue::Process();
	OutputQueue::Process();
}

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

void Event::CheckTimeout(void)
{
	int alive = Service::CheckTimeout();

	// If exit module is given, return
	if(gOpt.exit) return;

	// Else, no 'alive' modules

	if(alive == 0               &&
	   WaitingQueue::Empty()    && 
	   ProcessingQueue::Empty() && 
	   OutputQueue::Empty())
	   {
	         mars.warning = true;

		 marslog(LOG_WARN,"+--------------------------------------------+");
		 marslog(LOG_WARN,"|   No more modules - Event will quit...     |");
		 marslog(LOG_WARN,"| Quitting kills the communication port and  |");
		 marslog(LOG_WARN,"| this will stop all the remaining modules.  |");
		 marslog(LOG_WARN,"| Ignore FATAL messages caused by Dead Port! |");
		 marslog(LOG_WARN,"+--------------------------------------------+");

		 marsexit(0);
	    }
}

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

void Event::Startup()
{
	char buf[1024];
	char host[1024];

	if( getenv( "METVIEW_LOCALHOST" ) )
	{
		strcpy( buf, "localhost" );   //-- for network safe demo on portable pc
		marslog(LOG_INFO, "NOTE: $EVENT_HOST is now \'localhost\'!" );
	}
	else
	{
		gethostname(host,sizeof(host));
		sprintf(buf,"EVENT_HOST=%s",host);
	}
	putenv(strdup(buf));

	sprintf(buf,"EVENT_PORT=%d",gOpt.port);
	putenv(strdup(buf));

	if(gOpt.startup)
	{
		// There's a possible problem if we start Metview in batch mode
		// (-b <path/to/macro>) if the path to the macro contains spaces.
		// It is passed inside $MACRO_STARTUP, and although it contains
		// quotes around the path, these are lost when system() is
		// called (in this case, the invoked shell expands the environment
		// variable, and it seems to be this that loses the quotes).
		// The next few lines are perhaps a bit of a hack, but we get round
		// this specific case by expanding $MACRO_STARTUP before passing it
		// to the system() call. If invoked with the examiner option (-e)
        // then we get $METVIEW_STARTUP, which we have to treat in the same way.

		char *startup;
		char startupBuffer[1024];

		if (!strcmp(gOpt.startup, "$MACRO_STARTUP"))
			startup = getenv("MACRO_STARTUP");

		else if (!strncmp(gOpt.startup, "$METVIEW_STARTUP", 16))  // does it start with $METVIEW_STARTUP?
		{
			startup = getenv("METVIEW_STARTUP");
			sprintf(startupBuffer, "%s%s", getenv("METVIEW_STARTUP"), &gOpt.startup[16]);  // replace with value
			startup = startupBuffer;
		}

		else
			startup = gOpt.startup;
   

		sprintf(buf,"%s &", startup);

		if(system(buf))
		{
			marslog(LOG_EROR|LOG_PERR,"Cannot start %s",startup);
			marslog(LOG_EXIT,"Bye...");
		}
	}
}

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

void Event::CleanUp(int ,void *)
{
	if(gOpt.env) unlink(gOpt.env);
}

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

void Event::Loop()
{

	fd_set  fds;
	static struct timeval smalltime = { 0, 100,  };
	static struct timeval bigtime   = { 60,  0,  };
	int soc;

	/* Start real server */

	if((soc = tcp_server(gOpt.port))<0)
		marslog(LOG_EXIT,"Exiting server");

	
	gOpt.port = port_of(addr_of(soc));
	marslog(LOG_INFO,"Starting server: port is %d",gOpt.port);

	if(gOpt.env)
	{
		FILE *f = fopen(gOpt.env,"w");
		if(f == NULL)
			marslog(LOG_EXIT|LOG_PERR,"Cannot open %s",gOpt.env);
		fprintf(f,"EVENT_PORT=%d\n",gOpt.port);
		fprintf(f,";TMPDIR=%s\n",getenv("TMPDIR"));
		fclose(f);
	}
	

	if(fcntl(soc,F_SETFD,FD_CLOEXEC)<0)
		marslog(LOG_EROR|LOG_PERR,"fcntl");

	FD_ZERO(&gFiles);
	FD_SET(soc,&gFiles);

	Startup();

	for(;;)
	{
		struct timeval curr_delay = bigtime; //-- create a copy

		if(!OutputQueue::Empty() || !WaitingQueue::Empty())
			curr_delay = smalltime;

		//-- use a copy to safeguard original structs!
		struct timeval *delay = &curr_delay;

		fds = gFiles;

#ifdef hpux
//		Feedback from UkMet/SGI:
//		- HP should be happy now with the 'standard' parameters!
//        - but not at ECMWF workstation!!
		switch(select(FD_SETSIZE,(int*)&fds,NULL,NULL,delay) )
#else
		switch(select(FD_SETSIZE,&fds,NULL,NULL,delay) )
#endif
		{

		case -1:
			if(errno != EINTR)
				/* Interrupted system call : got on SIGCHLD signals */
				marslog(LOG_EXIT|LOG_PERR,"select");
			break;

		case 0: /* time out */
			break;

		default:
			if(FD_ISSET(soc,&fds)) /* new service */
				NewService(soc);
			else
				Service::DispatchInput(fds);
			break;
		}

		CheckQueues();
		CheckTimeout();
	}
}

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

void Event::EndOfModule(const char *name)
{
	if(name && gOpt.exit == name)
		marslog(LOG_EXIT,"%s exited, closing all lines",name);
}

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

void Event::AbortIfBatch( const char* )
{
  const char* mode = getenv( "MV_BATCH_NOABORT" );
  if(mode)
    {
      marslog(LOG_INFO,"AbortIfBatch: MV_BATCH_NOABORT set, no abort!");
      return;                                //-- NO ABORT requested by user
    }

  mode = getenv( "METVIEW_MODE" );
  if( mode && strcmp(mode,"batch") == 0 )    //-- abort only batch macros!
    {
      mars.warning = true;

      marslog(LOG_WARN,"+--------------------------------------------+");
      marslog(LOG_WARN,"| A Metview module has crashed in batch mode |");
      marslog(LOG_WARN,"| this will force your batch macro to abort! |");
      marslog(LOG_WARN,"|       Bailing out - Good Bye World!        |");
      marslog(LOG_WARN,"+--------------------------------------------+");
      marslog(LOG_WARN,"PS: set env.var. MV_BATCH_NOABORT if you do not want to abort!");

      marsexit(1);
    }

  marslog(LOG_INFO,"AbortIfBatch: not batch, no abort!");
}

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

void Event::Init(int argc,char **argv)
{
	marsinit(&argc,argv,&gOpt,NUMBER(opts),opts);
	install_exit_proc(CleanUp,NULL);
	mvSetMarslogLevel();
}

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

int main(int argc,char **argv)
{
	Event::Init(argc,argv);
	Event::Loop();
	return 0;
}
