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

#if (defined(__APPLE__) && defined(__MACH__))
#include <ctype.h>
#endif
#include "mars.h"
#include "api.h"


#ifndef NOCURL

typedef struct apidata {
	ecmwf_api* api;
	char* last;
	long long total;
	long long length;
	int tranfer;
	double start;
	char status[80];
	int wmo;
	int content_type_known;
	boolean json;
	char *datasets;
	char *service;
	char service_name[128];
} apidata;

static void apibase_init(void);

static err     apibase_open(void *data,request*,request* env,int);
static err     apibase_close(void *data);
static err     apibase_read(void *data,request *r,void *buffer,long *length);
static err     apibase_write(void *data,request *r,void *buffer,long *length);
static err     apibase_cntl(void *data,int code,void *param,int size);
static boolean apibase_check(void *data,request *r);
static err     apibase_validate(void *data,request*,request*,int);

static option opts[] = {

#if 0
	{"json","MARS_JSON_","-json","1",
		t_boolean, sizeof(boolean),OFFSET(apidata,json)},
#endif

	{"datasets",NULL,NULL,NULL,t_str,
		sizeof(char*),OFFSET(apidata,datasets)},


	{"service","MARS_API_SERVICE",NULL,"services/mars",t_str,
		sizeof(char*),OFFSET(apidata,service)},

};

static base_class _apibase_base = {

	NULL,                          /* parent class */
	"apibase",                     /* name         */

	false,                         /* inited       */

	sizeof(apidata),               /* private_size  */
	NUMBER(opts),                  /* options_count */
	opts,                          /* options       */

	apibase_init,                  /* init          */

	apibase_open,                  /* open          */
	apibase_close,                 /* close         */

	apibase_read,                  /* read          */
	apibase_write,                 /* write         */

	apibase_cntl,                  /* control       */

	apibase_check,                 /* check         */

	NULL,                      	   /* query        */

	NULL,                          /* archive      */
	NULL,                          /* admin        */

	apibase_validate,              /* validate */

};

/* the only 'public' variable ... */

base_class *apibase = &_apibase_base;

static FILE* post_open(apidata*,const char*,const char*,const char*);

static void apibase_init(void)
{
}

static char* proxies[] = {"HTTP_PROXY",
	"FTP_PROXY",
	"HTTPS_PROXY",
	"http_proxy",
	"https_proxy",
	"no_proxy",
	"ftp_proxy",
};


static void typecb(const char* type, void *data)
{
	apidata* api = (apidata*)data;

	marslog(LOG_INFO,"Type is %s", type);
	if(strcmp(type,"application/x-grib") == 0 || strcmp(type,"application/x-bufr") == 0)
	{
		api->wmo = 1;
	}
	else
	{
		api->wmo = 0;
	}
	api->content_type_known = 1;
}

static char *APIservice(void *data, const char *name)
{
	apidata* api = (apidata*)data;
	sprintf(api->service_name,"/%s/%s",api->service,name);
	marslog(LOG_DBUG,"Calling service: %s",api->service_name);
	return api->service_name;
}

static char *json2string(const json_value *j)
{
	static char *buf = NULL;
	off_t n;

	char *tmp = marstmp();
	FILE *f = fopen(tmp,"w");

	if(f == NULL)
	{
		marslog(LOG_EROR|LOG_PERR,"%s",tmp);
		return NULL;
	}
	json_save(j,f);
	fclose(f);

	f = fopen(tmp,"r");
	if(f == NULL)
	{
		marslog(LOG_EROR|LOG_PERR,"%s",tmp);
		return NULL;
	}

	if(buf) free(buf);

	fseek(f,0,2);
	n = ftell(f);
	rewind(f);
	buf = (char*)MALLOC(n+2);

	n = fread(buf,1,n,f);
	buf[n] = 0;

	fclose(f);

	unlink(tmp);

	return buf;
}


static json_value *request2json(const request *r)
{
	json_value *o = json_new_object();
	parameter *p = r->params;
	while(p)
	{
		if(*p->name != '_')
		{
			json_value *a = json_new_array();
			value * v = p->values;
			while(v)
			{
				json_array_push_item(a,json_new_string(v->name));
				v = v->next;
			}
			json_object_set_item(o,p->name,a);
		}
		p = p->next;
	}
	return o;	
}


