/*
 * (C) Copyright 2005- 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 "grib_api_internal.h"

/* Return the rank of the key using list of keys (For BUFR keys) */
/* The argument 'keys' is an input as well as output from each call */
int compute_bufr_key_rank(grib_handle* h, grib_string_list* keys, const char* key)
{
    grib_string_list* next = keys;
    grib_string_list* prev = keys;
    int theRank            = 0;
    size_t size            = 0;
    grib_context* c        = h->context;
    DebugAssert(h->product_kind == PRODUCT_BUFR);

    while (next && next->value && strcmp(next->value, key)) {
        prev = next;
        next = next->next;
    }
    if (!next) {
        DebugAssert(prev);
        if (prev) {
            prev->next = (grib_string_list*)grib_context_malloc_clear(c, sizeof(grib_string_list));
            next       = prev->next;
        }
    }
    DebugAssert(next);
    if (!next) return 0;

    if (!next->value) {
        next->value = strdup(key);
        next->count = 0;
    }

    next->count++;
    theRank = next->count;
    if (theRank == 1) {
        /* If the count is 1 it could mean two things: */
        /*   This is the first instance of the key and there is another one */
        /*   This is the first and only instance of the key */
        /* So we check if there is a second one of this key, */
        /* If not, then rank is zero i.e. this is the only instance */
        char* s = (char*)grib_context_malloc_clear(c, strlen(key) + 5);
        sprintf(s, "#2#%s", key);
        if (grib_get_size(h, s, &size) == GRIB_NOT_FOUND)
            theRank = 0;
        grib_context_free(c, s);
    }

    return theRank;
}

char** codes_bufr_copy_data_return_copied_keys(grib_handle* hin, grib_handle* hout, size_t* nkeys, int* err)
{
    bufr_keys_iterator* kiter = NULL;
    char** keys               = NULL;
    grib_sarray* k            = 0;

    if (hin == NULL || hout == NULL) {
        *err = GRIB_NULL_HANDLE;
        return NULL;
    }

    kiter = codes_bufr_data_section_keys_iterator_new(hin);
    if (!kiter)
        return NULL;
    k = grib_sarray_new(hin->context, 50, 10);

    while (codes_bufr_keys_iterator_next(kiter)) {
        char* name = codes_bufr_keys_iterator_get_name(kiter);
        /* if the copy fails we want to keep copying without any errors.
           This is because the copy can be between structures that are not
           identical and we want to copy what can be copied and skip what
           cannot be copied because is not in the output handle
         */
        *err = codes_copy_key(hin, hout, name, 0);
        if (*err == 0) {
            /* 'name' will be freed when we call codes_bufr_keys_iterator_delete so copy */
            char* copied_name = strdup(name);
            k                 = grib_sarray_push(hin->context, k, copied_name);
        }
    }
    *nkeys = grib_sarray_used_size(k);
    keys   = grib_sarray_get_array(hin->context, k);
    grib_sarray_delete(hin->context, k);
    if (*nkeys > 0) {
        /* Do the pack if something was copied */
        *err = grib_set_long(hout, "pack", 1);
    }
    codes_bufr_keys_iterator_delete(kiter);
    return keys;
}

int codes_bufr_copy_data(grib_handle* hin, grib_handle* hout)
{
    bufr_keys_iterator* kiter = NULL;
    int err                   = 0;
    int nkeys                 = 0;

    if (hin == NULL || hout == NULL) {
        return GRIB_NULL_HANDLE;
    }

    kiter = codes_bufr_data_section_keys_iterator_new(hin);
    if (!kiter)
        return GRIB_INTERNAL_ERROR;

    while (codes_bufr_keys_iterator_next(kiter)) {
        char* name = codes_bufr_keys_iterator_get_name(kiter);
        /* if the copy fails we want to keep copying without any error messages.
           This is because the copy can be between structures that are not
           identical and we want to copy what can be copied and skip what
           cannot be copied because is not in the output handle
         */
        err = codes_copy_key(hin, hout, name, GRIB_TYPE_UNDEFINED);
        if (err == 0)
            nkeys++;
    }

    if (nkeys > 0) {
        /* Do the pack if something was copied */
        err = grib_set_long(hout, "pack", 1);
    }

    codes_bufr_keys_iterator_delete(kiter);
    return err;
}

