/***************************** 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 "MvQGribMessageScanner.h"
#include <QMutexLocker>

#include <cassert>
#include <sstream>

#include "MvEccBufr.h"
#include "MvKeyProfile.h"
#include "LogHandler.h"
#include "GribMetaData.h"
#include "MvMiscellaneous.h"
#include "MvLog.h"

#define UI_THREAD

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

#ifdef ECCODES_UI
#ifndef ABS
#define ABS(x) (((x) >= 0) ? (x) : -(x))
#endif
#endif

GribMessageScanner::GribMessageScanner(MvMessageMetaData* data, QObject* parent) :
    MvQAbstractMessageScanner(data, parent)
{
    messageType_ = GribType;

    connect(this, SIGNAL(progress(int, MvKeyProfile*, int, int)),
            this, SLOT(slotProgress(int, MvKeyProfile*, int, int)));
}

GribMessageScanner::~GribMessageScanner() = default;

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

bool GribMessageScanner::precomputeMessageNum() const
{
    return true;
}

// This executes ouside the thread (in the main GUI thread!!!)
void GribMessageScanner::slotProgress(int percent, MvKeyProfile* chunkProf, int chunkStart, int chunkSize)
{
    // If stop was already requested we ignore the progress
    if (needToStop_)
        return;

    if (!chunkProf) {
        Q_EMIT profileProgress(percent);
    }
    else {
        Q_ASSERT(chunkProf);
        Q_ASSERT(chunkProf->valueNum() >= chunkSize);

        // We initialise the data structures when slotProgress is called for the first time
        if (chunkStart == 0) {
            if (firstScan_) {
                Q_ASSERT(data_->totalMessageNum() == 0);
            }

            if (filterEnabled_)
                Q_ASSERT(data_->messageNum() == static_cast<int>(filterOffset_.size()));

            if (firstScan_) {
                data_->setTotalMessageNum(totalMessageNum_);
                if (!filterEnabled_) {
                    messageNum_ = totalMessageNum_;
                    Q_ASSERT(data_->messageNum() == data_->totalMessageNum());
                }
            }

            // Pre allocate the profile
            prepareKeyProfile();

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

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

        Q_EMIT profileProgress(percent);

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

// This slot is called when totalMessageNum_ is computed in the thread!
void GribMessageScanner::slotTotalMessageNumComputed()
{
    // We handle this when the first chunk of data is ready in
    // slotProgress()
#if 0
    Q_ASSERT(data_->totalMessageNum() == 0);

    if(filterEnabled_)
        Q_ASSERT(data_->messageNum() == static_cast<int>(filterOffset_.size()));

    //Pre allocate the profile
    prof_->preAllocate(totalMessageNum_);

    //Pre allocate the message vector in the data
    data_->setTotalMessageNum(totalMessageNum_);

    if(!readyEmitted_)
    {
        Q_EMIT profileReady(prof_);
        readyEmitted_=true;
    }
#endif
}

void GribMessageScanner::readMessages(MvKeyProfile* profOri)
{
    /*std::vector<char*> name_space;
    name_space.push_back(0);
    name_space.push_back("geography");
    name_space.push_back("ls");== GRIB_SUCCESS
    name_space.push_back("mars");
    name_space.push_back("parameter");
    name_space.push_back("time");
    name_space.push_back("vertical");*/

    FILE* fp = nullptr;
    codes_handle* ch = nullptr;
    int err = 0;
    int grib_count = 0;
    int profIndex = 0;
    long longValue = 0;

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

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

    // hasMultiMessage_=false;
    // bool firstScan_=true;
    // int messageNum_=0;

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

    // open grib file for reading
    fp = fopen(fileName_.c_str(), "rb");
    if (!fp) {
        Q_EMIT guiLog(ErrorLogType, "GribMessageScanner::readMessages() ---> Cannot open GRIB file: \n        " +
                                        fileName_ + "\n");
        return;
    }

    // For filtered datasets
    if (filterEnabled_ == true) {
        int totalScanNum = filterOffset_.size() + totalMessageNum_;
        int totalCnt = 0;

        // At the first scan we need to find the message cnt for each filtered message.
        // The goal is to fill up the filterCnt_ vector.
        if (firstScan_) {
            // initialise filterCnt_
            size_t foundCnt = 0;
            for (int& i : filterCnt_) {
                i = -1;
            }

            int msgCnt = 0;
            while ((ch = codes_handle_new_from_file(nullptr, fp, PRODUCT_GRIB, &err)) != nullptr || err != CODES_SUCCESS) {
                if (!ch) {
                    Q_EMIT guiLog(ErrorLogType,
                                  "GribMetaData::readMessages() --->  Unable to create GRIB handle for message: " +
                                      std::to_string(msgCnt) + "\n");
                }

                msgCnt++;
                totalCnt++;

                if (totalCnt % broadcastStep_ == 0) {
                    Q_EMIT progress(percentage(totalCnt, totalScanNum), nullptr, -1, -1);
                }

                if (totalCnt % 25 == 0) {
                    bool sval = false;
                    {
                        QMutexLocker locker(&mutex_);
                        sval = needToStop_;
                    }
                    if (sval) {
                        if (ch)
                            codes_handle_delete(ch);
                        fclose(fp);
                        return;
                    }
                }

                if (ch) {
                    if (codes_get_long(ch, "offset", &longValue) == CODES_SUCCESS) {
                        for (size_t i = 0; i < filterOffset_.size(); i++) {
                            if (filterCnt_[i] == -1) {
                                off_t offset_diff = longValue - filterOffset_[i];
                                if (ABS(offset_diff) < 120) {
                                    filterCnt_[i] = msgCnt;
                                    filterOffset_[i] = longValue;
                                    foundCnt++;
                                    break;
                                }
                            }
                        }
                    }
                    else {
                        Q_EMIT guiLog(ErrorLogType,
                                      "GribMetaData::readMessages() --->  Cannot get offset for message count: " +
                                          std::to_string(msgCnt) + "\n");

                        codes_handle_delete(ch);
                        return;
                    }

                    // currentOffset+=longValue;
                    codes_handle_delete(ch);

                    if (foundCnt == filterOffset_.size())
                        break;
                }
            }
        }

        // Read the filtered messages
        for (long i : filterOffset_) {
            fseek(fp, i, SEEK_SET);

            int err = 0;
            ch = codes_handle_new_from_file(nullptr, fp, PRODUCT_GRIB, &err);
            if (!ch) {
                Q_EMIT guiLog(ErrorLogType,
                              "GribMetaData::readMessages() --->  Unable to create grib handle for offset: " +
                                  QString::number(i).toStdString() + "\n");
            }

            readMessage(prof, ch, grib_count, profIndex);
            grib_count++;
            totalCnt++;
            profIndex++;

            if (totalCnt % broadcastStep_ == 0) {
                int chunkStart = grib_count - profIndex;
                int chunkSize = profIndex;
                Q_EMIT progress(percentage(totalCnt, totalScanNum), prof, chunkStart, chunkSize);

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

            if (totalCnt % 25 == 0) {
                bool sval = false;
                {
                    QMutexLocker locker(&mutex_);
                    sval = needToStop_;
                }
                if (sval) {
                    if (ch)
                        codes_handle_delete(ch);
                    fclose(fp);
                    return;
                }
            }

            if (ch)
                codes_handle_delete(ch);
        }

        if (profIndex > 0) {
            int chunkStart = grib_count - profIndex;
            int chunkSize = profIndex;
            Q_EMIT progress(percentage(totalCnt, totalScanNum),
                            prof, chunkStart, chunkSize);
        }
    }
    else {
        // Get messages form the file
        while ((ch = codes_handle_new_from_file(nullptr, fp, PRODUCT_GRIB, &err)) != nullptr || err != CODES_SUCCESS) {
            if (!ch) {
                Q_EMIT guiLog(ErrorLogType,
                              "GribMetaData::readMessages() --->  Unable to create grib handle for message count: " +
                                  QString::number(grib_count + 1).toStdString() + "\n");
            }

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

            if (grib_count % broadcastStep_ == 0) {
                int chunkStart = grib_count - profIndex;
                int chunkSize = profIndex;
                Q_EMIT progress(percentage(grib_count, totalMessageNum_),
                                prof, chunkStart, chunkSize);

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

            if (grib_count % 25 == 0) {
                bool sval = false;
                {
                    QMutexLocker locker(&mutex_);
                    sval = needToStop_;
                }
                if (sval) {
                    if (ch)
                        codes_handle_delete(ch);
                    fclose(fp);
                    return;
                }
            }

            if (ch)
                codes_handle_delete(ch);
        }

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

    fclose(fp);

    if (firstScan_) {
        if (totalMessageNum_ != grib_count) {
            if (totalMessageNum_ != 0)
                hasMultiMessage_ = true;

            totalMessageNum_ = grib_count;
        }
    }

    messageNum_ = grib_count;

    formatKeyCount(prof, grib_count);
}

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

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

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

        if (grib_get_native_type(ch, name.c_str(), &keyType) == CODES_SUCCESS &&
            grib_get_size(ch, name.c_str(), &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.c_str(), &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 (grib_get_string(ch, name.c_str(), value, &vlen) == CODES_SUCCESS) {
                        prof->at(i)->setStringValue(profIndex, value);
                        foundValue = true;
                    }
                }
            }
            else if ((strcmp(name.c_str(), "paramId") == 0 || strcmp(name.c_str(), "parameter.paramId") == 0) &&
                     keyType == CODES_TYPE_LONG) {
                if (prof->at(i)->readIntAsString() == false) {
                    if (codes_get_long(ch, name.c_str(), &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.c_str(), value, &vlen) == GRIB_SUCCESS) {
                        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)->name() != "MV_Frame") {
                if (prof->at(i)->valueType() == MvKey::StringType) {
                    prof->at(i)->setStringValue(profIndex, "N/A");
                }
            }
        }
    }
}
