/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQBufrMessageScanner.h"
#include <QMutexLocker>
#include <QDebug>

#include <assert.h>
#include <sstream>

#include "BufrMetaData.h"
#include "MvEccBufr.h"
#include "MvKeyProfile.h"
#include "LogHandler.h"

#define UI_THREAD

#define MV_CODES_CHECK(a, msg) MvEccBufr::codesCheck(#a, __FILE__, __LINE__, a, msg)


BufrMessageScanner::BufrMessageScanner(MvMessageMetaData* data, QObject* parent) :
    MvQAbstractMessageScanner(data, parent),
    bufrData_(static_cast<BufrMetaData*>(data))
{
    Q_ASSERT(bufrData_);

    messageType_ = BufrType;

    //We will need to pass various non-Qt types via signals and slots for error messages.
    //So we need to register these types.
    static bool reg = false;
    if (!reg) {
        qRegisterMetaType<std::vector<MvEccBufrMessage*> >("std::vector<MvEccBufrMessage*>");
        reg = true;
    }

    connect(this, SIGNAL(progress(int, MvKeyProfile*, int, int, std::vector<MvEccBufrMessage*>)),
            this, SLOT(slotProgress(int, MvKeyProfile*, int, int, std::vector<MvEccBufrMessage*>)));
}

BufrMessageScanner::~BufrMessageScanner() = default;

bool BufrMessageScanner::useThreadForScanning(qint64 fSize) const
{
    return fSize > 1024 * 1024;
}

bool BufrMessageScanner::precomputeMessageNum() const
{
    return false;
}

//We are inside the thread
void BufrMessageScanner::run()
{
    readMessages(profLocal_);
}

void BufrMessageScanner::loadKeyProfile(MvKeyProfile* prof)
{
    if (firstScan_)
        Q_ASSERT(totalMessageNum_ == 0);

    readMessages(prof);
}

void BufrMessageScanner::readMessages(MvKeyProfile* profOri)
{
    int err       = 0;
    int bufrCount = 0;
    int profIndex = 0;
    std::vector<MvEccBufrMessage*> msgVec;
    int num_messages                = 0;
    codes_bufr_header* header_array = nullptr;

    // we use non strict mode
    err = codes_bufr_extract_headers_malloc(nullptr, fileName_.c_str(), &header_array, &num_messages, 0);
    if (err) {
        Q_EMIT guiLog(ErrorLogType,
                      "BufrMessageScanner::readMessages() --->  Errors were generated during header scan: " +
                          std::string(grib_get_error_message(err)));
    }

    if (firstScan_) {
        Q_ASSERT(totalMessageNum_ == 0);
        totalMessageNum_ = num_messages;
        Q_ASSERT(bufrData_->totalMessageNum() == 0);
        Q_ASSERT(bufrData_->messageNum() == 0);
        messageNum_ = totalMessageNum_;
    }

    Q_ASSERT(num_messages == totalMessageNum_);
    Q_ASSERT(messageNum_ == totalMessageNum_);

    if (totalMessageNum_ <= 0) {
        if (header_array != nullptr) {
            free(header_array);
        }
        return;
    }

    broadcastStep_ = totalMessageNum_ / 200;
    if (broadcastStep_ == 0)
        broadcastStep_ = 1;

    //At this point we must know the total message num!!!
    assert(totalMessageNum_ > 0);
    assert(profOri);
    MvKeyProfile* prof = profOri->clone();
    prof->preAllocate(broadcastStep_);

    Q_EMIT guiLog(TaskLogType, "Generating bufr key list for all the messages" +
                                   GuiLog::methodKey() + "ecCodes C interface");

    for (int i = 0; i < num_messages; ++i) {
        if (firstScan_) {
            auto* m = new MvEccBufrMessage(&header_array[i], i);
            msgVec.push_back(m);
            if (!m->isHeaderValid() || !m->isDataValid()) {
                prof->markErrorRow(i);
            }
        }
        else {
            msgVec.push_back(nullptr);
        }

        readMessage(prof, &header_array[i], profIndex);

        profIndex++;
        bufrCount++;

        if (bufrCount % broadcastStep_ == 0) {
            //The slot (outside the thread will take ownership of the
            //prof!
            int chunkStart = bufrCount - profIndex;
            int chunkSize  = profIndex;
            Q_EMIT progress(percentage(bufrCount, totalMessageNum_),
                            prof, chunkStart, chunkSize, msgVec);

            //We allocate a new profile
            prof = profOri->clone();
            prof->preAllocate(broadcastStep_);
            profIndex = 0;
            msgVec.clear();
        }
    }

    if (profIndex > 0) {
        int chunkStart = bufrCount - profIndex;
        int chunkSize  = profIndex;
        Q_EMIT progress(percentage(bufrCount, totalMessageNum_),
                        prof, chunkStart, chunkSize, msgVec);
    }

    free(header_array);
}