#define BUFR_SECTION0_LEN 8 /* BUFR section 0 is always 8 bytes long */
static int bufr_extract_edition(const void* message, long* edition)
{
    const long nbits_edition = 8;
    long pos_edition         = 7 * 8;
    const unsigned char* pMessage = (const unsigned char*)message;

    *edition = (long)grib_decode_unsigned_long(pMessage, &pos_edition, nbits_edition);
    return GRIB_SUCCESS;
}
/* The ECMWF BUFR local use section */
static int bufr_decode_rdb_keys(const void* message, long offset_section2, codes_bufr_header* hdr)
{
    const unsigned char* pMessage = (const unsigned char*)message;
    long nbits_rdbType    = 1 * 8;
    long pos_rdbType      = (offset_section2 + 4) * 8;
    long nbits_oldSubtype = 1 * 8;
    long pos_oldSubtype   = (offset_section2 + 5) * 8;

    long nbits_qualityControl = 1 * 8;
    long pos_qualityControl   = (offset_section2 + 48) * 8;
    long nbits_newSubtype     = 2 * 8;
    long pos_newSubtype       = (offset_section2 + 49) * 8;
    long nbits_daLoop         = 1 * 8;
    long pos_daLoop           = (offset_section2 + 51) * 8;

    long start                = 0;
    const long offset_keyData = offset_section2 + 6;
    const long offset_rdbtime = offset_section2 + 38;
    const long offset_rectime = offset_section2 + 41;

    unsigned char* p = (unsigned char*)message + offset_keyData;

    DebugAssert(hdr->ecmwfLocalSectionPresent);

    hdr->rdbType    = (long)grib_decode_unsigned_long(pMessage, &pos_rdbType, nbits_rdbType);
    hdr->oldSubtype = (long)grib_decode_unsigned_long(pMessage, &pos_oldSubtype, nbits_oldSubtype);

    start            = 0;
    hdr->localYear   = (long)grib_decode_unsigned_long(p, &start, 12);
    hdr->localMonth  = (long)grib_decode_unsigned_long(p, &start, 4);
    hdr->localDay    = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->localHour   = (long)grib_decode_unsigned_long(p, &start, 5);
    hdr->localMinute = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->localSecond = (long)grib_decode_unsigned_long(p, &start, 6);

    /* rdbtime */
    p                  = (unsigned char*)message + offset_rdbtime;
    start              = 0;
    hdr->rdbtimeDay    = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->rdbtimeHour   = (long)grib_decode_unsigned_long(p, &start, 5);
    hdr->rdbtimeMinute = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->rdbtimeSecond = (long)grib_decode_unsigned_long(p, &start, 6);

    /* rectime */
    p                  = (unsigned char*)message + offset_rectime;
    start              = 0;
    hdr->rectimeDay    = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->rectimeHour   = (long)grib_decode_unsigned_long(p, &start, 5);
    hdr->rectimeMinute = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->rectimeSecond = (long)grib_decode_unsigned_long(p, &start, 6);
    hdr->restricted    = (long)grib_decode_unsigned_long(p, &start, 1);

    hdr->qualityControl = (long)grib_decode_unsigned_long(pMessage, &pos_qualityControl, nbits_qualityControl);
    hdr->newSubtype     = (long)grib_decode_unsigned_long(pMessage, &pos_newSubtype, nbits_newSubtype);
    hdr->daLoop         = (long)grib_decode_unsigned_long(pMessage, &pos_daLoop, nbits_daLoop);
    hdr->rdbSubtype     = (hdr->oldSubtype < 255) ? hdr->oldSubtype : hdr->newSubtype;

    return GRIB_SUCCESS;
}

#define IDENT_LEN 9 /* 8 chars plus the final 0 terminator */

/* The ECMWF BUFR local use section */
static int bufr_decode_extra_rdb_keys(const void* message, long offset_section2, codes_bufr_header* hdr)
{
    int isSatelliteType       = 0;
    long start                = 0;
    const long offset_keyData = offset_section2 + 6;
    const long offset_keyMore = offset_section2 + 19; /* 8 bytes long */
    const long offset_keySat  = offset_section2 + 27; /* 9 bytes long */

    unsigned char* pKeyData = (unsigned char*)message + offset_keyData;
    char* pKeyMore          = (char*)message + offset_keyMore;

    DebugAssert(hdr->ecmwfLocalSectionPresent);

    if (hdr->rdbType == 2 || hdr->rdbType == 3 || hdr->rdbType == 8 || hdr->rdbType == 12) {
        isSatelliteType = 1;
    }
    if (isSatelliteType || hdr->numberOfSubsets > 1) {
        hdr->isSatellite = 1;
    }
    else {
        hdr->isSatellite = 0;
    }

    if (hdr->isSatellite) {
        unsigned char* pKeyMoreLong = (unsigned char*)message + offset_keyMore; /* as an integer */
        unsigned char* pKeySat      = (unsigned char*)message + offset_keySat;
        long lValue                 = 0;
        start                       = 40;
        lValue                      = (long)grib_decode_unsigned_long(pKeyData, &start, 26);
        hdr->localLongitude1        = (lValue - 18000000.0) / 100000.0;
        start                       = 72;
        lValue                      = (long)grib_decode_unsigned_long(pKeyData, &start, 25);
        hdr->localLatitude1         = (lValue - 9000000.0) / 100000.0;
        start                       = 0;
        lValue                      = (long)grib_decode_unsigned_long(pKeyMoreLong, &start, 26);
        hdr->localLongitude2        = (lValue - 18000000.0) / 100000.0;
        start                       = 32;
        lValue                      = (long)grib_decode_unsigned_long(pKeyMoreLong, &start, 25);
        hdr->localLatitude2         = (lValue - 9000000) / 100000.0;

        if (hdr->oldSubtype == 255 || hdr->numberOfSubsets > 255 ||
            (hdr->oldSubtype >= 121 && hdr->oldSubtype <= 130) ||
            hdr->oldSubtype == 31) {
            start                          = 0;
            hdr->localNumberOfObservations = (long)grib_decode_unsigned_long(pKeySat, &start, 16);
            start                          = 16;
            hdr->satelliteID               = (long)grib_decode_unsigned_long(pKeySat, &start, 16);
        }
        else {
            start                          = 0;
            hdr->localNumberOfObservations = (long)grib_decode_unsigned_long(pKeySat, &start, 8);
            start                          = 8;
            hdr->satelliteID               = (long)grib_decode_unsigned_long(pKeySat, &start, 16);
        }
    }
    else {
        size_t i = 0;
        long lValue  = 0;
        char* pTemp = NULL;
        char temp[IDENT_LEN] = {0,};

        start               = 72;
        lValue              = (long)grib_decode_unsigned_long(pKeyData, &start, 25);
        hdr->localLatitude  = (lValue - 9000000.0) / 100000.0;
        start               = 40;
        lValue              = (long)grib_decode_unsigned_long(pKeyData, &start, 26);
        hdr->localLongitude = (lValue - 18000000.0) / 100000.0;

        /* interpret keyMore as a string. Copy to a temporary */
        for (i = 0; i < IDENT_LEN - 1; ++i) {
            temp[i] = *pKeyMore++;
        }
        temp[i] = '\0';
        pTemp = temp;
        lrtrim(&pTemp, 1, 1); /* Trim left and right */
        strncpy(hdr->ident, pTemp, IDENT_LEN - 1);
    }

    return GRIB_SUCCESS;
}

