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

/*

B.Raoult
ECMWF Oct-93

*/

#include <ctype.h>
#include "mars.h"

#ifdef FORTRAN_UPPERCASE
#define bufrex_ BUFREX
#endif
#ifdef FORTRAN_NO_UNDERSCORE
#define bufrex_ bufrex
#endif


#ifdef FORTRAN_UPPERCASE
#define busel_ BUSEL
#endif
#ifdef FORTRAN_NO_UNDERSCORE
#define busel_ busel
#endif

#define MASK(n) ((unsigned char)(~(0xff << (n))))

unsigned long getbits(unsigned char* p, int skip, int len) {
    int s             = skip % 8;
    int n             = 8 - s;
    unsigned long ret = 0;

    p += (skip >> 3); /* skip the bytes */

    if (s) {
        ret = (MASK(n) & *p++);
        len -= n;
    }

    while (len >= 8) {
        ret = (ret << 8) + *p++;
        len -= 8;
    }

    ret = (ret << len) + (*p >> (8 - len));

    return ret;
}


/* Use these macros only here...
Some BUFR messages have a wrong length (65535) in the key
on purpose. This indicates the length has to be read from
section 0 instead of from the key. Ask Milan for more
details */

#define WRONG_KEY_LENGTH 65535
#ifdef LITTLE_END
#define KEY_LENGTH(k) ((unsigned int)getbits((unsigned char*)k->length, 0, 16))
#else
#define KEY_LENGTH(k) (k->length)
#endif

unsigned long key_length(const char* buffer, const packed_key* k) {
    unsigned long length = KEY_LENGTH(k);
    if ((length == WRONG_KEY_LENGTH) || (length == 0)) {
        length = (unsigned long)getbits((unsigned char*)(buffer + 4), 0, 24);
    }
    return length;
}

void set_key_length(packed_key* key, unsigned long keylength) {
    if (keylength >= WRONG_KEY_LENGTH)
        return;
    SET_KEY_LENGTH(*key, keylength);
}

boolean get_packed_key(char* buffer, packed_key* k) {
    int version = buffer[7];
    // marslog(LOG_DBUG, "getkey version %d\n", version);

    /* skip section 0 , check for BUFR version */
    if (version > 1)
        buffer += 8;
    else
        buffer += 4;

    /* check if the key is present */

    // marslog(LOG_DBUG, "SEC1_FLAGS %d\n", (int)SEC1_FLAGS(buffer, version));

    if (SEC1_FLAGS(buffer, version)) {
        buffer += SEC1_LENGTH(buffer); /* skip section 1 */
        buffer += 4;                   /* skip header of section 2 */
        /* now copy the packed key minus the size of two_byte_n_of_subset */
        memcpy((void*)k, (void*)buffer, sizeof(packed_key));
        return true;
    }

    return false;
}

boolean get_packed_section_1(char* buffer, packed_section_1* section) {
    /* skip section 0 , check for BUFR version */
    if ((unsigned char)buffer[7] > 1)
        buffer += 8;
    else
        buffer += 4;

    memcpy((void*)section, buffer, sizeof(packed_section_1));

    return true;
}

int subset_count(char* buffer) {
    int version = buffer[7];

    /* skip section 0 , check for BUFR version */
    if (version > 1)
        buffer += 8;
    else
        buffer += 4;

    /* check if the key is present */

    if (SEC1_FLAGS(buffer, version)) {
        char* p;
        unsigned long sec2len;

        buffer += SEC1_LENGTH(buffer); /* skip section 1 */
        p = buffer;                    /* p pointing to section 2 */
        /*--------------------------------------------------------*/
        /* There is a number of subsets field in the packed key   */
        /* which is either 8 bits or 16 bits long depending on	  */
        /* subtypes. It is also present at the beginning of 	  */
        /* section 3 where it is coded using 16 bits.             */
        /*--------------------------------------------------------*/
        sec2len = getbits((unsigned char*)p, 0, 24); /* get len of section 2 */
        p += sec2len;                                /* p pointing to section 3 */
        /*------------------------------------------------------------*/
        /* get the number of subsets skipping the length of section 3 */
        /* ( 3 bytes) and a byte set to 0 							  */
        /*------------------------------------------------------------*/
        return getbits((unsigned char*)p, 32, 16);
    }

    return 0;
}