void BufrMessageScanner::readMessage(MvKeyProfile* prof, codes_bufr_header* bh, int profIndex)
{
    long centre     = bh->bufrHeaderCentre;
    bool hasLocal2  = (centre == 98 && bh->ecmwfLocalSectionPresent != 0);
    size_t vlen     = 0;
    char value[512] = {
        0,
    };

    for (std::size_t i = 0; i < prof->size(); i++) {
        std::string strName = prof->at(i)->name();

        std::size_t pos;
        if ((pos = strName.find(":98")) == strName.size() - 3) {
            if (hasLocal2) {
                strName = strName.substr(0, strName.size() - 3);
            }
            else {
                prof->at(i)->setStringValue(profIndex, "N/A");
                continue;
            }
        }

        if (strName == "section1Flags") {
            strName = "localSectionPresent";
        }

        if (prof->at(i)->name() != "MV_Index" && prof->at(i)->name() != "MV_Frame") {
            vlen     = 0;
            value[0] = '\n';
            if (codes_bufr_header_get_string(bh, strName.c_str(), value, &vlen) == CODES_SUCCESS) {
                if (strName != "localSectionPresent") {
                    if (strcmp(value, "not_found") == 0) {
                        prof->at(i)->setStringValue(profIndex, "N/A");
                    }
                    else {
                        prof->at(i)->setStringValue(profIndex, value);
                    }
                }
                else {
                    prof->at(i)->setStringValue(profIndex, (strcmp(value, "0") == 0) ? "0" : "1");
                }
            }
            else {
                prof->at(i)->setStringValue(profIndex, "N/A");
            }
        }
    }
}


//This executes ouside the thread (in the main GUI thread!!!)
//It takes ownership of the prof pointer!!!!
void BufrMessageScanner::slotProgress(int percent, MvKeyProfile* prof, int chunkStart, int chunkSize, std::vector<MvEccBufrMessage*> msgVec)
{
    //If stop was already requested we ignore the progress
    if (needToStop_)
        return;

    Q_ASSERT(prof);
    Q_ASSERT(prof->valueNum() >= chunkSize);

    //We initialise the data structures when slotProgress is called for the first time
    if (chunkStart == 0) {
        Q_ASSERT(!filterEnabled_);

        if (firstScan_) {
            Q_ASSERT(bufrData_->totalMessageNum() == 0);
            Q_ASSERT(bufrData_->messageNum() == 0);
            messageNum_ = totalMessageNum_;
        }
        Q_ASSERT(messageNum_ == totalMessageNum_);

        //Pre allocate the profile
        prepareKeyProfile();

        //Pre allocate the message vector in the data
        if (firstScan_) {
            bufrData_->setTotalMessageNum(totalMessageNum_);
        }
        Q_ASSERT(bufrData_->messageNum() == bufrData_->totalMessageNum());
        Q_ASSERT(static_cast<int>(bufrData_->messages().size()) == messageNum_);
    }

    if (firstScan_ && chunkStart >= 0) {
        bufrData_->setMessage(chunkStart, msgVec);
    }

    prof_->setValuesInChunk(chunkStart, chunkSize, prof);
    delete prof;

    Q_EMIT profileProgress(percent);

    if (isRunning() && !readyEmitted_) {
        readyEmitted_ = true;
        Q_EMIT profileReady(prof_);
    }
}

//This slot is called when totalMessageNum_ is determined!
void BufrMessageScanner::slotTotalMessageNumComputed()
{
    //We handle this when the first chunk of data is ready in
    //slotProgress()
}


#if 0

