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

#ifndef   ObsKey_H
#include "ObsKey.h"
#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_TYPE(k)         ((unsigned int)k->header.type)
#define KEY_SUBTYPE(k)      ((unsigned int)k->header.subtype)
#define KEY_YEAR(k)         ((unsigned int)getbits( (unsigned char*)k->header.date_time,  0, 12 ))
#define KEY_MONTH(k)        ((unsigned int)getbits( (unsigned char*)k->header.date_time, 12,  4 ))
#define KEY_DAY(k)          ((unsigned int)getbits( (unsigned char*)k->header.date_time, 16,  6 ))
#define KEY_HOUR(k)         ((unsigned int)getbits( (unsigned char*)k->header.date_time, 22,  5 ))
#define KEY_MINUTE(k)       ((unsigned int)getbits( (unsigned char*)k->header.date_time, 27,  6 ))
#define KEY_SECOND(k)       ((unsigned int)getbits( (unsigned char*)k->header.date_time, 33,  6 ))
#else
#define KEY_TYPE(k)         (k->header.type)
#define KEY_SUBTYPE(k)      (k->header.subtype)
#define KEY_YEAR(k)         (k->header.year)
#define KEY_MONTH(k)        (k->header.month)
#define KEY_DAY(k)          (k->header.day)
#define KEY_HOUR(k)         (k->header.hour)
#define KEY_MINUTE(k)       (k->header.minute)
#define KEY_SECOND(k)       (k->header.second)
#endif

#define KEY_CORR1(k)        CORR_CORR(k->corr1)
#define KEY_CORR2(k)        CORR_CORR(k->corr2)
#define KEY_CORR3(k)        CORR_CORR(k->corr3)
#define KEY_CORR4(k)        CORR_CORR(k->corr4)
#define KEY_IDENT(k)        ((char*)k+15)
#define KEY_LATITUDE(k)     ((unsigned long)getbits((unsigned char*)k,88,25))
#define KEY_LATITUDE1(k)    ((unsigned long)getbits((unsigned char*)k,88,25))
#define KEY_LATITUDE2(k)    ((unsigned long)getbits((unsigned char*)k,152,25))

#ifdef LITTLE_END
#define SET_KEY_LENGTH(k,v) (k).length[0]=(unsigned char)((v)/256);(k).length[1]=(unsigned char)((v)%256);
#define KEY_LENGTH(k)       ((unsigned int)getbits( (unsigned char*)k->length, 0, 16))
#else
#define SET_KEY_LENGTH(k,v) (k).length = (v)
#define KEY_LENGTH(k)       (k->length)
#endif

#define KEY_LONGITUDE(k)    ((unsigned long)getbits((unsigned char*)k,56,26))
#define KEY_LONGITUDE1(k)   ((unsigned long)getbits((unsigned char*)k,56,26))
#define KEY_LONGITUDE2(k)   ((unsigned long)getbits((unsigned char*)k,120,26))
#define KEY_NOBS(k)         ((unsigned long)getbits((unsigned char*)k,184,8))
#define KEY_PART1(k)        CORR_PART(k->corr1) 
#define KEY_PART2(k)        CORR_PART(k->corr2) 
#define KEY_PART3(k)        CORR_PART(k->corr3) 
#define KEY_PART4(k)        CORR_PART(k->corr4) 
#define KEY_QC(k)           (k->qc) 
#define KEY_RDBDAY(k)       TIME_DAY(k->rdbtime)    
#define KEY_RDBHOUR(k)      TIME_HOUR(k->rdbtime)
#define KEY_RDBMINUTE(k)    TIME_MINUTE(k->rdbtime)
#define KEY_RDBSECOND(k)    TIME_SECOND(k->rdbtime)
#define KEY_RECDAY(k)       TIME_DAY(k->rectime)
#define KEY_RECHOUR(k)      TIME_HOUR(k->rectime)
#define KEY_RECMINUTE(k)    TIME_MINUTE(k->rectime)
#define KEY_RECSECOND(k)    TIME_SECOND(k->rectime)

/* day 6 bits,hour 5 bits, min 6 sec 6  1 spare*/

#define TIME_DAY(a)     (unsigned long)((a[0] & 0xFC)>>2)
#define TIME_HOUR(a)    (unsigned long)(((a[0]&0x3)<<3)+((a[1]&0xE0)>>5))
#define TIME_MINUTE(a)  (unsigned long)(((a[1]&0x1F)<<1)+((a[2]&0x8)>>3))
#define TIME_SECOND(a)  (unsigned long)((a[2] & 0x7E)>>1)

#define CORR_CORR(a)        (unsigned long)((unsigned char)((unsigned char)a & (unsigned char)0xFC) >>  (unsigned char)2)
#define CORR_PART(a)        (unsigned long)((unsigned char)((unsigned char)a & (unsigned char)0x02) >>  (unsigned char)1)

