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

#define N_VO 0
#define N_D  1
#define N_U  2
#define N_V  3

/* TODO: Read from a file * */


typedef struct wind_family {
	long u;
	long v;
	long vo;
	long d;
} wind_family;

static const wind_family WIND_PARAMS[] = {
	{     131  ,     132 ,    138,    155 },
	{  129131  ,  129132 , 129138, 129155 },
    {  200131  ,  200132 , 200138, 200155 },
    {  171131  ,  171132 , 171138, 171155 },
};

struct wind_data {
	struct wind_data *next;
	request *r;
	grib_handle *grib[4];
	boolean given[4];
    int family;
};

struct _wind {
	struct wind_data *data;
	request *r;
	boolean want[4];
	long64* total;
	boolean active;
};


static struct wind_data* find_wind_data(request* r,struct wind_data *data, err *ret)
{
	while(data) {

		if(reqcmp(r,data->r,false) == 0) {
			marslog(LOG_DBUG,"MATCH for find_wind_data");
			return data;
		}

		data = data->next;
	}

	marslog(LOG_DBUG,"NO MATCH for find_wind_data");
	return NULL;
}

static struct wind_data* free_wind_data(struct wind_data *data)
{
	int i;
	struct wind_data* next = data->next;
/* printf("free_wind_data: %x\n",data); */
	for(i=0;i<4;i++)
		if(data->grib[i])
		{
/* printf("grib_handle_delete %x\n",data->grib[i]); */
			grib_handle_delete(data->grib[i]);
		}

	free_all_requests(data->r);
	FREE(data);
	return next;
}