static void messages(const char* msg, void *data)
{
	apidata* api = (apidata*)data;

	/*
	   if(mars.debug) 
	   {
	   marslog(LOG_DBUG," MESSAGE ==> %s", msg);
	   }
	 */

	if(strncmp(msg,"mars - ",7) == 0) 
	{
		int state = 0;
		int mode  = -1;

		while(*msg) {

			switch(*msg)
			{
				case '-':
					state++;
					if(state == 3 && mode != -1) {
						msg++;
						while(*msg && isspace(*msg)) msg++;
						if(*msg) {
							marslog(mode, "%s [%s]", msg, database_name(api));
						}
					}
					break;

				case 'W':
					if(state == 1 && mode == -1) mode = LOG_WARN;
					break;

				case 'I':
					if(state == 1 && mode == -1) mode = LOG_INFO;
					break;

				case 'E':
					if(state == 1 && mode == -1) mode = LOG_EROR;
					break;

				case 'D':
					if(state == 1 && mode == -1) mode = LOG_DBUG;
					break;

				case 'F':
					if(state == 1 && mode == -1) mode = LOG_EXIT;
					break;


			}
			if(*msg)
				msg++;
		}
	}
}

static err  apibase_open(void *data,request *r,request *env,int mode)
{
	apidata* api = (apidata*)data;
	char path[PATH_MAX];
	const json_value* v;
	const char* key , *email, *url;
	char* tmp;
	char* rs;
	const char *s;
	int i;

	sprintf(path,"%s/.ecmwfapirc", getenv("HOME"));
	if(access(path,F_OK) == 0) {
		v = json_read_file(path);

		key   = json_get_string(json_object_find(v,"key"));
		email = json_get_string(json_object_find(v,"email"));
		url   = json_get_string(json_object_find(v,"url"));
	}
	else
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s", path);
	}

	if(getenv("ECMWF_API_KEY"))   key   = getenv("ECMWF_API_KEY");
	if(getenv("ECMWF_API_URL"))   url   = getenv("ECMWF_API_URL");
	if(getenv("ECMWF_API_EMAIL")) email = getenv("ECMWF_API_EMAIL");

	if(!key) {
		marslog(LOG_EROR,"API KEY is missing");
		return -1;
	}

	if(!url) {
		marslog(LOG_EROR,"API URL is missing");
		return -1;
	}

	if(!email) {
		marslog(LOG_EROR,"API EMAIL is missing");
		return -1;
	}

	/* The service should be configured with the database */
	if(api->datasets)
	{
		const char *dataset = get_value(r,"DATASET",0);
		char s[1024];
		if(!dataset)
		{
			marslog(LOG_EROR,"Please provide a value for DATASET");
			return -1;
		}
		sprintf(s,"%s/%s",api->datasets,dataset);
		api->service = strcache(s);
		api->json = 1;
	}

	api->api = ecmwf_api_create(url, key, email);
	ecmwf_api_set_msg_callback(api->api, messages, api);

	marslog(LOG_INFO,"ECMWF API is at %s", url);
	marslog(LOG_INFO,"Using MARS service at %s",APIservice(api,""));

	for(i = 0; i < NUMBER(proxies); i++) {
		char *p = getenv(proxies[i]);
		if(p) {
			marslog(LOG_INFO,"Proxy settings: %s=%s", proxies[i], p);
			if(*p && p[strlen(p) - 1] == '/') {
				marslog(LOG_WARN,"Libcurl does not use proxy settings that finish with a '/'");
			}
		}
	}

	ecmwf_api_verbose(api->api, mars.debug);
	if(getenv("ECMWF_API_DEBUG"))
		ecmwf_api_verbose(api->api, atol(getenv("ECMWF_API_DEBUG")));

	v = ecmwf_api_call(api->api, "GET",  "/who-am-i", NULL);
	if(!v) {
		marslog(LOG_EROR,"API: Failed to get user information");
		return -1;
	}

	marslog(LOG_INFO,"ECMWF user id is '%s'", json_get_string(json_object_find(v, "uid")));


	v = ecmwf_api_call(api->api, "GET",  APIservice(api,"news"), NULL);
	if(!v) {
		marslog(LOG_EROR,"API: Failed to get service information");
		return -1;
	}

	if((v != NULL) && ((v = json_object_find(v, "news")) != NULL)) {
		char *p = strdup(json_get_string(v));
		char *q = p;
		char *tok = "\n";
		while(strtok(p,tok)) {
			marslog(LOG_INFO,"%s", p);
			p = NULL;
		}
		free(q);
	}

	if(api->json)
	{
		json_value * j = request2json(r);
		v = ecmwf_api_call(api->api, "POST", APIservice(api,"requests"), json2string(j));
		json_free(j);
	}
	else
	{
		rs = request2string(r);
		tmp = MALLOC(strlen(rs)*3);
		v = ecmwf_api_call(api->api, "POST", APIservice(api,"requests"), json_encode_string(request2string(r), tmp));
		FREE(tmp);
	}

	if(!v) {
		marslog(LOG_EROR,"API: Failed to send request");
		return -1;
	}

	marslog(LOG_INFO,"Request ID is %s", json_get_string(json_object_find(v, "name")));
	s = json_get_string(json_object_find(v, "status"));
	if(s && strcmp(s,api->status) != 0) {
		strcpy(api->status,s);
		marslog(LOG_INFO,"Request is %s", s);
	}

	api->last = strcache(ecmwf_api_location(api->api));

	while(ecmwf_api_in_progress(api->api))
	{
		ecmwf_api_in_wait(api->api);
		strfree(api->last);
		api->last = strcache(ecmwf_api_location(api->api));
		v = ecmwf_api_call(api->api, "GET",  ecmwf_api_location(api->api) , NULL);
		if(!v) {
			marslog(LOG_EROR,"API: Failed to get request status");
			return -1;
		}
		s = json_get_string(json_object_find(v, "status"));
		if(s && strcmp(s,api->status) != 0) {
			strcpy(api->status,s);
			marslog(LOG_INFO,"Request is %s", s);
		}
	}

	if(!ecmwf_api_transfer_ready(api->api)) {
		marslog(LOG_EROR,"API: Failed to initiate transfer");
		return -1;
	}

	api->tranfer = 1;

	url = ecmwf_api_location(api->api);

	api->total  = 0;
	api->length = ecmwf_api_transfer_start(api->api, url, typecb, api );

	marslog(LOG_INFO,"Transfering %s from %s", bytename(api->length), url);
	timer_start(get_timer("Transfer", NULL,0));
	api->start = timer_value(get_timer("Transfer", NULL,0));

	return 0;
}

