/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "mars.h"
#include <time.h>
#include <sys/time.h>

static void get_date_for_schedule(long *year,long *month,long *day,long *hour,long *minute,long *second)
{
	char *schedule_fake_now = getenv("SCHEDULE_FAKE_NOW");
	const char *who  = user(NULL);	
	time_t now;
	struct tm* t;
	time(&now);
	t = gmtime(&now);

	marslog(LOG_DBUG,"-> get_date_for_schedule");
	/* Only 'max' can fake current time in order to test the schedule */
	if((schedule_fake_now != NULL) && EQ(who,"max"))
	{
		request *rnow = read_request_file(schedule_fake_now);
		*year   = atol(get_value(rnow,"year",0));
		*month  = atol(get_value(rnow,"month",0));
		*day    = atol(get_value(rnow,"day",0));
		*hour   = atol(get_value(rnow,"hour",0));
		*minute = atol(get_value(rnow,"minute",0));
		*second = atol(get_value(rnow,"second",0));
		marslog(LOG_WARN,"Date has been changed to: %d%02d%02d %02d:%02d:%02d",*year,*month,*day,*hour,*minute,*second);
		free_all_requests(rnow);
	}
	else
	{
		*year   = (t->tm_year+1900);
		*month  = (t->tm_mon+1);
		*day    = t->tm_mday;
		*hour   = t->tm_hour;
		*minute = t->tm_min;
		*second = t->tm_sec;
	}
	marslog(LOG_DBUG,"<- get_date_for_schedule");
}


static boolean user_category_allowed(request *allow, request *env)
{
	request *r = env;
	int i = 0;
	const char *allowed_category = NULL;
	boolean ok = false;

	while( (allowed_category = get_value(allow,"category",i++)) && !ok)
	{
		int j =  0;
		const char *cat = NULL;

		while((cat = get_value(env,"category",j++)) && !ok)
			ok = EQ(cat,allowed_category);

		if(!ok)
			marslog(LOG_DBUG,"user_category_allowed did not match category '%s'",allowed_category);
		else
			marslog(LOG_DBUG,"User category '%s' allowed to retrieve products before schedule",allowed_category);
	}

	return ok;
}


static void debug_schedule()
{

	if(mars.dissemination_schedule & (SCHEDULE_INFORM & SCHEDULE_INFORM_FUTURE_CHANGE))
	{
		marslog(LOG_WARN,"MARS internal error: SCHEDULE_INFORM & SCHEDULE_INFORM_FUTURE_CHANGE set");
	}

	if(mars.debug)
	{
		if(mars.dissemination_schedule & SCHEDULE_INFORM)
			marslog(LOG_INFO,"mars.dissemination_schedule & SCHEDULE_INFORM set");
		if(mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE)
			marslog(LOG_INFO,"mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE set");
		if(mars.dissemination_schedule & SCHEDULE_FAIL)
			marslog(LOG_INFO,"mars.dissemination_schedule & SCHEDULE_FAIL set");
		if(mars.dissemination_schedule & SCHEDULE_LOG)
			marslog(LOG_INFO,"mars.dissemination_schedule & SCHEDULE_LOG set");
		if(mars.dissemination_schedule & SCHEDULE_MAIL)
			marslog(LOG_INFO,"mars.dissemination_schedule & SCHEDULE_MAIL set");
	}
}


