/*
 * © 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"
#include <ctype.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];
	boolean remote_postproc;
} 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)
	},

	{	"remote_postproc", "MARS_API_REMOTE_POSTPROC", NULL, "1",
		t_boolean, sizeof(boolean), OFFSET(apidata, remote_postproc)
	},
};

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;
	request *r = empty_request(_r->name);

	reqcpy(r, _r);
	if (!api->remote_postproc) {
		// We use the local interpolation
		unset_value(r, "GRID");
		unset_value(r, "AREA");
		unset_value(r, "RESOL");
		unset_value(r, "FRAME");
		unset_value(r, "ROTATION");
		unset_value(r, "BITMAP");
		unset_value(r, "INTERPOLATION");
		unset_value(r, "FORMAT");
		unset_value(r, "PACKING");
		unset_value(r, "TRUNCATION");
		unset_value(r, "INTGRID");
		unset_value(r, "LSM");
	}

	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