static err  apibase_close(void *data)
{
	apidata* api = (apidata*)data;
	int e = 0;
	if(api->tranfer) {
		timer_stop(get_timer("Transfer", NULL,0),api->length);
		timer_partial_rate(get_timer("Transfer", NULL,0), api->start, api->length);
		if(!ecmwf_api_transfer_end(api->api)) {
			marslog(LOG_EROR,"API: Transfer failed");
			e = -1;
		}
	}
	if(api->last) {
		ecmwf_api_call(api->api, "DELETE", api->last, NULL);
		strfree(api->last);
	}
	if(api->api)
		ecmwf_api_destroy(api->api);

	return e;
}

static long readcb(void *data, void *buffer, long len)
{
	apidata* api = (apidata*)data;

	long size = ecmwf_api_transfer_read(api->api,buffer,len);
	if(size > 0)
		api->total += size;
	return size?size:EOF;
}

static err  apibase_read(void *data,request *r,void *buffer,long *length)
{
	apidata* api = (apidata*)data;
	size_t len = *length;

	if(!api->content_type_known)
	{
		ecmwf_wait_for_data(api->api,1024);
		if(!api->content_type_known) {
			marslog(LOG_WARN,"apibase_read: Did not receive the header information");
			return -12;
		}

	}


	if(api->wmo) {
		err ret =  wmo_read_any_from_stream(data,&readcb,buffer,&len);
		*length = len;
		if(ret != 0 && ret != -1) {
			marslog(LOG_EROR,"apibase_read:  wmo_read_any_from_stream returns %ld", (long)ret);
			if(ret != BUF_TO_SMALL)
				return RETRY_ERR;
		}
		return ret;
	}
	else
	{
		long size = ecmwf_api_transfer_read(api->api,buffer,len);
		if(size > 0) {
			*length = size;
			api->total += size;
			return 0;

		}
		return -1;
	}
}

static err  apibase_write(void *data,request *r,void *buffer,long *length)
{
	return -1;
}

static err  apibase_list(void *data,request *r)
{
	return -1;
}

static err  apibase_cntl(void *data,int code,void *param,int size)
{
	switch(code)
	{
		case CNTL_LIST:
			return -1;
			/*NOTREACHED*/
			break;

		default:
			return -1;
			/*NOTREACHED*/
			break;
	}
	return -1;
}

static boolean  apibase_check(void *data,request *r)
{
	return true;
}

static err apibase_validate(void *data, request *r, request *e, int mode)
{
	err ret = NOERR;

	/*	if(net->validate)*/
	/*		ret = validate_request(r,e,net->validate);*/

	return ret;
}

#else
extern base_class _nullbase;
base_class *apibase = &_nullbase;
#endif
