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

typedef struct _buffer {
	char* ptr;
	int  size;
	int  pos;
} _buffer;

static int put_bytes(void *data, void* p, unsigned int len)
{
	_buffer *b = (_buffer*)data;
	if(b->pos + len > b->size)
	{
		marslog(LOG_EROR,"free format buffer too small %d %d %d",b->size,b->pos,len);
		return -1;
	}
	memcpy(b->ptr + b->pos,p, len);
	b->pos += len;
	return len;
}

int n = 0;

static int get_bytes(void *data, void* p, unsigned int len)
{
	_buffer *b = (_buffer*)data;
	if(b->pos + len > b->size)
	{
		marslog(LOG_EXIT,"free format buffer too small %d %d %d",b->size,b->pos,len);
		return -1;
	}
	memcpy(p,b->ptr + b->pos, len);
	 /* printf("read %d %02x\n",len,(int)*(char*)p);  */
	b->pos += len;
	return len;
}

static err put_request(mstream* s,const request *r)
{
	int n = 0;
	parameter *p = r->params;

	/* stream_write_start(s,"Request"); */
	stream_write_string(s,r->name);

	/* count */
	
	while(p) { n++; p = p->next; }
	stream_write_int(s,n);

	p = r->params;
	while(p)
	{
		value *v = p->values;

		if( p->values == NULL) 
			marslog(LOG_EXIT,"Internal error: missing value for %s",p->name);

		stream_write_string(s,p->name);

		n = 0;
		while(v) { n++; v = v->next; }
		stream_write_int(s,n);

		v = p->values;
		while(v) 
		{
			stream_write_string(s,v->name);
			v = v->next;
		}
		
		p = p->next;
	}

	/* stream_write_end(s); */

	return s->error;
}

static request* get_request(mstream* s)
{
    request* r = empty_request(stream_read_string(s));
    int      n = stream_read_int(s);
    int      i;
	/* printf("getrequest %d %s\n",n,r->name); */

    for(i = 0; i < n; i++)
    {
        const char* p = stream_read_string(s);
        int m         = stream_read_int(s);
        int j;
        char *q = strcache(p);
	/* printf("getrequest %s %d\n",p,m); */

        for(j = 0; j < m; j++)
            add_value(r,q,"%s",stream_read_string(s));

        strfree(q);
    }

    return r;
}

err encode_free_format(void *buffer, long* length,
	const request* r,const void* blob,long bloblen)
{
	mstream s = {0,};
	_buffer b;

	b.ptr  = (char*)buffer;
	b.size = *length;
	b.pos  = 0;

	s.data  = &b;
	s.write = put_bytes;

	stream_write_int(&s,1);  /* number of requests */
	put_request(&s,r);

	stream_write_blob(&s,blob,bloblen); 

	*length = b.pos;

	return s.error;


}

request* decode_free_format_request(void *buffer, long length)
{
	mstream s = {0,};
	_buffer b;

	b.ptr  = (char*)buffer;
	b.size = length;
	b.pos  = 0;

	s.data  = &b;
	s.read = get_bytes;

	n = stream_read_int(&s);  /* number of requests */
	/* printf("n = %d\n",n); */
	return get_request(&s);
}

long decode_free_format_blob(void *buffer, long length,void* blob,long max)
{
	int n;
	mstream s = {0,};
	_buffer b;
	const void *data;
	long len = 0;

	b.ptr  = (char*)buffer;
	b.size = length;
	b.pos  = 0;

	s.data  = &b;
	s.read = get_bytes;

	n = stream_read_int(&s);  /* number of requests */
	while(n-- > 0)
		free_all_requests(get_request(&s));

	data = stream_read_blob(&s,&len);
	if(len < 0)
	{
		marslog(LOG_EROR,"Read blob failed\n");
		return -1;
	}

	if(len > max)
	{
		marslog(LOG_EROR,"Blob too large: %d > %d",len,max);
		return -1;
	}

	memcpy(blob,data,len);
	return len;

}