static int bufr_decode_edition3(const void* message, codes_bufr_header* hdr)
{
    int err = GRIB_SUCCESS;
    const unsigned char* pMessage = (const unsigned char*)message;

    unsigned long totalLength    = 0;
    const long nbits_totalLength = 3 * 8;
    long pos_totalLength         = 4 * 8;

    unsigned long section1Length    = 0;
    const long nbits_section1Length = 3 * 8;
    long pos_section1Length         = 8 * 8;

    long nbits_masterTableNumber = 1 * 8;
    long pos_masterTableNumber   = 11 * 8;

    long nbits_bufrHeaderSubCentre = 1 * 8;
    long pos_bufrHeaderSubCentre   = 12 * 8;

    long nbits_bufrHeaderCentre = 1 * 8;
    long pos_bufrHeaderCentre   = 13 * 8;

    long nbits_updateSequenceNumber = 1 * 8;
    long pos_updateSequenceNumber   = 14 * 8;

    long section1Flags       = 0;
    long nbits_section1Flags = 1 * 8;
    long pos_section1Flags   = 15 * 8;

    long nbits_dataCategory = 1 * 8;
    long pos_dataCategory   = 16 * 8;

    long nbits_dataSubCategory = 1 * 8;
    long pos_dataSubCategory   = 17 * 8;

    long nbits_masterTablesVersionNumber = 1 * 8;
    long pos_masterTablesVersionNumber   = 18 * 8;

    long nbits_localTablesVersionNumber = 1 * 8;
    long pos_localTablesVersionNumber   = 19 * 8;

    const long typicalCentury       = 21; /* This century */
    long typicalYearOfCentury       = 0;
    long nbits_typicalYearOfCentury = 1 * 8;
    long pos_typicalYearOfCentury   = 20 * 8;

    long nbits_typicalMonth = 1 * 8;
    long pos_typicalMonth   = 21 * 8;

    long nbits_typicalDay = 1 * 8;
    long pos_typicalDay   = 22 * 8;

    long nbits_typicalHour = 1 * 8;
    long pos_typicalHour   = 23 * 8;

    long nbits_typicalMinute = 1 * 8;
    long pos_typicalMinute   = 24 * 8;

    long section2Length        = 0;
    long offset_section2       = 0;
    long offset_section3       = 0;
    long nbits_numberOfSubsets = 2 * 8;
    long pos_numberOfSubsets   = 0; /*depends on offset_section3*/

    long section3Flags;
    long nbits_section3Flags = 1 * 8;
    long pos_section3Flags   = 0; /*depends on offset_section3*/

    totalLength = grib_decode_unsigned_long(pMessage, &pos_totalLength, nbits_totalLength);
    if (totalLength != hdr->message_size) {
        return GRIB_WRONG_LENGTH;
    }
    section1Length                 = grib_decode_unsigned_long(pMessage, &pos_section1Length, nbits_section1Length);
    hdr->masterTableNumber         = (long)grib_decode_unsigned_long(pMessage, &pos_masterTableNumber, nbits_masterTableNumber);
    hdr->bufrHeaderSubCentre       = (long)grib_decode_unsigned_long(pMessage, &pos_bufrHeaderSubCentre, nbits_bufrHeaderSubCentre);
    hdr->bufrHeaderCentre          = (long)grib_decode_unsigned_long(pMessage, &pos_bufrHeaderCentre, nbits_bufrHeaderCentre);
    hdr->updateSequenceNumber      = (long)grib_decode_unsigned_long(pMessage, &pos_updateSequenceNumber, nbits_updateSequenceNumber);
    section1Flags                  = (long)grib_decode_unsigned_long(pMessage, &pos_section1Flags, nbits_section1Flags);
    hdr->dataCategory              = (long)grib_decode_unsigned_long(pMessage, &pos_dataCategory, nbits_dataCategory);
    hdr->dataSubCategory           = (long)grib_decode_unsigned_long(pMessage, &pos_dataSubCategory, nbits_dataSubCategory);
    hdr->masterTablesVersionNumber = (long)grib_decode_unsigned_long(
        pMessage, &pos_masterTablesVersionNumber, nbits_masterTablesVersionNumber);
    hdr->localTablesVersionNumber = (long)grib_decode_unsigned_long(pMessage, &pos_localTablesVersionNumber, nbits_localTablesVersionNumber);
    typicalYearOfCentury          = (long)grib_decode_unsigned_long(pMessage, &pos_typicalYearOfCentury, nbits_typicalYearOfCentury);
    hdr->typicalYear              = (typicalCentury - 1) * 100 + typicalYearOfCentury;
    hdr->typicalMonth             = (long)grib_decode_unsigned_long(pMessage, &pos_typicalMonth, nbits_typicalMonth);
    hdr->typicalDay               = (long)grib_decode_unsigned_long(pMessage, &pos_typicalDay, nbits_typicalDay);
    hdr->typicalHour              = (long)grib_decode_unsigned_long(pMessage, &pos_typicalHour, nbits_typicalHour);
    hdr->typicalMinute            = (long)grib_decode_unsigned_long(pMessage, &pos_typicalMinute, nbits_typicalMinute);
    hdr->typicalSecond            = 0;
    hdr->typicalDate              = hdr->typicalYear * 10000 + hdr->typicalMonth * 100 + hdr->typicalDay;
    hdr->typicalTime              = hdr->typicalHour * 10000 + hdr->typicalMinute * 100 + hdr->typicalSecond;

    offset_section2          = BUFR_SECTION0_LEN + section1Length; /*bytes*/
    section2Length           = 0;
    hdr->localSectionPresent = (section1Flags != 0);
    if (hdr->localSectionPresent) {
        long pos_section2Length;
        const long nbits_section2Length = 3 * 8;
        pos_section2Length              = offset_section2 * 8;

        section2Length = grib_decode_unsigned_long(pMessage, &pos_section2Length, nbits_section2Length);

        if (hdr->bufrHeaderCentre == 98) {
            hdr->ecmwfLocalSectionPresent = 1;
            err                           = bufr_decode_rdb_keys(message, offset_section2, hdr);
        }
    }

    offset_section3       = BUFR_SECTION0_LEN + section1Length + section2Length; /*bytes*/
    pos_numberOfSubsets   = (offset_section3 + 4) * 8;
    hdr->numberOfSubsets  = grib_decode_unsigned_long(pMessage, &pos_numberOfSubsets, nbits_numberOfSubsets);

    pos_section3Flags   = (offset_section3 + 6) * 8;
    section3Flags       = (long)grib_decode_unsigned_long(pMessage, &pos_section3Flags, nbits_section3Flags);
    hdr->observedData   = (section3Flags & 1 << 7) ? 1 : 0;
    hdr->compressedData = (section3Flags & 1 << 6) ? 1 : 0;

    if (hdr->ecmwfLocalSectionPresent && hdr->bufrHeaderCentre == 98 && section2Length == 52) {
        err = bufr_decode_extra_rdb_keys(message, offset_section2, hdr);
    }

    return err;
}