boolean check_dissemination_schedule(request *user, request *env, boolean logstat)
{
	static request *r = 0;
	request *schedule = NULL;
	err e = NOERR;
	int i = 0;
	hypercube *cube = new_hypercube_from_mars_request(user);
	long release_seconds = -1;
	request *max_schedule = NULL;
	request *allow = NULL;
	boolean allowed = false;
	boolean dont_log_users_product_before_schedule = (getenv("MARS_DONT_LOG_USERS_PRODUCT_BEFORE_SCHEDULE") != NULL);
	char buf[80];


	start_timer();
	marslog(LOG_DBUG,"Enter 'check_dissemination_schedule'");

	debug_schedule();

	if(!r) r = read_request_file(mars.dissemination_schedule_file);

	allow = empty_request("allow");
	set_value(allow,"category","product_before_schedule");

	if(!r)
	{
		FILE *f  = mail_open("max@ecmwf.int","Error while reading MARS schedule");
		mail_msg(f,"Error while reading MARS schedule from '%s'",mars.dissemination_schedule_file);
		mail_request(f,"User:",env);
		mail_request(f,"User request:",user);
		mail_close(f);

		marslog(LOG_WARN,"Error while reading MARS schedule from '%s'",mars.dissemination_schedule_file);
		marslog(LOG_WARN,"Please, inform mars@ecmwf.int");

		/* Allowed users will carry on*/
		if( (allowed = user_category_allowed(allow,env)) )
		{
			marslog(LOG_WARN,"MARS schedule ignored");
			return 0;
		}
		return -1;
	}
	else
	{
		schedule = r;
	}

	if(EQ("allow",r->name))
	{
		free_all_requests(allow);
		allow = r;
		schedule = r->next;
	}

	if((allowed = user_category_allowed(allow,env)) && dont_log_users_product_before_schedule)
		return 0; 

	while(schedule)
	{
		const request *schedule_request = get_subrequest(schedule,"request",0);
		const request *release_request  = get_subrequest(schedule,"release",0);
		int release_delta_day = -1;

		if(cube_contains(cube,schedule_request))
		{
			long t = atol(get_value(release_request,"release_seconds",0));
			long d = atol(get_value(release_request,"release_delta_day",0));

			t += d*24*3600;

			if(t > release_seconds)
			{
				release_seconds = t;
				max_schedule = schedule;
			}
			marslog(LOG_DBUG,"check_dissemination_schedule: cube_order %d",cube_contains(cube,schedule_request));

			if(mars.debug)
			{
				marslog(LOG_DBUG,"schedule request is:");
				print_all_requests(schedule_request);

				marslog(LOG_DBUG,"release request is:");
				print_all_requests(release_request);
			}

		}

		schedule = schedule->next;
	}
	marslog(LOG_DBUG,"Release seconds: %ld",release_seconds);

    stop_timer(buf);
	if(*buf) marslog(LOG_INFO,"Verify schedule: %s",buf);

	if(release_seconds != -1)
	{
		double jnow, judate, delta;
		long year, month, day, hour, minute, second;
		long user_date = 0;

		get_date_for_schedule(&year,&month,&day,&hour,&minute,&second);

		jnow   = date_to_julian(year*10000+month*100+day) + hour/24.0 + minute/24.0/60.0 + second/24.0/60.0/60.0;

		/* This causes problems with Climatology, Monthly means and
		   the like, although they should not match any dissemination schedule */
		for(i = 0; i<count_values(user,"DATE");++i)
		{
			const char *p = get_value(user,"DATE",i);
			long d = 0;
			if(is_number(p))
				d = atol(p);
			else
			{
				long julian = 0,second = 0; boolean isjul;
				parsedate(p,&julian,&second,&isjul);
				d = julian_to_date(julian,mars.y2k);
			}
			if(d > user_date)
				user_date = d;
		}
		/* For Hindcasts, date to block is REFDATE */
		for(i = 0; i<count_values(user,"REFDATE");++i)
		{
			const char *p = get_value(user,"REFDATE",i);
			long d = 0;
			if(is_number(p))
				d = atol(p);
			else
			{
				long julian = 0,second = 0; boolean isjul;
				parsedate(p,&julian,&second,&isjul);
				d = julian_to_date(julian,mars.y2k);
			}
			if(d > user_date)
				user_date = d;
		}

		judate = date_to_julian(user_date) + release_seconds/24.0/60.0/60.0;
		delta  = (judate-jnow)* 24*60*60;
		
		marslog(LOG_DBUG,"jnow: %0.30f judate : %0.30f",jnow,judate);
		if(judate <= jnow)
		{
			delta = -delta;
			/* printf("OK since %ld sec=%ld min=%ld hour\n",delta,delta/60,delta/60/60); */
			e = 0;
		}
		else
		{
			struct {
				long second;
				long minute;
				long hour;
				long days;
			} release;
			const request *schedule_request = get_subrequest(max_schedule,"request",0);
			const request *release_request  = get_subrequest(max_schedule,"release",0);
			long release_second = delta + hour*60*60 + minute*60 + second + 0.5;
			long release_minute = 0;
			long release_hour   = 0;
			long release_days   = 0;
			char msg[64];

			if(logstat)
			{
				const char *trigger = getenv("MSJ_PATH");
				boolean suite = (trigger != NULL);

				if(allowed)
				{
					log_statistics("schedule","allowed");
				}
				else
				{
					if(suite)
						log_statistics("schedule","%s",trigger);
					else
						log_statistics("schedule","user");
				}
			}

			if(allowed)
			{
				/* We could print a message to tell allowed users that this data 
				   should not be passed on externally, if HO wants */
				return 0;
			}

			release.second = delta + hour*60*60 + minute*60 + second + 0.5;
			release.minute = 0;
			release.hour   = 0;
			release.days   = 0;

			release.hour    = release.second/60/60;
			release.second -= release.hour*60*60;
			if(release.hour>=24)
			{
				release.days  = (int)(release.hour/24);
				release.hour -= release.days*24;
				sprintf(msg,"after %ld day%s at",release.days,(release.days>1)?"s":"");
			}
			else
				sprintf(msg,"for after");

			release.minute  = release.second/60;
			release.second -= release.minute*60;

			if((mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE) && !allowed)
			{
				marslog(LOG_WARN,"On 1st February 2006, MARS access will be");
				marslog(LOG_WARN,"harmonised with the dissemination schedule");
				marslog(LOG_WARN,"Executing this request after that day will");
				marslog(LOG_WARN,"fail and show the following warning:");
			}

			if((mars.dissemination_schedule & SCHEDULE_INFORM ||
			   mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE) && !allowed)
			{
				int loglevel = LOG_WARN;

				if(mars.dissemination_schedule & SCHEDULE_FAIL)
					loglevel = LOG_EROR;
				marslog(loglevel,"Data not yet available. Scheduled %s %02ld:%02ld:%02ld, (%s)",
							msg, release.hour,release.minute,release.second,
			                get_value(release_request,"release_time",0));

				marslog(loglevel,"User request matches the following schedule rule:");
				marslog(loglevel,"      DATE = %ld",user_date);
				marslog(loglevel,"      TIME = %s",get_value(schedule_request,"TIME",0));
				if(get_value(schedule_request,"STEP",0) != 0)
				{
					if(count_values(schedule_request,"STEP") > 1)
						marslog(loglevel,"      STEP = %s/...",get_value(schedule_request,"STEP",0));
					else
						marslog(loglevel,"      STEP = %s",get_value(schedule_request,"STEP",0));
				}
				marslog(loglevel,"      RELEASE = %s",get_value(release_request,"release_time",0));
			/* printf("NOT OK %ld sec=%ld min=%ld hour\n",delta,delta/60,delta/60/60); */
			}

			if((mars.dissemination_schedule & SCHEDULE_INFORM_FUTURE_CHANGE) && !allowed)
				marslog(LOG_WARN,"Continue with the request execution");

			if(mars.dissemination_schedule & SCHEDULE_MAIL)
			{
				FILE *f  = mail_open(mars.authmail,"MARS request issued before schedule");
				marslog(LOG_DBUG,"check_dissemination_schedule: send email to '%s'",mars.authmail);
				mail_msg(f,"Request issued on %d%02d%02d at %02d:%02d:%02d",year,month,day,hour,minute,second);
				mail_request(f,"User:",env);
				mail_request(f,"Matching schedule:",schedule_request);
				mail_request(f,"User request:",user);
				mail_close(f);
			}
			e = 1;
		}
	}

	marslog(LOG_DBUG,"Exit 'check_dissemination_schedule'");

	free_hypercube(cube);

	return e;
}