int bufr_sat_id(char* buffer, packed_key* k) {
    unsigned char* p = (unsigned char*)k;
    int id;
    int offset = 192;

    int n = subset_count(buffer);
    if (n > 255 || (k->header.subtype >= 121 && k->header.subtype <= 130)
        || k->header.subtype == 31) {
        offset = 200;
    }

    id = p[offset / 8];
    id = id << 8;
    id += p[(offset / 8) + 1];

    return id;
}

void set_bufr_sat_id(char* buffer, int id, packed_key* k) {
    char* p    = (char*)k;
    int offset = 192;

    int n = subset_count(buffer);
    if (n > 255 || (k->header.subtype >= 121 && k->header.subtype <= 130)
        || k->header.subtype == 31) {
        offset = 200;
    }

#ifdef LITTLE_END
    p[offset / 8 + 1] = id >> 8;
    p[offset / 8]     = id;
#else
    p[offset / 8]     = id >> 8;
    p[offset / 8 + 1] = id;
#endif
}

static packed_key* get_packed_key_addr(char* buffer) {
    int version = buffer[7];

    /* skip section 0 , check for BUFR version */
    if (version > 1)
        buffer += 8;
    else
        buffer += 4;

    /* check if the key is present */

    if (SEC1_FLAGS(buffer, version)) {
        buffer += SEC1_LENGTH(buffer); /* skip section 1 */
        buffer += 4;                   /* skip header of section 2 */
        return (packed_key*)buffer;
    }

    return NULL;
}

boolean replace_key(char* buffer, packed_key* key) {
    packed_key* k = get_packed_key_addr(buffer);
    if (k) {
        memcpy(k, key, sizeof(packed_key));
        return true;
    }
    return false;
}

boolean patch_key_date(char* buffer, int y, int m, int d, int H, int M,
                       int S) {
    packed_key* k = get_packed_key_addr(buffer);
    if (k) {

        packed_key p;
        memcpy(&p, k, sizeof(packed_key));

        if (H == 24 && M == 0 && S == 0) {
            H = 23;
            M = 59;
            S = 59;
        }

#ifdef LITTLE_END
        marslog(LOG_WARN, "Set date & time not implemented on little-endian architecture");
        marslog(LOG_EXIT, "If needed, bufr.c needs modifications. Exiting...");
#else
        p.header.year   = y;
        p.header.month  = m;
        p.header.day    = d;
        p.header.hour   = H;
        p.header.minute = M;
        p.header.second = S;
#endif
        memcpy(k, &p, sizeof(packed_key));
        marslog(LOG_INFO, "New date %d-%d-%d %d:%d:%d (%d)",
                y, m, d, H, M, S, sizeof(packed_key));

        return true;
    }

    return false;
}

boolean patch_key_ident(char* buffer, int ident) {
#if 0
	packed_key* k = get_packed_key_addr(buffer);
	if (k)
	{
		char s[10];
		char p[10];

		packed_key p;
		memcpy(&p,k,sizeof(packed_key));

		/* put the ident value followed by spaces in 9 chars.  */
		sprintf(p,"%d",ident);
		memset(s,' ',9);
		s[9] = 0;
		memcpy(s,p,strlen(p));

/*  NOT FINISHED */

		memcpy(k,&p,sizeof(packed_key));

		return true;
	}
#endif

    return false;
}

boolean patch_key_length(char* buffer, unsigned int new_value) {
    packed_key* k = get_packed_key_addr(buffer);

    if (new_value == WRONG_KEY_LENGTH) {
        marslog(LOG_WARN, "Cannot set rdbkey length to %d", new_value);
        return false;
    }

    if (k) {
        packed_key p;
        memcpy(&p, k, sizeof(packed_key));
        SET_KEY_LENGTH(p, new_value);
        memcpy(k, &p, sizeof(packed_key));
        return true;
    }

    return false;
}

boolean shift_packed_key_ident(char* buffer) {
    packed_key* k = get_packed_key_addr(buffer);
    if (k) {
        char* ident = KEY_IDENT(k);
        int i       = 0;
        int j;
        while (i < 9 && ident[i] == ' ') /* ident len = 9 */
            i++;
        if (i < 9)
            memcpy(ident, &ident[i], 9 - i);
        for (j = 8; j > 9 - i; j--)
            ident[j] = ' ';
        return true;
    }

    return false;
}