static int bufr_decode_edition4(const void* message, codes_bufr_header* hdr)
{
    int err = GRIB_SUCCESS;
    const unsigned char* pMessage = (const unsigned char*)message;

    unsigned long totalLength    = 0;
    const long nbits_totalLength = 3 * 8;
    long pos_totalLength         = 4 * 8;

    unsigned long section1Length;
    const long nbits_section1Length = 3 * 8;
    long pos_section1Length         = 8 * 8;

    long nbits_masterTableNumber = 1 * 8;
    long pos_masterTableNumber   = 11 * 8;

    long nbits_bufrHeaderCentre = 2 * 8;
    long pos_bufrHeaderCentre   = 12 * 8;

    long nbits_bufrHeaderSubCentre = 2 * 8;
    long pos_bufrHeaderSubCentre   = 14 * 8;

    long nbits_updateSequenceNumber = 1 * 8;
    long pos_updateSequenceNumber   = 16 * 8;

    long section1Flags       = 0;
    long nbits_section1Flags = 1 * 8;
    long pos_section1Flags   = 17 * 8;

    long nbits_dataCategory = 1 * 8;
    long pos_dataCategory   = 18 * 8;

    long nbits_internationalDataSubCategory = 1 * 8;
    long pos_internationalDataSubCategory   = 19 * 8;

    long nbits_dataSubCategory = 1 * 8;
    long pos_dataSubCategory   = 20 * 8;

    long nbits_masterTablesVersionNumber = 1 * 8;
    long pos_masterTablesVersionNumber   = 21 * 8;

    long nbits_localTablesVersionNumber = 1 * 8;
    long pos_localTablesVersionNumber   = 22 * 8;

    long typicalYear2      = 0; /* corrected */
    long nbits_typicalYear = 2 * 8;
    long pos_typicalYear   = 23 * 8;

    long nbits_typicalMonth = 1 * 8;
    long pos_typicalMonth   = 25 * 8;

    long nbits_typicalDay = 1 * 8;
    long pos_typicalDay   = 26 * 8;

    long nbits_typicalHour = 1 * 8;
    long pos_typicalHour   = 27 * 8;

    long nbits_typicalMinute = 1 * 8;
    long pos_typicalMinute   = 28 * 8;

    long nbits_typicalSecond = 1 * 8;
    long pos_typicalSecond   = 29 * 8;

    long section2Length        = 0;
    long offset_section2       = 0;
    long offset_section3       = 0;
    long nbits_numberOfSubsets = 2 * 8;
    long pos_numberOfSubsets   = 0; /*depends on offset_section3*/

    long section3Flags;
    long nbits_section3Flags = 1 * 8;
    long pos_section3Flags   = 0; /*depends on offset_section3*/

    totalLength = grib_decode_unsigned_long(pMessage, &pos_totalLength, nbits_totalLength);
    if (totalLength != hdr->message_size) {
        return GRIB_WRONG_LENGTH;
    }
    section1Length                    = grib_decode_unsigned_long(pMessage, &pos_section1Length, nbits_section1Length);
    hdr->masterTableNumber            = (long)grib_decode_unsigned_long(pMessage, &pos_masterTableNumber, nbits_masterTableNumber);
    hdr->bufrHeaderCentre             = (long)grib_decode_unsigned_long(pMessage, &pos_bufrHeaderCentre, nbits_bufrHeaderCentre);
    hdr->bufrHeaderSubCentre          = (long)grib_decode_unsigned_long(pMessage, &pos_bufrHeaderSubCentre, nbits_bufrHeaderSubCentre);
    hdr->updateSequenceNumber         = (long)grib_decode_unsigned_long(pMessage, &pos_updateSequenceNumber, nbits_updateSequenceNumber);
    section1Flags                     = (long)grib_decode_unsigned_long(pMessage, &pos_section1Flags, nbits_section1Flags);
    hdr->dataCategory                 = (long)grib_decode_unsigned_long(pMessage, &pos_dataCategory, nbits_dataCategory);
    hdr->internationalDataSubCategory = (long)grib_decode_unsigned_long(pMessage, &pos_internationalDataSubCategory, nbits_internationalDataSubCategory);
    hdr->dataSubCategory              = (long)grib_decode_unsigned_long(pMessage, &pos_dataSubCategory, nbits_dataSubCategory);
    hdr->masterTablesVersionNumber    = (long)grib_decode_unsigned_long(pMessage, &pos_masterTablesVersionNumber, nbits_masterTablesVersionNumber);
    hdr->localTablesVersionNumber     = (long)grib_decode_unsigned_long(pMessage, &pos_localTablesVersionNumber, nbits_localTablesVersionNumber);

    hdr->typicalYear   = (long)grib_decode_unsigned_long(pMessage, &pos_typicalYear, nbits_typicalYear);
    typicalYear2       = hdr->typicalYear < 100 ? 2000 + hdr->typicalYear : hdr->typicalYear; /*ECC-556*/
    hdr->typicalMonth  = (long)grib_decode_unsigned_long(pMessage, &pos_typicalMonth, nbits_typicalMonth);
    hdr->typicalDay    = (long)grib_decode_unsigned_long(pMessage, &pos_typicalDay, nbits_typicalDay);
    hdr->typicalHour   = (long)grib_decode_unsigned_long(pMessage, &pos_typicalHour, nbits_typicalHour);
    hdr->typicalMinute = (long)grib_decode_unsigned_long(pMessage, &pos_typicalMinute, nbits_typicalMinute);
    hdr->typicalSecond = (long)grib_decode_unsigned_long(pMessage, &pos_typicalSecond, nbits_typicalSecond);
    hdr->typicalDate   = typicalYear2 * 10000 + hdr->typicalMonth * 100 + hdr->typicalDay;
    hdr->typicalTime   = hdr->typicalHour * 10000 + hdr->typicalMinute * 100 + hdr->typicalSecond;

    offset_section2          = BUFR_SECTION0_LEN + section1Length; /*bytes*/
    section2Length           = 0;
    hdr->localSectionPresent = (section1Flags != 0);
    if (hdr->localSectionPresent) {
        long pos_section2Length;
        const long nbits_section2Length = 3 * 8;
        pos_section2Length              = offset_section2 * 8;

        section2Length = grib_decode_unsigned_long(pMessage, &pos_section2Length, nbits_section2Length);

        if (hdr->bufrHeaderCentre == 98) {
            hdr->ecmwfLocalSectionPresent = 1;
            err                           = bufr_decode_rdb_keys(message, offset_section2, hdr);
        }
    }

    offset_section3       = BUFR_SECTION0_LEN + section1Length + section2Length; /*bytes*/
    pos_numberOfSubsets   = (offset_section3 + 4) * 8;
    hdr->numberOfSubsets  = grib_decode_unsigned_long(pMessage, &pos_numberOfSubsets, nbits_numberOfSubsets);

    pos_section3Flags   = (offset_section3 + 6) * 8;
    section3Flags       = (long)grib_decode_unsigned_long(pMessage, &pos_section3Flags, nbits_section3Flags);
    hdr->observedData   = (section3Flags & 1 << 7) ? 1 : 0;
    hdr->compressedData = (section3Flags & 1 << 6) ? 1 : 0;

    if (hdr->ecmwfLocalSectionPresent && hdr->bufrHeaderCentre == 98 && section2Length == 52) {
        err = bufr_decode_extra_rdb_keys(message, offset_section2, hdr);
    }

    return err;
}