#define IS_SATTELITE(k)     (KEY_TYPE(k)==2||KEY_TYPE(k)==3||KEY_TYPE(k)==12)

#define SEC1_LENGTH(a)  (getbits((unsigned char*)a,0,24))
#define SEC1_FLAGS(a)   (getbits((unsigned char*)a,56,8))

static unsigned long key_length(const char *buffer, const 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;
}

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

static bool getkey(char *buffer,key *k)
{
	/* skip section 0 , check for BUFR version */
	if((unsigned char)buffer[7]>1)
		buffer += 8;
	else
		buffer += 4;

	/* check if the key is present */

	if(SEC1_FLAGS(buffer))
	{
		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(key));

		return true;
	}

	return false;

}

bool verify_bufr_key(const char* buffer, fortint length, const key* key,string& s)
{
	bool ok = true;
	unsigned long klength = key_length(buffer,key);

	if (klength != length)
	{
		s = "Wrong key length in bufr message";
		cerr << "key length " << klength << ", length " << length << endl;
		return false;
	}
		
	{
		char *p = (char*) buffer + klength-4;
		ok = (p[0] == '7' && p[1] == '7' && p[2] == '7' 
				&& p[3] == '7');
		if (!ok)
		{
			s = "7777 not found where it should be";
			return false;
			
		}
	}
	{
		if (KEY_HOUR(key) >= 24)
		{
			s = "time is weird";
			return false;
		}

		if (KEY_MINUTE(key) >= 60)
		{
			s = "time is weird";
			return false;
		}

		if (KEY_SECOND(key) >= 60)
		{
			s = "time is weird";
			return false;
		}
	}

	return ok;
}

ObsKey::ObsKey(char* buffer,fortint length,bool check)
{
	if (!getkey(buffer,&key_))
		throw BadBufr("No key in message");
	if (check)
	{
		string s;
		if (!verify_bufr_key(buffer,length,&key_,s))
			throw BadBufr(s);
	}

	const key *k = (const key *)&key_;

	length_ = KEY_LENGTH(k);
    if((length_ == WRONG_KEY_LENGTH) || ( length_ == 0))
    {
		
        length_ = key_length(buffer,k);
    }
}

Ordinal	ObsKey::length()   const
{ 
	return length_;
}

double ObsKey::latitude() const
{
    // Magic formulae available from BUFR User Guide and Reference Manual
    // October 1994 p. 53
    return (double)((KEY_LATITUDE(&key_)/1.0 - 9000000.0) / 100000.0);
}

double ObsKey::longitude() const
{
    // Magic formulae available from BUFR User Guide and Reference Manual
    // October 1994 p. 53
    return (double)((KEY_LONGITUDE(&key_)/1.0 - 18000000.0) / 100000.0);
}

int ObsKey::year()     const
{ 
#ifdef LITTLE_END
	return (unsigned int)getbits( (unsigned char*)key_.header.date_time,  0, 12 );
#else
	return key_.header.year;
#endif
}

int ObsKey::month()    const
{ 
#ifdef LITTLE_END
	return (unsigned int)getbits( (unsigned char*)key_.header.date_time, 12,  4 );
#else
	return key_.header.month;
#endif
}

int ObsKey::day()      const
{ 
#ifdef LITTLE_END
	return (unsigned int)getbits( (unsigned char*)key_.header.date_time, 16,  6 );
#else
	return key_.header.day;
#endif
}

int ObsKey::hour()     const
{ 
#ifdef LITTLE_END
	return (unsigned int)getbits( (unsigned char*)key_.header.date_time, 22,  5 );
#else
	return key_.header.hour;
#endif
}

int ObsKey::minute()   const
{ 	
#ifdef LITTLE_END
	return (unsigned int)getbits( (unsigned char*)key_.header.date_time, 27,  6 );
#else
	return key_.header.minute;
#endif
}

int ObsKey::second()   const 
{ 
#ifdef LITTLE_END
	return (unsigned int)getbits( (unsigned char*)key_.header.date_time, 33,  6 );
#else
	return key_.header.second;
#endif
}

long ObsKey::station() const
{
	char* p = KEY_IDENT(&key_);
	long id = 0;
	long count = 0;
    // some idents are right justified
    // 9 characters
    while (*p == ' ' && count < 4)
    {
        p++;
        count++;
    }

    if(count <= 4)
	{
		cout << "Found IDENT less than 4 characters long '" << KEY_IDENT(&key_) << "'" << endl;
	}

    for (Ordinal i = 0; i < 5; i++)
    {
        id = 10 * id + (*p) - '0';
        p++;
    }

    return id;
}

void ObsKey::print(ostream& s) const
{
	s << "[";
	s << "type:" << key_.header.type ;
	s << ",";
	s << "subtype:" << key_.header.subtype ;
	s << ",";
    s << "latitude:" << latitude() ;
	s << ",";
    s << "longitude:" << longitude() ;
	s << "]";
}