void set_bufr_mars_type(request* r, const packed_key* key) {
    set_value(r, "TYPE", "OB");

    if (KEY_TYPE(key) == BUFR_TYPE_TRACK) {
        set_value(r, "TYPE", "TF");
    }
}

err bufr_to_request(request* r, char* buffer, long length) {
    packed_key ky;
    packed_key* k = &ky;
    char buf[10];
    int i;
    char* p;

    if (!get_packed_key(buffer, k)) {
        marslog(LOG_WARN, "BUFR message without key found");
        return -1;
    }

    if (mars.debug)
        print_packed_key(buffer, k);

    set_bufr_mars_type(r, k);

    set_value(r, "_BUFR_TYPE", "%d", KEY_TYPE(k));
    set_value(r, "_BUFR_SUBTYPE", "%d", KEY_SUBTYPE(k));
    set_value(r, "OBSTYPE", "%d", KEY_SUBTYPE(k));

    set_value(r, "DATE", "%04d%02d%02d", KEY_YEAR(k), KEY_MONTH(k), KEY_DAY(k));
    set_value(r, "TIME", "%02d%02d", KEY_HOUR(k), KEY_MINUTE(k));


    if (IS_SATTELITE(k)) {
        /* set_value(r,"_IS_SATTELITE","true"); */

        set_value(r, "_LATITUDE1", "%.4f", UNLAT(KEY_LATITUDE1(k)));
        set_value(r, "_LATITUDE2", "%.4f", UNLAT(KEY_LATITUDE2(k)));
        set_value(r, "_LONGITUDE1", "%.4f", UNLON(KEY_LONGITUDE1(k)));
        set_value(r, "_LONGITUDE2", "%.4f", UNLON(KEY_LONGITUDE2(k)));
        /* set_value(r,"IDENT","%d",KEY_IDSAT(k)); */
    }
    else {
        /* set_value(r,"_IS_SATTELITE","false"); */

        set_value(r, "_LATITUDE1", "%.4f", UNLAT(KEY_LATITUDE(k)));
        set_value(r, "_LATITUDE2", "%.4f", UNLAT(KEY_LATITUDE(k)));
        set_value(r, "_LONGITUDE1", "%.4f", UNLON(KEY_LONGITUDE(k)));
        set_value(r, "_LONGITUDE2", "%.4f", UNLON(KEY_LONGITUDE(k)));

        memcpy(buf, KEY_IDENT(k), 9);
        i      = 0;
        p      = buf;
        buf[9] = 0;

        while (*p) {
            if (isalnum(*p))
                buf[i++] = *p;
            p++;
        }
        buf[i] = 0;
        set_value(r, "IDENT", buf);
    }

    set_value(r, "_NOBS", "%d", KEY_NOBS(k));
    set_value(r, "_RDBDAY", "%d", KEY_RDBDAY(k));
    set_value(r, "_RDBHOUR", "%d", KEY_RDBHOUR(k));
    set_value(r, "_RDBMINUTE", "%d", KEY_RDBMINUTE(k));
    set_value(r, "_RDBSECOND", "%d", KEY_RDBSECOND(k));

    set_value(r, "_RECDAY", "%d", KEY_RECDAY(k));
    set_value(r, "_RECHOUR", "%d", KEY_RECHOUR(k));
    set_value(r, "_RECMINUTE", "%d", KEY_RECMINUTE(k));
    set_value(r, "_RECSECOND", "%d", KEY_RECSECOND(k));

    set_value(r, "_CORR1", "%d", KEY_CORR1(k));
    set_value(r, "_CORR2", "%d", KEY_CORR2(k));
    set_value(r, "_CORR3", "%d", KEY_CORR3(k));
    set_value(r, "_CORR4", "%d", KEY_CORR4(k));

    set_value(r, "_PART1", "%d", KEY_PART1(k));
    set_value(r, "_PART2", "%d", KEY_PART2(k));
    set_value(r, "_PART3", "%d", KEY_PART3(k));
    set_value(r, "_PART4", "%d", KEY_PART4(k));

    set_value(r, "_QC", "%d", KEY_QC(k));


    if (mars.debug)
        print_one_request(r);

    return 0;
}