static int bufr_decode_header(grib_context* c, const void* message, off_t offset, size_t size, codes_bufr_header* hdr)
{
    int err = GRIB_SUCCESS;

    hdr->message_offset = (unsigned long)offset;
    hdr->message_size   = (unsigned long)size;

    err = bufr_extract_edition(message, &hdr->edition);
    if (err) return err;

    if (hdr->edition == 3) {
        err = bufr_decode_edition3(message, hdr);
    }
    else if (hdr->edition == 4) {
        err = bufr_decode_edition4(message, hdr);
    }
    else {
        grib_context_log(c, GRIB_LOG_ERROR, "Unsupported BUFR edition: %ld", hdr->edition);
        err = GRIB_DECODING_ERROR;
    }

    return err;
}

static int count_bufr_messages(grib_context* c, FILE* f, int* n, int strict_mode)
{
    int err      = 0;
    void* mesg   = NULL;
    size_t size  = 0;
    off_t offset = 0;
    int done     = 0;

    *n = 0;
    if (!c)
        c = grib_context_get_default();

    while (!done) {
        mesg = wmo_read_bufr_from_file_malloc(f, 0, &size, &offset, &err);
        /*printf("Count so far=%d, mesg=%x, err=%d (%s)\n", *n, mesg, err, grib_get_error_message(err));*/
        if (!mesg) {
            if (err == GRIB_END_OF_FILE || err == GRIB_PREMATURE_END_OF_FILE) {
                done = 1; /* reached the end */
                break;
            }
            if (strict_mode)
                return GRIB_DECODING_ERROR;
        }
        if (mesg && !err) {
            grib_context_free(c, mesg);
        }
        (*n)++;
        if (*n >= INT_MAX/100) {
            grib_context_log(c, GRIB_LOG_ERROR, "Limit reached: looped %d times without finding a valid BUFR message", *n);
            done = 1;
            err = GRIB_INTERNAL_ERROR;
        }
    }
    (void)done;
    rewind(f);
    if (err == GRIB_END_OF_FILE)
        err = GRIB_SUCCESS;
    return err;
}