void BufrMessageScanner::readMessages(MvKeyProfile* profOri)
{
    FILE* fp;
    codes_handle* ch = nullptr;
    int err          = 0;
    int bufr_count   = 0;
    int profIndex    = 0;
    std::vector<MvEccBufrMessage*> msgVec;

    //At this point we must know the total message num!!!
    assert(totalMessageNum_ > 0);

    assert(profOri);
    MvKeyProfile* prof = profOri->clone();
    prof->preAllocate(broadcastStep_);

    Q_EMIT guiLog(TaskLogType, "Generating bufr key list for all the messages" +
                                   GuiLog::methodKey() + "ecCodes C interface");

    //open bufr file for reading
    fp = fopen(fileName_.c_str(), "rb");
    if (!fp) {
        Q_EMIT guiLog(ErrorLogType, "Generating bufr key list for all the messages" +
                                        GuiLog::methodKey() + "ecCodes C interface");
        return;
    }

    //Get messages form the file
    while ((ch = codes_handle_new_from_file(nullptr, fp, PRODUCT_BUFR, &err)) != nullptr || err != CODES_SUCCESS) {
        if (!ch) {
            Q_EMIT guiLog(ErrorLogType,
                          "BufrMessageScanner::readMessages() --->  Unable to create code handle for message count: " +
                              QString::number(bufr_count + 1).toStdString());
        }

        if (firstScan_) {
            MvEccBufrMessage* m = new MvEccBufrMessage(ch, bufr_count);
            msgVec.push_back(m);

            if (!m->isHeaderValid() || !m->isDataValid()) {
                prof->markErrorRow(bufr_count);
            }
        }
        else
            msgVec.push_back(nullptr);

        readMessage(prof, ch, bufr_count, profIndex);
        bufr_count++;
        profIndex++;

        if (bufr_count % broadcastStep_ == 0) {
            //The slot (outside the thread will take ownership of the
            //prof!
            int chunkStart = bufr_count - profIndex;
            int chunkSize  = profIndex;
            Q_EMIT progress(percentage(bufr_count, totalMessageNum_),
                            prof, chunkStart, chunkSize, msgVec);

            //We allocate a new profile
            prof = profOri->clone();
            prof->preAllocate(broadcastStep_);
            profIndex = 0;
            msgVec.clear();
        }

        if (bufr_count % 25 == 0) {
            bool sval = false;
            {
                QMutexLocker locker(&mutex_);
                sval = needToStop_;
            }
            if (sval) {
                if (ch) {
                    codes_handle_delete(ch);
                }
                fclose(fp);
#if 0
                if(msgVec.size() > 0)
                {
                    Q_EMIT progress(bufr_count,prof,msgVec);
                }
#endif
                return;
            }
        }

        if (ch)
            codes_handle_delete(ch);
    }

    fclose(fp);

    if (profIndex > 0) {
        int chunkStart = bufr_count - profIndex;
        int chunkSize  = profIndex;
        Q_EMIT progress(percentage(bufr_count, totalMessageNum_),
                        prof, chunkStart, chunkSize, msgVec);
    }

    //formatKeyCount(prof,bufr_count);
}

void BufrMessageScanner::readMessage(MvKeyProfile* prof, codes_handle* ch, int msgCnt, int profIndex)
{
    if (!ch) {
        readMessageFailed(prof, msgCnt, profIndex);
        return;
    }

    const int MAX_VAL_LEN = 1024;
    long longValue;
    char value[MAX_VAL_LEN];
    std::string svalue;
    size_t vlen = MAX_VAL_LEN;

    long centre;
    if (MV_CODES_CHECK(codes_get_long(ch, "bufrHeaderCentre", &centre), 0) == false) {
        readMessageFailed(prof, msgCnt, profIndex);
        return;
    }

    if (MV_CODES_CHECK(codes_get_long(ch, "section1Flags", &longValue), 0) == false) {
        readMessageFailed(prof, msgCnt, profIndex);
        return;
    }

#if 0
    if(firstTypicalDate_.empty())
    {
        long lDateValue;
        if(MV_CODES_CHECK(codes_get_long(ch,"typicalDate",&lDateValue),0))
        {
            firstTypicalDate_ = std::to_string<long>(lDateValue);
        }
        else
        {
            firstTypicalDate_="N/A";
        }
    }
#endif

    bool hasLocal2 = (centre == 98 && longValue != 0);

    for (unsigned int i = 0; i < prof->size(); i++) {
        size_t len;
        int keyType;
        const char* name    = prof->at(i)->name().c_str();
        std::string strName = prof->at(i)->name();
        bool foundValue     = false;

        std::size_t pos;
        if ((pos = strName.find(":98")) == strName.size() - 3) {
            if (hasLocal2) {
                strName = strName.substr(0, strName.size() - 3);
                name    = strName.c_str();
            }
            else {
                prof->at(i)->setStringValue(profIndex, "N/A");
                continue;
            }
        }

        if (grib_get_native_type(ch, name, &keyType) == CODES_SUCCESS &&
            grib_get_size(ch, name, &len) == CODES_SUCCESS &&
            (keyType == CODES_TYPE_STRING || keyType == CODES_TYPE_LONG ||
             keyType == CODES_TYPE_DOUBLE)) {
            if (keyType == CODES_TYPE_STRING || len == 1) {
                if ((prof->at(i)->readIntAsString() == false) &&
                    keyType == CODES_TYPE_LONG) {
                    if (codes_get_long(ch, name, &longValue) == CODES_SUCCESS) {
                        std::stringstream s;
                        s << longValue;
                        prof->at(i)->setStringValue(profIndex, s.str().c_str());
                        foundValue = true;
                    }
                }
                else {
                    vlen = MAX_VAL_LEN;
                    if (codes_get_string(ch, name, value, &vlen) == CODES_SUCCESS) {
                        if (prof->at(i)->name() == "section1Flags") {
                            prof->at(i)->setStringValue(profIndex, (strcmp(value, "0") == 0) ? "0" : "1");
                        }
                        else {
                            prof->at(i)->setStringValue(profIndex, value);
                        }
                        foundValue = true;
                    }
                }
            }
            else {
                std::stringstream s;
                s << len;
                svalue = "Array (" + s.str() + ")";
                prof->at(i)->setStringValue(profIndex, svalue);
                foundValue = true;
            }
        }

        if (!foundValue) {
            if (prof->at(i)->name() != "MV_Index")
                prof->at(i)->setStringValue(profIndex, "N/A");
        }
    }
}

#endif