err encode_free_format_grib(
	unsigned char* bin, unsigned char* bout,
	fortint* length,fortint maxlen,
	request *r, 
	int marsclass, int type, int stream, char* expver)
{
	unsigned char *in  = bin;
	unsigned char *out = bout;
	long len;
	fortint outlen;
	unsigned char local[10240];
	unsigned char newlocal[10240];
	unsigned char ff[10240];
	int locallen;
	int newlocallen;
	int n;
	int i = 0;
	boolean localdef = false;
	int fixedlensec1 = 40;

	unsigned char subcenter;

	if(in[7] != 1) {
		marslog(LOG_EROR,"encode_free_format_grib only supported for grib edition 1 (edition is %ld)", (long)in[7]);
		return -2;
	}

	/* copy sec0 */
	memcpy(out,in,8);
	marslog(LOG_DBUG,"memcpy in -> out 8 bytes");
	out += 8;
	marslog(LOG_DBUG,"advance 'out' of 8 bytes");
	in  += 8;
	marslog(LOG_DBUG,"advance 'in' of 8 bytes");

	/* sec 1 */
	len = (in[0] << 16) | (in[1] << 8) | (in[2] << 0);
	subcenter = in[25];
	localdef = (len>40);
	fixedlensec1 = localdef ? 40:len;

	marslog(LOG_DBUG,"sec1 len: %d, fixedlensec1 %d",len,fixedlensec1);

	memcpy(out,in,fixedlensec1);
	out[25] = 98; /* sub-center ecmwf */
	if(localdef)
	{
		locallen = len - 40;
		if(locallen > sizeof(local))
		{
			marslog(LOG_EROR,"Original local definition too large %d, sec1 len: %d",locallen,len);
			return -2;
		}

		marslog(LOG_DBUG,"Save original local extension");

		/* save original local extension */
		memcpy(local,in+40,locallen);
	}
	else
	{
		marslog(LOG_DBUG,"GRIB doesn't have local extension");
		locallen = 0;
	}
	in  += locallen + fixedlensec1;
	marslog(LOG_DBUG,"advance 'in' of %d bytes (%d + %d)",locallen + fixedlensec1,locallen,fixedlensec1);

	/* put ours */
	memset(newlocal,0,sizeof(newlocal));
	n = 0;

	newlocal[n++] = 191;
	newlocal[n++] = marsclass; /* class */
	newlocal[n++] = type; /* type */

	newlocal[n++] = (stream >> 8) & 0xff; /* stream */
	newlocal[n++] = (stream >> 0) & 0xff; /* stream */

	newlocal[n++] = expver[0]; /* expver */
	newlocal[n++] = expver[1]; /* expver */
	newlocal[n++] = expver[2]; /* expver */
	newlocal[n++] = expver[3]; /* expver */

	newlocal[n++] = 0; /* spare */
	newlocal[n++] = 0; /* spare */

	newlocal[n++] = 1; /* version */
	newlocal[n++] = 0; /* sub-version */

	newlocal[n++] = subcenter; /* origial sub-center */
	newlocal[n++] = 0; /* origial local def */

	newlocal[n++] = 0; /* spare */
	newlocal[n++] = 0; /* spare */
	newlocal[n++] = 0; /* spare */


	/* encode original local extension */
	len = sizeof(newlocal) - n - 80;
	if(encode_free_format(ff,&len,r,local,locallen))
		marslog(LOG_EXIT,"Encode error");

	newlocal[n++] = (len >> 8) & 0xff; /* len */
	newlocal[n++] = (len >> 0) & 0xff; /* len */

	marslog(LOG_DBUG,"copy original local def encoded, len = %d, pos = %d",len, out-bout + 40 + n); 

	i = 0;
	while(len-- > 0) newlocal[n++] = ff[i++];

	/* add padding */

	/* The section is padded with zeroes to make the overall */
	/* length of the section = 60 + 80*M for some M > 0      */
	while( ((40+n)<60+80) || (((n + 40) - 60) % 80) ) newlocal[n++] = 0;
	marslog(LOG_DBUG,"pad newlocal extension with %d bytes", n - 40);

	if(n > sizeof(newlocal))
		marslog(LOG_EXIT,"Local extension buffer too small");

	newlocallen = n;
	marslog(LOG_DBUG,"newlocal extension length %d bytes",newlocallen);

	memcpy(out+40,newlocal,newlocallen);
	marslog(LOG_DBUG,"copy newlocal extension into 'out+40', %d bytes",newlocallen);

	/* set length */
	len = 40 + newlocallen;
	out[0] = (len >> 16) & 0xff;
	out[1] = (len >> 8) & 0xff;
	out[2] = (len) & 0xff;
	marslog(LOG_DBUG,"set grib section 1 length to %d bytes",len);

	out += 40 + newlocallen;
	marslog(LOG_DBUG,"advance 'out' of %d bytes",40 + newlocallen);

	/* copy rest */

	outlen = (*length) - locallen + newlocallen + (40 - fixedlensec1);;
	if(outlen > maxlen)
		marslog(LOG_EXIT,"Output buffer too small %d > %d",outlen,maxlen);
	marslog(LOG_DBUG,"copy rest, outlen %d, input length %d",outlen,*length);

	marslog(LOG_DBUG,"memcpy in -> out, %d bytes",*length - (in -bin));
	memcpy(out,in,*length - (in -bin));
	*length = outlen;

	/* update length */
	bout[4] = ( (*length) >> 16 ) & 0xff;
	bout[5] = ( (*length) >>  8 ) & 0xff;
	bout[6] = ( (*length) >>  0 ) & 0xff;
	marslog(LOG_DBUG,"update full GRIB length to %d bytes",*length);

	return 0;
}