int codes_bufr_extract_headers_malloc(grib_context* c, const char* filename, codes_bufr_header** result, int* num_messages, int strict_mode)
{
    int err = 0, i = 0;
    FILE* fp     = NULL;
    void* mesg   = NULL;
    size_t size  = 0;
    off_t offset = 0;

    if (!c)
        c = grib_context_get_default();
    if (path_is_directory(filename)) {
        grib_context_log(c, GRIB_LOG_ERROR, "codes_bufr_extract_headers_malloc: \"%s\" is a directory", filename);
        return GRIB_IO_PROBLEM;
    }
    fp = fopen(filename, "rb");
    if (!fp) {
        grib_context_log(c, GRIB_LOG_ERROR, "codes_bufr_extract_headers_malloc: Unable to read file \"%s\"", filename);
        perror(filename);
        return GRIB_IO_PROBLEM;
    }
    err = count_bufr_messages(c, fp, num_messages, strict_mode);
    if (err) {
        grib_context_log(c, GRIB_LOG_ERROR, "codes_bufr_extract_headers_malloc: Unable to count BUFR messages in file \"%s\"", filename);
        fclose(fp);
        return err;
    }

    size = *num_messages;
    if (size == 0) {
        grib_context_log(c, GRIB_LOG_ERROR, "codes_bufr_extract_headers_malloc: No BUFR messages in file \"%s\"", filename);
        return GRIB_INVALID_MESSAGE;
    }
    *result = (codes_bufr_header*)calloc(size, sizeof(codes_bufr_header));
    if (!*result) {
        fclose(fp);
        return GRIB_OUT_OF_MEMORY;
    }
    i = 0;
    while (err != GRIB_END_OF_FILE) {
        if (i >= *num_messages)
            break;
        mesg = wmo_read_bufr_from_file_malloc(fp, 0, &size, &offset, &err);
        if (mesg != NULL && err == 0) {
            int err2 = bufr_decode_header(c, mesg, offset, size, &(*result)[i]);
            if (err2) {
                fclose(fp);
                return err2;
            }
            grib_context_free(c, mesg);
        }
        if (mesg && err) {
            if (strict_mode) {
                fclose(fp);
                grib_context_free(c, mesg);
                return GRIB_DECODING_ERROR;
            }
        }
        if (!mesg) {
            if (err != GRIB_END_OF_FILE && err != GRIB_PREMATURE_END_OF_FILE) {
                /* An error occurred */
                grib_context_log(c, GRIB_LOG_ERROR, "codes_bufr_extract_headers_malloc: Unable to read BUFR message");
                if (strict_mode) {
                    fclose(fp);
                    return GRIB_DECODING_ERROR;
                }
            }
        }
        ++i;
    }

    fclose(fp);
    return GRIB_SUCCESS;
}

