/***************************** 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 "MvQAbstractMessageScanner.h"
#include <QMutexLocker>
#include <QFileInfo>
#include <QTimer>
#include <QDebug>

#include <cassert>
#include <sstream>
#include "eccodes.h"

#include "MvKeyProfile.h"
#include "LogHandler.h"
#include "MvMessageMetaData.h"
#include "MvMiscellaneous.h"

#define UI_THREAD

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

MvQAbstractMessageScanner::MvQAbstractMessageScanner(MvMessageMetaData* data, QObject* parent) :
    QThread(parent),
    messageType_(NoType),
    messageNum_(0),
    totalMessageNum_(0),
    broadcastStep_(100),
    firstScan_(true),
    filterEnabled_(false),
    data_(data),
    prof_(nullptr),
    profLocal_(nullptr),
    profToProcess_(nullptr),
    readyEmitted_(false),
    needToStop_(false),
    startedReceived_(false),
    finishedReceived_(false)
{
    Q_ASSERT(data_);

    // 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::string>("std::string");
        qRegisterMetaType<MvKeyProfile*>("MvKeyProfile*");
        reg = true;
    }

    connect(this, SIGNAL(started()),
            this, SLOT(slotLoadStartedInThread()));

    connect(this, SIGNAL(finished()),
            this, SLOT(slotLoadFinishedInThread()));

    connect(this, SIGNAL(guiLog(int, std::string)),
            this, SLOT(slotGuiLog(int, std::string)));

    connect(this, SIGNAL(totalMessageNumComputed()),
            this, SLOT(slotTotalMessageNumComputed()));

    timer_ = new QTimer(this);
    // timer_->setInterval(1);
    connect(timer_, SIGNAL(timeout()),
            this, SLOT(timeout()));
}

MvQAbstractMessageScanner::~MvQAbstractMessageScanner() = default;

bool MvQAbstractMessageScanner::hasFinished() const
{
    return (startedReceived_ && finishedReceived_) ||
           (!startedReceived_ && !finishedReceived_);
}

void MvQAbstractMessageScanner::startScan(MvKeyProfile* prof)
{
    // We need to stop the running thread - if there is any
    stopLoad();

    // At this point we still cannot be sure that all the signals
    // emitted from the thread have reached the slots!!!

    // So we need to check!
    if (hasFinished()) {
        // All the signals reached the slots
        runScan(prof);
    }
    else {
        // Not all the signals reached the slots!
        // We start the timer - this will check periodically if the slots were called
        // and we can start the new task
        profToProcess_ = prof;
        timer_->start(1);
    }
}

void MvQAbstractMessageScanner::timeout()
{
    if (isRunning())
        return;

    if (!hasFinished())
        return;

    timer_->stop();

    runScan(profToProcess_);
    profToProcess_ = nullptr;
}

// we are in the main thread!
void MvQAbstractMessageScanner::runScan(MvKeyProfile* prof)
{
    Q_ASSERT(data_);
    Q_ASSERT(!isRunning());

    clear();

    // we cannot access data_ in the thread!!!!

    // it is possible that totalmessageNum_ > 0 but
    // firstScan_ == true. It can happen when an initial scan was interrupted!!
    if (data_->totalMessageNum() > 0 && data_->firstScan()) {
        data_->clearData();
    }

    readyEmitted_ = false;
    needToStop_ = false;
    fileName_ = data_->fileName();
    QFileInfo fInfo(QString::fromStdString(fileName_));
    qint64 fSize = fInfo.size();

    totalMessageNum_ = data_->totalMessageNum();
    messageNum_ = data_->messageNum();
    firstScan_ = data_->firstScan();

    // The profile we need to load (will be used outside the class)
    prof_ = prof;
    prof_->clearKeyData();

    // It is a profile reload!!!
    if (!firstScan_) {
        if (totalMessageNum_ == 0) {
            Q_EMIT profileReady(prof_);
            return;
        }

        filterEnabled_ = data_->isFilterEnabled();
        if (filterEnabled_) {
            filterOffset_ = data_->filterOffset();
            filterLen_ = data_->filterLen();
            filterCnt_ = data_->filterCnt();
        }
    }
    else {
        // The data must be empty at this point!!!!
        Q_ASSERT(totalMessageNum_ == 0);

        filterEnabled_ = data_->isFilterEnabled();
        if (!filterEnabled_) {
            Q_ASSERT(messageNum_ == 0);
        }
        else {
            filterOffset_ = data_->filterOffset();
            filterLen_ = data_->filterLen();
            filterCnt_ = data_->filterCnt();

            // If the there is a filter the messageNum equals the filter size
            Q_ASSERT(messageNum_ == static_cast<int>(filterOffset_.size()));
        }

        if (fSize == 0) {
            Q_EMIT profileReady(prof_);
            return;
        }
    }

    // Main thread!!!
    // If the file size is small enough (or the format is BUFR when we have
    // a fast scan method) we do everything in the main thread.
    // Otherwise we use the threaded profile load!
    if ((!firstScan_ && messageNum_ < 1000) ||
        (firstScan_ && !useThreadForScanning(fSize))) {
        if (firstScan_ && precomputeMessageNum()) {
            totalMessageNum_ = computeTotalMessageNum();
            slotTotalMessageNumComputed();
        }

        // Load the profile
        loadKeyProfile(prof_);

        // Finish the load
        finishScan();
    }
    else {
        // Real thread!
        if (isRunning()) {
            if (!wait(5000)) {
                exit(1);
            }
        }

        // The profile we use in the thread!!!
        profLocal_ = prof_->clone();

        start();
    }
}

void MvQAbstractMessageScanner::clear()
{
    messageNum_ = 0;
    totalMessageNum_ = 0;
    broadcastStep_ = 0;
    firstScan_ = true;
    filterEnabled_ = false;
    prof_ = nullptr;
    if (profLocal_) {
        delete profLocal_;
        profLocal_ = nullptr;
    }
    readyEmitted_ = false;
    needToStop_ = false;
    startedReceived_ = false;
    finishedReceived_ = false;
}

void MvQAbstractMessageScanner::stopLoad()
{
    if (!isRunning())
        return;

    mutex_.lock();
    needToStop_ = true;
    mutex_.unlock();
    if (!wait(2000)) {
        Q_ASSERT(0);
        exit(1);
    }
}

void MvQAbstractMessageScanner::slotLoadStartedInThread()
{
    startedReceived_ = true;
    emit loadStarted();
}

void MvQAbstractMessageScanner::slotLoadFinishedInThread()
{
    finishedReceived_ = true;
    finishScan();
}

void MvQAbstractMessageScanner::finishScan()
{
    if (needToStop_) {
        return;
    }

    if (!readyEmitted_) {
        // This can only be emitted from the main thread!!!!
        Q_EMIT profileReady(prof_);
        readyEmitted_ = true;
    }

    if (filterEnabled_ && firstScan_) {
        data_->updateFilterCnt(filterOffset_, filterCnt_);
    }

    data_->setFirstScan(false);

    emit loadFinished();
}

// We are inside the thread
void MvQAbstractMessageScanner::run()
{
    if (firstScan_ && precomputeMessageNum()) {
        totalMessageNum_ = computeTotalMessageNum();
        Q_EMIT totalMessageNumComputed();
    }

    // loadKeyProfile(prof_);
    loadKeyProfile(profLocal_);
}

// This executes ouside the thread (in the main GUI thread!!!)
void MvQAbstractMessageScanner::slotGuiLog(int type, std::string msg)
{
    switch (type) {
        case TaskLogType:
            GuiLog().task() << msg;
            break;
        case ErrorLogType:
            GuiLog().error() << msg;
            break;
        default:
            break;
    }
}

void MvQAbstractMessageScanner::prepareKeyProfile()
{
    Q_ASSERT(messageNum_ > 0);

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

    // Set the proper keytypes in the profile
    MvKey* indexKey = prof_->key("MV_Index");
    for (unsigned int i = 0; i < prof_->size(); i++) {
        if (prof_->at(i) != indexKey) {
            prof_->at(i)->setKeyType(MvKey::EccodesKey);
        }
    }

    // Fill index key
    if (indexKey) {
        assert(indexKey->valueType() == MvKey::IntType);
        for (int i = 0; i < indexKey->valueNum(); i++) {
            indexKey->setIntValue(i, i + 1);
        }
    }
}

void MvQAbstractMessageScanner::loadKeyProfile(MvKeyProfile* prof)
{
    if (totalMessageNum_ > 0) {
        if (isRunning()) {
            broadcastStep_ = totalMessageNum_ / 200;
            if (broadcastStep_ == 0)
                broadcastStep_ = 1;

            if (broadcastStep_ > 500)
                broadcastStep_ = 500;
        }
        else {
            broadcastStep_ = totalMessageNum_;
        }

        readMessages(prof);
    }
}


int MvQAbstractMessageScanner::computeTotalMessageNum()
{
    FILE* fp = nullptr;
    fp = fopen(fileName_.c_str(), "rb");
    if (!fp) {
        // log->error("GribMetaData::readMessages() ---> Cannot open grib file: \n        " + fileName_ + "\n");
        return 0;
    }

    int res = 0;
    if (codes_count_in_file(nullptr, fp, &res) != CODES_SUCCESS)
        res = 0;

    fclose(fp);
    return res;

#if 0
    int count=0;
    void* mesg=nullptr;
    size_t size=0;
    off_t offset=0;
    int err=GRIB_SUCCESS;
    typedef void* (*wmo_read_proc)(FILE *, int, size_t *, off_t *, int *);
    wmo_read_proc wmo_read = nullptr;
    grib_context* c=codes_context_get_default();

    if(messageType_ == GribType)
        wmo_read=wmo_read_grib_from_file_malloc;
    else if (messageType_ == BufrType)
        wmo_read=wmo_read_bufr_from_file_malloc;
    else
        return 0;

    int done = 0;
    while(!done)
    {
        mesg = wmo_read(fp, 0, &size, &offset, &err);
        if (!mesg)
        {
            if (err == GRIB_END_OF_FILE || err == GRIB_PREMATURE_END_OF_FILE)
            {
                done = 1; // reached the end
            }
        }
        if (mesg && !err)
        {
            //grib_context_free(c,mesg);
            free(mesg);
            count++;
        }
    }

    fclose(fp);
    return count;
#endif
}

int MvQAbstractMessageScanner::percentage(int current, int total)
{
    return 100 * static_cast<int>(static_cast<float>(current) / static_cast<float>(total));
}

void MvQAbstractMessageScanner::readMessageFailed(MvKeyProfile* prof, int msgCnt, int profIndex)
{
    prof->markErrorRow(msgCnt);
    for (unsigned int i = 0; i < prof->size(); i++) {
        if (prof->at(i)->valueType() == MvKey::StringType)
            prof->at(i)->setStringValue(profIndex, "N/A");
    }
}

//------------------------------
// Format key "count"
//------------------------------
void MvQAbstractMessageScanner::formatKeyCount(MvKeyProfile* /*prof*/, int num)
{
    char c[20];
    int n = 0, c_format_id = 0;
    const char c_format[4][5] = {"%02d", "%03d", "%04d", "%05d"};

    if (num < 100) {
        c_format_id = 0;
    }
    else if (num < 1000) {
        c_format_id = 1;
    }
    else if (num < 10000) {
        c_format_id = 2;
    }
    else {
        c_format_id = 3;
    }

    MvKey* key = prof_->key("count");
    if (key) {
        for (size_t i = 0; i < key->value().size(); i++) {
            std::string s = key->value()[i];
            if (!s.empty()) {
                n = metview::fromString<int>(s);
                sprintf(c, c_format[c_format_id], n);
                s = c;
                key->setValue(i, s);
            }
        }
    }
}
