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