static const char* codes_bufr_header_get_centre_name(long edition, long centre_code)
{
    (void)edition;
    switch (centre_code) {
        case 1:
            return "ammc";
        case 4:
            return "rums";
        case 7:
            return "kwbc";
        case 24:
            return "fapr";
        case 28:
            return "vabb";
        case 29:
            return "dems";
        case 34:
            return "rjtd";
        case 38:
            return "babj";
        case 40:
            return "rksl";
        case 41:
            return "sabm";
        case 46:
            return "sbsj";
        case 54:
            return "cwao";
        case 58:
            return "fnmo";
        case 69:
            return "nzkl";
        case 74:
            return "egrr";
        case 78:
            return "edzw";
        case 80:
            return "cnmc";
        case 82:
            return "eswi";
        case 84:
            return "lfpw";
        case 85:
            return "lfpw";
        case 86:
            return "efkl";
        case 88:
            return "enmi";
        case 94:
            return "ekmi";
        case 98:
            return "ecmf";
        case 173:
            return "nasa";
        case 195:
            return "wiix";
        case 204:
            return "niwa";
        case 214:
            return "lemm";
        case 215:
            return "lssw";
        case 218:
            return "habp";
        case 224:
            return "lowm";
        case 227:
            return "ebum";
        case 233:
            return "eidb";
        case 235:
            return "ingv";
        case 239:
            return "crfc";
        case 244:
            return "vuwien";
        case 245:
            return "knmi";
        case 246:
            return "ifmk";
        case 247:
            return "hadc";
        case 250:
            return "cosmo";
        case 252:
            return "mpim";
        case 254:
            return "eums";
        case 255:
            return "consensus";
        case 291:
            return "anso";
        case 292:
            return "ufz";
        default:
            return NULL;
    }
}

#if 0
/* TODO: Not efficient as it opens the code table every time */
static char* codes_bufr_header_get_centre_name(long edition, long centre_code)
{
    char full_path[2014] = {0,};
    char line[1024];
    FILE *f = NULL;
    const char* defs_path = grib_definition_path(NULL);

    if      (edition == 3) sprintf(full_path, "%s/common/c-1.table", defs_path);
    else if (edition == 4) sprintf(full_path, "%s/common/c-11.table", defs_path);
    else return NULL;

    f = codes_fopen(full_path, "r");
    if (!f) return NULL;

    while(fgets(line,sizeof(line)-1,f)) {
        char* p = line;
        int code = 0;
        char abbreviation[32] = {0,};
        char* q = abbreviation;

        line[strlen(line)-1] = 0;

        while(*p != '\0' && isspace(*p)) p++;

        if(*p == '#')
            continue;

        while(*p != '\0' && isspace(*p)) p++;

        if( *p =='\0' ) continue;

        Assert(isdigit(*p));
        while(*p != '\0') {
            if(isspace(*p)) break;
            code *= 10;
            code += *p - '0';
            p++;
        }

        while(*p != '\0' && isspace(*p)) p++;

        while(*p != '\0') {
            if(isspace(*p)) break;
            *q++ = *p++;
        }
        *q = 0;
        if (code == centre_code) {
            fclose(f);
            return strdup(abbreviation);
        }
    }
    fclose(f);
    return NULL;
}
#endif