boolean verify_bufr_key(const char* buffer, long length, const packed_key* key) {
    boolean ok            = true;
    unsigned long klength = key_length(buffer, key);

    if (klength != length) {
        marslog(LOG_EROR, "Wrong key length in bufr message %d != %d", klength, length);
        return false;
    }

    {
        char* p = (char*)buffer + length - 4;
        ok      = (p[0] == '7' && p[1] == '7' && p[2] == '7'
              && p[3] == '7');
        if (!ok) {
            marslog(LOG_EROR, "7777 not found at offset+key length");
            return false;
        }
    }
    {
        char s[100];
        long date = KEY_YEAR(key) * 10000 + KEY_MONTH(key) * 100 + KEY_DAY(key);
        if (mars_julian_to_date(mars_date_to_julian(date), true) != date) {
            marslog(LOG_EROR, "date is weird %d", date);
            return false;
        }

        if (KEY_HOUR(key) >= 24) {
            print_key_time(key, s);
            marslog(LOG_EROR, "time is weird %s", s);
            return false;
        }

        if (KEY_MINUTE(key) >= 60) {
            print_key_time(key, s);
            marslog(LOG_EROR, "time is weird %s", s);
            return false;
        }

        if (KEY_SECOND(key) >= 60) {
            print_key_time(key, s);
            marslog(LOG_EROR, "time is weird %s", s);
            return false;
        }
    }

    return ok;
}

static void zero_spaces(char* p) {
    while (*p) {
        if (*p == ' ')
            *p = '0';
        p++;
    }
}

void print_key_date(const packed_key* keyptr, char* s) {
    sprintf(s, "%2d/%2d/%4d", KEY_DAY(keyptr), KEY_MONTH(keyptr), KEY_YEAR(keyptr));
    zero_spaces(s);
}

void print_key_time(const packed_key* keyptr, char* s) {
    sprintf(s, "%2d:%2d:%2d", KEY_HOUR(keyptr),
            KEY_MINUTE(keyptr),
            KEY_SECOND(keyptr));
    zero_spaces(s);
}

static void set_subset_count(char* buffer, packed_key* k) {
    char* p = (char*)k;
    p[23]   = subset_count(buffer);
}