static boolean checkuv(wind *w,void *buffer,long length,err *ret)
{
	long param = 0;
	struct wind_data* data;
	int  which = -1;
	int i;
    int family;

	request* r;
	grib_handle *g = grib_handle_new_from_message_copy(0,buffer,length);
	if(!g) {
		marslog(LOG_EROR,"checkuv: grib_handle_new_from_message_copy() failed");
		*ret = -2;
	}

	*ret = grib_get_long(g,"paramId",&param);

/* printf("grib_handle_new_from_message_copy %x, param %ld\n",g,param); */

	if(*ret) {
		marslog(LOG_EROR,"grib_get_long(paramId) failed: %s",grib_get_error_message(*ret));
/* printf("grib_handle_delete %x\n",g); */
		grib_handle_delete(g);
		return false;
	}

	marslog(LOG_DBUG,"WIND got param %ld",param);

    for(i = 0; i < NUMBER(WIND_PARAMS); i++) {

        if(param == WIND_PARAMS[i].vo) /* VO */
        {
            which = N_VO;
            family = i;
        }

        if(param== WIND_PARAMS[i].d) /* D */
        {
            which = N_D;
            family = i;
        }

        if(param == WIND_PARAMS[i].u) /* U */
        {
            which = N_U;
            family = i;
        }

        if(param == WIND_PARAMS[i].v) /* V */
        {
            which = N_V;
            family = i;
        }

    }

	if(which == -1)
	{
/* printf("grib_handle_delete %x\n",g); */
		grib_handle_delete(g);
		return false;
	}

	r = empty_request(0);
	*ret = handle_to_request(r,g,NULL);
	unset_value(r,"PARAM");
	if(mars.debug)
		print_all_requests(r);

	if(*ret) {
		marslog(LOG_EROR,"handle_to_request failed: %s",grib_get_error_message(*ret));
/* printf("grib_handle_delete %x\n",g); */
		grib_handle_delete(g);
		return false;
	}


	data = find_wind_data(r, w->data, ret);
	if(!data)
	{
		struct wind_data *prev = w->data;
		struct wind_data * d = w->data;
		data = NEW_CLEAR(struct wind_data);
		data->r = r;
        data->family = family;

		while(d)
		{
			prev = d;
			d = d->next;
		}
		if(prev) {
			prev->next = data;
		}
		else  {
			w->data = data;
		}

	}
	else
	{
		free_all_requests(r);
	}

    if(data->family != family) {
		marslog(LOG_EXIT,"wind family mimatch: %d %d", family, data->family);
    }

	if(data->grib[which]) {
		marslog(LOG_EROR,"wind failed: duplicate field");
		*ret = -2;
		print_all_requests(data->r);
/* printf("grib_handle_delete %x\n",g); */
		grib_handle_delete(g);
		return false;
	}

	data->grib[which] = g;

	/* Assumes that VO/D comes after U/V */
	if(data->grib[N_VO] && data->grib[N_D] && !(data->grib[N_U] || data->grib[N_V]))
	{
		long len;
		size_t vo_len, d_len;
		const void *vo_buffer, *d_buffer;
		char *u_buffer;
		char *v_buffer;
		long param;
		double estimate_factor;

		grib_get_message(data->grib[N_VO], &vo_buffer, &vo_len);
		grib_get_message(data->grib[N_D],  &d_buffer,  &d_len);
		if(mars.debug)
		{
			marslog(LOG_EROR,"checkuv: VO D");
			r = empty_request(0);
			grib_to_request(r,(char *)vo_buffer, vo_len);
			print_all_requests(r);
			free_all_requests(r);

			r = empty_request(0);
			grib_to_request(r,(char *)d_buffer, d_len);
			print_all_requests(r);
			free_all_requests(r);
		}

		/* estimate buffer size from VO, factoring in an increase in accuracy */
		len = MAX(vo_len + 4096, ppestimate());
		if (mars.accuracy > 16) {
			estimate_factor = (double) mars.accuracy / 16.;
			len = (long) ceil((double) len * estimate_factor);
		}

		u_buffer = reserve_mem(len);
		v_buffer = reserve_mem(len);

		if(mars.debug) {
			marslog(LOG_DBUG,"makeuv for:");
			print_all_requests(data->r);
		}

        /* clear flag */
        mars.wind_requested_by_server = false;

		*ret = makeuv(
				(char*)vo_buffer,
				(char*)d_buffer,
				d_len,
				u_buffer,
				v_buffer,
				&len);


		if(*ret == 0) {
			grib_handle *u = grib_handle_new_from_message_copy(0,u_buffer,len);
			grib_handle *v = grib_handle_new_from_message_copy(0,v_buffer,len);

			if(!u) {
				marslog(LOG_EXIT,"checkuv: grib_handle_new_from_message_copy() failed");
				*ret = -2;
			}

            param = 0;
            grib_get_long(u,"paramId",&param);
            if(param != WIND_PARAMS[data->family].u)
            {
                marslog(LOG_WARN|LOG_ONCE,"makeuv returns invalid paramId for U %ld (should be %ld)", param, WIND_PARAMS[data->family].u);
                *ret = grib_set_long(u, "paramId", WIND_PARAMS[data->family].u);
            }

			if(!v) {
				marslog(LOG_EXIT,"checkuv: grib_handle_new_from_message_copy() failed");
				*ret = -2;
			}

            param = 0;
            grib_get_long(v,"paramId",&param);
            if(param != WIND_PARAMS[data->family].v)
            {
                marslog(LOG_WARN|LOG_ONCE,"makeuv returns invalid paramId for V %ld (should be %ld)", param, WIND_PARAMS[data->family].v);
                *ret = grib_set_long(v, "paramId", WIND_PARAMS[data->family].v);
            }


/* printf("grib_handle_new_from_message_copy %x, param %ld\n",g,param); */


			if(mars.debug)
			{
				marslog(LOG_EROR,"checkuv: U V");
				r = empty_request(0);
				grib_to_request(r,u_buffer, len);
				print_all_requests(r);
				free_all_requests(r);
				r = empty_request(0);
				grib_to_request(r,v_buffer, len);
				print_all_requests(r);
				free_all_requests(r);
			}

			if(data->grib[N_U]) {
				marslog(LOG_EROR,"wind failed: duplicate U field");
				*ret = -2;
				print_all_requests(data->r);
/* printf("grib_handle_delete %x\n",u); */
				grib_handle_delete(u);

			}
			else {
				data->grib[N_U] = u;
				marslog(LOG_DBUG,"U ready");
			}

			if(data->grib[N_V]) {
				marslog(LOG_EROR,"wind failed: duplicate V field");
				*ret = -2;
				print_all_requests(data->r);
/* printf("grib_handle_delete %x\n",v); */
				grib_handle_delete(v);
			}
			else {
				data->grib[N_V] = v;
				marslog(LOG_DBUG,"V ready");
			}

			release_mem(u_buffer);
			release_mem(v_buffer);


			/* Try to recover some memory */
			for(i = 0 ; i < 4; i++) {
				if(!w->want[i] && data->grib[i]) {
/* printf("grib_handle_delete %x\n",data->grib[i]); */
					grib_handle_delete(data->grib[i]);
					data->grib[i] = NULL;
				}
			}
		}
	}

	return *ret?false:true;
}