int codes_bufr_header_get_string(codes_bufr_header* bh, const char* key, char* val, size_t* len)
{
    static const char* NOT_FOUND = "not_found";
    int isEcmwfLocal             = 0;
    Assert(bh);
    Assert(key);
    *len = strlen(NOT_FOUND); /*By default*/

    isEcmwfLocal = (bh->ecmwfLocalSectionPresent == 1);
    Assert(!(isEcmwfLocal && bh->bufrHeaderCentre != 98));
    Assert(!(bh->ecmwfLocalSectionPresent && !bh->localSectionPresent));

    if (strcmp(key, "message_offset") == 0)
        *len = sprintf(val, "%lu", bh->message_offset);
    else if (strcmp(key, "offset") == 0)
        *len = sprintf(val, "%lu", bh->message_offset);
    else if (strcmp(key, "message_size") == 0)
        *len = sprintf(val, "%lu", bh->message_size);
    else if (strcmp(key, "totalLength") == 0)
        *len = sprintf(val, "%lu", bh->message_size);
    else if (strcmp(key, "edition") == 0)
        *len = sprintf(val, "%ld", bh->edition);
    else if (strcmp(key, "masterTableNumber") == 0)
        *len = sprintf(val, "%ld", bh->masterTableNumber);
    else if (strcmp(key, "bufrHeaderSubCentre") == 0)
        *len = sprintf(val, "%ld", bh->bufrHeaderSubCentre);
    else if (strcmp(key, "bufrHeaderCentre") == 0)
        *len = sprintf(val, "%ld", bh->bufrHeaderCentre);

    else if (strcmp(key, "centre") == 0) {
        const char* centre_str = codes_bufr_header_get_centre_name(bh->edition, bh->bufrHeaderCentre);
        if (centre_str)
            *len = sprintf(val, "%s", centre_str);
        else
            *len = sprintf(val, "%ld", bh->bufrHeaderCentre);
    }

    else if (strcmp(key, "updateSequenceNumber") == 0)
        *len = sprintf(val, "%ld", bh->updateSequenceNumber);
    else if (strcmp(key, "dataCategory") == 0)
        *len = sprintf(val, "%ld", bh->dataCategory);
    else if (strcmp(key, "dataSubCategory") == 0)
        *len = sprintf(val, "%ld", bh->dataSubCategory);
    else if (strcmp(key, "masterTablesVersionNumber") == 0)
        *len = sprintf(val, "%ld", bh->masterTablesVersionNumber);
    else if (strcmp(key, "localTablesVersionNumber") == 0)
        *len = sprintf(val, "%ld", bh->localTablesVersionNumber);
    else if (strcmp(key, "typicalYear") == 0)
        *len = sprintf(val, "%ld", bh->typicalYear);
    else if (strcmp(key, "typicalMonth") == 0)
        *len = sprintf(val, "%ld", bh->typicalMonth);
    else if (strcmp(key, "typicalDay") == 0)
        *len = sprintf(val, "%ld", bh->typicalDay);
    else if (strcmp(key, "typicalHour") == 0)
        *len = sprintf(val, "%ld", bh->typicalHour);
    else if (strcmp(key, "typicalMinute") == 0)
        *len = sprintf(val, "%ld", bh->typicalMinute);
    else if (strcmp(key, "typicalSecond") == 0)
        *len = sprintf(val, "%ld", bh->typicalSecond);
    else if (strcmp(key, "typicalDate") == 0)
        *len = sprintf(val, "%06ld", bh->typicalDate);
    else if (strcmp(key, "typicalTime") == 0)
        *len = sprintf(val, "%06ld", bh->typicalTime);
    else if (strcmp(key, "internationalDataSubCategory") == 0)
        *len = sprintf(val, "%ld", bh->internationalDataSubCategory);
    else if (strcmp(key, "localSectionPresent") == 0)
        *len = sprintf(val, "%ld", bh->localSectionPresent);
    else if (strcmp(key, "ecmwfLocalSectionPresent") == 0)
        *len = sprintf(val, "%ld", bh->ecmwfLocalSectionPresent);

    /* Local ECMWF keys. Can be absent so must return NOT_FOUND */
    else if (strcmp(key, "rdbType") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rdbType);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "oldSubtype") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->oldSubtype);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "ident") == 0) {
        if (!isEcmwfLocal || strlen(bh->ident) == 0)
            strcpy(val, NOT_FOUND);
        else
            *len = sprintf(val, "%s", bh->ident);
    }
    else if (strcmp(key, "localYear") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localYear);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localMonth") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localMonth);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localDay") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localDay);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localHour") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localHour);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localMinute") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localMinute);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localSecond") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localSecond);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rdbtimeDay") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rdbtimeDay);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rdbtimeHour") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rdbtimeHour);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rdbtimeMinute") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rdbtimeMinute);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rdbtimeSecond") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rdbtimeSecond);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rectimeDay") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rectimeDay);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rectimeHour") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rectimeHour);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rectimeMinute") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rectimeMinute);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rectimeSecond") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rectimeSecond);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "restricted") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->restricted);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "isSatellite") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->isSatellite);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localLongitude1") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%g", bh->localLongitude1);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localLatitude1") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%g", bh->localLatitude1);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localLongitude2") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%g", bh->localLongitude2);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localLatitude2") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%g", bh->localLatitude2);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localLatitude") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%g", bh->localLatitude);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localLongitude") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%g", bh->localLongitude);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "qualityControl") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->qualityControl);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "newSubtype") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->newSubtype);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "rdbSubtype") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->rdbSubtype);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "daLoop") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->daLoop);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "localNumberOfObservations") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->localNumberOfObservations);
        else
            strcpy(val, NOT_FOUND);
    }
    else if (strcmp(key, "satelliteID") == 0) {
        if (isEcmwfLocal)
            *len = sprintf(val, "%ld", bh->satelliteID);
        else
            strcpy(val, NOT_FOUND);
    }

    else if (strcmp(key, "numberOfSubsets") == 0)
        *len = sprintf(val, "%lu", bh->numberOfSubsets);
    else if (strcmp(key, "observedData") == 0)
        *len = sprintf(val, "%ld", bh->observedData);
    else if (strcmp(key, "compressedData") == 0)
        *len = sprintf(val, "%ld", bh->compressedData);
    else
        return GRIB_NOT_FOUND;

    return GRIB_SUCCESS;
}

/* Returns 1 if the BUFR key is in the header and 0 if it is in the data section */
int codes_bufr_key_is_header(const grib_handle* h, const char* key, int* err)
{
    grib_accessor* acc = grib_find_accessor(h, key);
    if (!acc) {
        *err = GRIB_NOT_FOUND;
        return 0;
    }
    return ((acc->flags & GRIB_ACCESSOR_FLAG_BUFR_DATA) == 0);
}