int build_packed_key(char* buffer, fortint length, packed_key* k) {
    fortint kerr = 0;

#if 0

#define KVALS 80000

	fortint ksup[9];
	fortint ksec0[3];
	fortint ksec1[100];
	fortint ksec2[64];
	fortint ksec3[4];
	fortint ksec4[2];

	fortfloat values[KVALS];
	char cnames[20000*64];
	char cunits[20000*24];
	char cvals[KVALS*80];

	int i;
	char name[65];
	fortint kvals = KVALS;
	fortint kelem = KVALS / subset_count(buffer);
	fortint ktdexl;


	/* for busel */
	fortint ktdlst[2000];
	fortint ktdexp[2000];

	int	year	= -1;
	int month	= -1;
	int day		= -1;
	int hour	= -1;
	int	minute	= -1;
	int	second	= -1;
	int	block	= -1;
	int station	= -1;
	int	id		= -1;
	int	id_type	= 0;
	int lat		= -1;
	int	lon		= -1;

	memset((void*) k,0,sizeof(*k));

	bufrex_(&length,
	    buffer,
	    ksup,
	    ksec0, ksec1, ksec2, ksec3, ksec4,

	    &kelem,

	    cnames,
	    cunits,

	    &kvals,

	    values,
	    cvals,
	    &kerr,

	    64,
	    24,
	    80);
	if (kerr)
		return kerr;


	busel_(&kelem,ktdlst,&ktdexl,ktdexp,&kerr);
	if (kerr)
		return kerr;

	for (i = 0; i < 13; i++)
	{
		switch (ktdexp[i])
		{
			case 1006:
			case 1007:
			case 1005:
			case 1008:
			case 1011:
				id = i;
				id_type = ktdexp[i];
				break;
			case 1001:
				block = i;
				id_type++;
				break;
			case 1002:
				id_type++;
				station = i;
				break;
			case 4001:
				year = i;
				break;
			case 4002:
				month = i;
				break;
			case 4003:
				day = i;
				break;
			case 4004:
				hour = i;
				break;
			case 4005:
				minute = i;
				break;
			case 4006:
				second = i;
				break;
			case 5001:
				lat = i;
				break;
			case 6001:
				lon = i;
				break;
			case 5002:
				lat = i;
				break;
			case 6002:
				lon = i;
				break;
		}
	}

	if (year == -1 || month == -1 || day == -1 || hour == -1 || minute == -1 ||
		  lat == -1 || lon == -1)
		return -10;

	KEY_YEAR(k) 	= values[year];
	KEY_MONTH(k)	= values[month];
	KEY_DAY(k)		= values[day];
	KEY_HOUR(k)		= values[hour];
	KEY_MINUTE(k)	= values[minute];
	KEY_SECOND(k)	= second == -1 ? 0 : values[second];

	/* id */
	switch (id_type)
	{
		char ident[10];
		int i;
		int indx;
		int l;

		case 1007: /* SATELLITE IDENTIFIER INDEX */
			set_bufr_sat_id(buffer,values[id],k);
			break;

		case 1005: /* BUOY/PLATFORM IDENTIFIER INDEX */
			i = values[id];
			set_bufr_sat_id(buffer,i,k);
			sprintf(ident,"%5d",i);
			zero_spaces(ident);
			memcpy(KEY_IDENT(k),ident,5);
			break;

		case 1006: /* AIRCRAFT FLIGHT NUMBER INDEX */
		case 1008: /* AIRCRAFT REGISTRATION NUMBER INDEX */
		case 1011: /* SHIPS' CALL SIGN INDEX */
			indx = (int) values[id] / 1000; /* index in cvals where data is */
			l = (int) values[id] % 1000; 	/* length of data in cvals */
			for (i = 0; i < l; i++)
				KEY_IDENT(k)[i] = cvals[indx++];
			break;

		case 2:    /* WMO BLOCK AND STATION NUMBER */
			i = values[block]*1000+values[station];
			set_bufr_sat_id(buffer,i,k);
			sprintf(ident,"%5d",i);
			zero_spaces(ident);
			memcpy(KEY_IDENT(k),ident,5);
			break;

		default:
			kerr = -9;
			break;
	}
	if (kerr)
		return kerr;

	if (abs(values[lon] - 1.7E38) < 10.E-11 ||
		abs(values[lat] - 1.7E38) < 10.E-11)
		return -8;

#if 0
	printf("LAT: %d\n",(int) (values[lat]) * 100000.0+9000000.0));
	printf("LON: %d\n",(int) ((values[lon]+180) * 100000.0+18000000.0));
#endif

	k->length = length;
	set_subset_count(buffer,k);

#endif
    return kerr;
}


void print_packed_key(char* buffer, packed_key* k) {
    printf("------------------------------------------------\n");
    printf("------------------------------------------------\n");
    printf("type                  %d\n", KEY_TYPE(k));
    printf("subtype               %d\n", KEY_SUBTYPE(k));
    printf("year                  %d\n", KEY_YEAR(k));
    printf("month                 %d\n", KEY_MONTH(k));
    printf("day                   %d\n", KEY_DAY(k));
    printf("hour                  %d\n", KEY_HOUR(k));
    printf("minute                %d\n", KEY_MINUTE(k));
    printf("second                %d\n", KEY_SECOND(k));
    printf("--------------------------------------------\n");
    printf("latitude1             %lu\n", KEY_LATITUDE1(k));
    printf("longitude1            %lu\n", KEY_LONGITUDE1(k));
    if (IS_SATTELITE(k)) {
        printf("latitude2             %lu\n", KEY_LATITUDE2(k));
        printf("longitude2            %lu\n", KEY_LONGITUDE2(k));
    }
    printf("------------------------------------------------\n");
    printf("length                %lu\n", key_length(buffer, k));
    printf("number of subsets     %lu\n", KEY_NOBS(k));
    printf("------------------------------------------------\n");
    if (IS_SATTELITE(k))
        printf("ident                 %d\n", bufr_sat_id(buffer, k));
    else {
        char id[6];
        memcpy(id, KEY_IDENT(k), 5);
        id[5] = 0;
        printf("ident                 %s\n", id);
    }
    printf("\n");
}