wind *wind_new(request *r,long64* total, boolean active)
{
	wind *w =  NEW_CLEAR(wind);

	int i , n = count_values(r,"PARAM"), j;

	w->total = total;
	w->active = active;

	if(active) {

		for(i=0;i<n;i++)
		{
			const char* p = get_value(r,"PARAM",i);
	        long param = 0, table = 0;
	        paramtable(p, &param, &table, false);

	        param += (table == 128 ? 0: table) * 1000;

	        marslog(LOG_DBUG,"wind param.... %d",param);

	        for(j = 0; j < NUMBER(WIND_PARAMS); j++) {

	            if(param == WIND_PARAMS[j].u) {
	                if(w->want[N_U]) marslog(LOG_WARN|LOG_ONCE,"Wind convertion: more than one U-like parameter requested.");
	                w->want[N_U]   = true;
	            }
	            if(param == WIND_PARAMS[j].v) {
	                if(w->want[N_V]) marslog(LOG_WARN|LOG_ONCE,"Wind convertion: more than one V-like parameter requested.");
	                w->want[N_V]   = true;
	            }
	            if(param == WIND_PARAMS[j].vo) {
	                if(w->want[N_VO]) marslog(LOG_WARN|LOG_ONCE,"Wind convertion: more than one VO-like parameter requested.");
	                w->want[N_VO]  = true;
	            }
	            if(param == WIND_PARAMS[j].d) {
	                if(w->want[N_D]) marslog(LOG_WARN|LOG_ONCE,"Wind convertion: more than one D-like parameter requested.");
	                w->want[N_D]   = true;
	            }

	        }
		}
	}

/* printf("wind_new: %x\n",w); */
	return w;
}

void wind_free(wind *w)
{
	int i;

/* printf("wind_free: %x\n",w); */
	if(w)
	{
		struct wind_data *data = w->data;
		while(data) {
			data = free_wind_data(data);
		}
		FREE(w);
	}
}

static boolean wind_advance(wind *w,char *buffer,long *length,err *ret)
{
	struct wind_data* prev = NULL;
	struct wind_data* data = w->data;
	boolean done;
	while(data) {
		int i,j = -1;
		for(i=0;i<4;i++)
			if(w->want[i] && data->grib[i] && !data->given[i])
			{
				j   = i;
				break;
			}

		if(j>=0)
		{
			size_t len;
			const void *msg;
			grib_get_message(data->grib[j],&msg, &len);

			if(*length < len)
			{
				*length = len;
				*ret = -3;
				return true;
			}

			*length = len;
			memcpy(buffer,msg,len);

			data->given[j]  = true;

			done = true;
			for(i=0;i<4;i++)
				if(w->want[i] && !data->given[i])
					done = false;

			if(done)
			{
				struct wind_data* next;

				if(mars.debug) {
					marslog(LOG_DBUG,"WIND free");
					print_all_requests(data->r);
				}

				next = free_wind_data(data);
				if(prev == NULL) {
					w->data = next;
				}
				else
				{
					prev->next = next;
				}
			}

			return true;
		}
		prev = data;
		data = data->next;
	}


	return false;
}

boolean is_wind(long param)
{
	boolean found = false;
	int i = 0;
	for(i = 0; i < NUMBER(WIND_PARAMS); i++) {

        if(param == WIND_PARAMS[i].u) /* U */
        {
			found = true;
        }

        if(param == WIND_PARAMS[i].v) /* V */
        {
			found = true;
        }
		if(found)
			break;

	}
	return found;
}

err wind_next(wind *w,FILE *f,char *buffer,long *length, timer *t)
{
	err ret = 0;
	long inlen = *length;

	if(w->want[N_U] || w->want[N_V])
	{
		for(;;)
		{
			*length = inlen;

			/* Are they ready made fields ? */

			if(wind_advance(w,buffer,length,&ret))
				return ret;

			/* ret = _readany(f,buffer,length); */
			ret = timed_readany(f,buffer,length,t);
			if(ret == 0)
			{
				if(w->total) (*w->total) += *length;
			}
			else
				return ret;

			if(!checkuv(w,buffer,*length,&ret))
			{
				/* other fields */
				return ret;
			}

		}

	}
	else {
		/* ret = _readany(f,buffer,length); */
		ret = timed_readany(f,buffer,length,t);
		if(ret == NOT_FOUND_7777 && mars.ignore_7777)
		{
			marslog(LOG_WARN,"Group 7777 not found, but message included");
			ret = 0;
		}
		if(ret == 0) {
			if(w->total) (*w->total) += *length;
		}
	}

	return ret;
}