err decode_free_format_grib(unsigned char* bin, unsigned char* bout,fortint* length,fortint maxlen,request *r)
{
	unsigned char *in  = bin;
	unsigned char *out = bout;
	long len = 0;
	fortint outlen;
	unsigned char local[10240];
	int sec1len;

	if(in[7] != 1) {
		marslog(LOG_EROR,"decode_free_format_grib only supported for grib edition 1 (edition is %ld)", (long)in[7]);
		return -2;
	}

	/* copy sec0 */
	memcpy(out,in,8);
	out += 8;
	in  += 8;

	/* sec 1 */
	sec1len = (in[0] << 16) | (in[1] << 8) | (in[2] << 0);

	if(in[25] != 98 && in[40] != 191)
		marslog(LOG_EXIT,"Grib not local");

	memcpy(out,in,40);

	/* put sub-center back */
	out[25] = in[53];

	len = decode_free_format_blob(in+60,len-60,local,sizeof(local));
	if(len < 0)
		marslog(LOG_EXIT,"Cannot get local extension");

	memcpy(out+40,local,len);

	/* set length */
	len = 40 + len;
	out[0] = (len >> 16) & 0xff;
	out[1] = (len >> 8) & 0xff;
	out[2] = (len) & 0xff;

	out += len;
	in  += sec1len;

	/* copy rest */

	outlen = (*length) - sec1len + len;
	if(outlen > maxlen)
		marslog(LOG_EXIT,"Output buffer too small %d > %d",outlen,maxlen);

	memcpy(out,in,*length - (in -bin));
	*length = outlen;

	/* update length */
	bout[4] = ( (*length) >> 16 ) & 0xff;
	bout[5] = ( (*length) >>  8 ) & 0xff;
	bout[6] = ( (*length) >>  0 ) & 0xff;

	return 0;
}

err original_grib(char* bin, fortint* length)
{
	unsigned char* in = (unsigned char*)bin;
	static char *out = 0;
	static int outlen = 0;
	err e = 0;

	in  += 8;

	if(in[25] != 98 && in[40] != 191)	
		return 0;

	if(out == 0 || outlen < *length)
	{
		if(out) release_mem(out);
		out = reserve_mem(*length);
		outlen = *length;
	}


	e = decode_free_format_grib(
		(unsigned char*) bin, 
		(unsigned char*) out,
		length,outlen,0);

	if(e == 0)
		memcpy(bin,out,*length);
	
	return e;

}
