/***************************** 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 "mars.h"
#include <time.h>
#include "MvApplication.h"
#include "MvDebugPrintControl.h"

typedef struct charl {
	struct charl *next;
	char         *name;
} charl;

typedef struct cache {
	struct cache *next;
	char         *name;
	char         *pclass;
	charl        *deps;
	time_t        lastuse;
	request      *r;
} cache;

typedef struct args {
	int timeout;
	} args;


option opts[] = {
	   {"timeout",NULL,"-timeout","0",t_int,sizeof(int), OFFSET(args,timeout)},
	 };

static cache *head = NULL;
static svc   *s = NULL;
static args setup;
static int  retention;

static void destroy(const char *,cache *);

static void send_status(char *pclass,char *name,char *state)
{
	static request *r = NULL;
	if(r == NULL) r = empty_request("STATUS");
	if(pclass) set_value(r,"CLASS","%s",pclass);
	set_value(r,"NAME","%s",name);
	set_value(r,"STATUS","%s",state);
	send_message(s,r);
}

/*
static void birth(svcid *id,request *r,void *data)
{
	cache *c = head;
	while(c)
	{
		send_status(c->pclass,c->name,"READY");
		c = c->next;
	}
}
*/

static void panic(int code,void *data)
{
	cache *c = head;
	while(c)
	{
		cache *p = c->next;
		send_status(c->pclass,c->name,"MODIFIED");
		destroy(progname(),c);
		c = p;
	}
}

static void clean()
{
	time_t now = time(0);
	cache *c = head;
	while(c)
	{
		cache *thiscache = c;
		c = c->next;  /* do this in advance because we might delete c */
		if (difftime(now, thiscache->lastuse) > retention)  destroy("pool: file not used", thiscache);
	}
}

static cache *find(const char *name,const char *pclass)
{
	cache *c = head;
	while(c)
	{
		if(c->name == name && (c->pclass == pclass || pclass == NULL || c->pclass == NULL))
			return c;
		c = c->next;
	}
	return NULL;
}

static void subname(char *name)
{
	cache *c = head;
	int len  = strlen(name);
	while(c)
	{
		if(strncmp(name,c->name,len)==0)
			if(c->name[len] == '/')
				send_status(c->pclass,c->name,"MODIFIED");
		c = c->next;
	}
}

static void destroy(const char *serv,cache *c)
{
	cache *p = head;
	cache *q = NULL;
	request *r;

	if(c == NULL) return;

	while(p) 
	{ 
		if(p == c) 
		{
			if(q) q->next = c->next;
			else head = c->next;
			break;
		}
		q = p;
		p = p->next;
	}

	marslog(LOG_INFO,"[%-10s] - Deleting %s",serv,c->name);

	r = c->r;
	while(r)
	{
		const char *p = get_value(r,"PATH",0);		
		const char *t = get_value(r,"TEMPORARY",0);		
		int tmp = t?atoi(t):0;
		if(p)
		{
			if(tmp)
			{
				unlink(p);
				marslog(LOG_INFO,"File %s deleted",p);
			}
			else
				marslog(LOG_INFO,"File %s not deleted",p);
		}
		r = r->next;
	}

	subname(c->name);

	free_all_requests(c->r);
	strfree(c->name);
	strfree(c->pclass);

	while(c->deps) 
	{ 
		charl *p = c->deps->next; 
		cache *d = find(c->deps->name,NULL);
		if(d) send_status(d->pclass,d->name,"MODIFIED");
		strfree(c->deps->name);
		free(c->deps);
		c->deps = p;
	}

	free(c);
}

static void newstat(svcid *id,request *r,void *data)
{
	const char *state = get_value(r,"STATUS",0);
	const char *name  = get_value(r,"NAME",0);
	cache *c;

	if(name && state)
	{
		if(
			EQ(state,"FAILED") || 
		    EQ(state,"MODIFIED") || 
		    EQ(state,"DELETED"))
				while((c = find(name,NULL)))
					destroy(get_svc_source(id),c);
	}
}

static void store(svcid *id,request *r,void *data)
{
	const char *name = get_value(r,"NAME",0);
	const char *pclass = get_value(r,"CLASS",0);
	if(name && r && r->next)
	{
		cache *c;

#ifdef NO_DUPS
		destroy(get_svc_source(id),find(name,pclass));
#else
		if(find(name,pclass))
			marslog(LOG_WARN,"[%-10s] - Name %s is duplicated",
				get_svc_source(id),name);
#endif

		c        = NEW_CLEAR(cache);
		c->name  = strcache(name);
		c->pclass = strcache(pclass);
		c->r     = clone_all_requests(r->next);
		c->lastuse = time(0);
		c->next  = head;
		head     = c;
		marslog(LOG_INFO,"[%-10s] - Caching  %s",get_svc_source(id),
			c->name);
	}
	send_reply(id,NULL);
}

static void fetchcb(svcid *id,request *r,void *data)
{
	const char *name  = get_value(r,"NAME",0);
	const char *pclass = get_value(r,"CLASS",0);
	cache *c   = find(name,pclass);

	if (!name)  c = NULL;
	marslog(LOG_INFO,"[%-10s] - Object %s [%s] %s",
	get_svc_source(id),name,pclass,c?"returned":"not cached");
	if(c) {
		c->lastuse = time(0);
		send_reply(id,c->r);
	}
	else  send_reply(id,NULL);
}

static void depend(svcid *id,request *r,void *data)
{
	const char *name1 = get_value(r,"NAME1",0);
	const char *name2 = get_value(r,"NAME2",0);
	cache *c    = find(name2,NULL);

	if(c==NULL)
		marslog(LOG_INFO,"[%-10s] - Link %s not found",
			get_svc_source(id),name1);
	else
	{
		charl *p = NEW_CLEAR(charl);
		marslog(LOG_INFO,"[%-10s] - Link %s and %s",get_svc_source(id),
			name1,name2);
		p->next = c->deps;
		c->deps = p;
		p->name = strcache(name1);
	}
	send_reply(id,NULL);
}

int main(int argc,char **argv)
{
	//time_t now,then;
	//int day = 60*60*24;
	//int sec;
	MvRequest pref;
	const char* r;

	marsinit(&argc,argv,&setup,NUMBER(opts),opts);
	mvSetMarslogLevel();       //-- if "quiet log"
	
	s = create_service("pool");

	pref       = MvApplication::getPreferences();
	r          = get_value(pref, "DATA_CACHE_RETENTION_PERIOD", 0);
	retention  = (r) ? atoi(r) : setup.timeout;
	s->timeout = retention * 60;


#if 0
	now  = time(0);
	then = ((now/day)+1)*day;
	/* marslog(LOG_INFO,"It is now  %s",ctime(&now)); */
	/* marslog(LOG_INFO,"Suicide at %s",ctime(&then)); */

	if( (r = getenv("METVIEW_MODE")) && strcmp(r,"batch") != 0)
	{
		alarm(sec = then-now);
		marslog(LOG_INFO,"Suicide in %d hour(s) %d minutes(s) %d second(s)",
			sec/(60*60) , (sec % (60*60)) / 60 , sec % 60);
	}
#endif

	add_service_callback(s,"STORE",	store,   NULL);
	add_service_callback(s,"FETCH",	fetchcb,   NULL);
	add_service_callback(s,"LINK",	depend,  NULL);

	add_message_callback(s,"STATUS",newstat,NULL);
	/* add_message_callback(s,"BIRTH", birth  ,NULL); */

	install_exit_proc(panic,NULL);
    while (1)  {
	   clean();
	   service_run(s);
	 }
}
