/***************************** 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 "Metview.h"

#include "MvOdb.h"

#include <iostream>
#include <stdexcept>

#include <fcntl.h>
#include <unistd.h>

#include "MvScanFileType.h"

#include <eckit/exception/Exceptions.h>


void messageToLog(const std::string& msg, int severity, const char* useLogWindow, MvRequest& out)
{
    if (useLogWindow) {
        MvRequest reqst("USER_MESSAGE");
        reqst("INFO") = msg.c_str();
        send_message(MvApplication::getService(), (request*)reqst);
    }

    out = MvRequest("REPLY");
    out("_FILTER_MESSAGE") = msg.c_str();

    // also send to the Mars/Metview module log

    char msg_c[1024];
    strncpy(msg_c, msg.c_str(), 1023);
    marslog(severity, msg_c);
}


void getStringLines(std::string ibuff, std::vector<std::string>& obuff)
{
    std::string::size_type pos = 0, pos_prev = 0;
    while ((pos = ibuff.find("\n", pos_prev)) != std::string::npos) {
        obuff.push_back(ibuff.substr(pos_prev, pos - pos_prev));
        pos_prev = pos + 1;
    }
    if (pos_prev < ibuff.size()) {
        obuff.push_back(ibuff.substr(pos_prev));
    }
}

void getShellArgument(std::string ibuff, std::string& obuff)
{
    std::string::size_type pos = 0;
    obuff = ibuff;

    // Replace linebrake with whitespace
    pos = 0;
    while ((pos = obuff.find("\n", pos)) != std::string::npos) {
        obuff.replace(pos, 1, " ");
        pos++;
    }

    // Replace linebrake with whitespace
    pos = 0;
    while ((pos = obuff.find("$", pos)) != std::string::npos) {
        obuff.replace(pos, 1, "\\$");
        pos += 2;
    }


    // "(" -> "/("
    /*pos=0;
    while((pos=obuff.find("(",pos)) !=std::string::npos)
    {
        obuff.replace(pos,1,"\\(");
        pos+=3;
    }

    // ")" -> "/)"
    pos=0;
    while((pos=obuff.find(")",pos)) !=std::string::npos)
    {
        obuff.replace(pos,1,"\\)");
        pos+=3;
    }*/
}

class Base : public MvService
{
protected:
    Base(const char* a) :
        MvService(a) {}
};

class OdbFilter : public Base
{
public:
    OdbFilter() :
        Base("ODB_FILTER") {}
    void serve(MvRequest&, MvRequest&);
};


void OdbFilter::serve(MvRequest& in, MvRequest& out)
{
    //    std::cout << "---OdbFilter::serve() IN---" << std::endl;
    in.print();

    // Get prefix
    std::string verb = in.getVerb();
    const char* useLogWindow = (const char*)in("_USELOGWINDOW");
    const char* dataPath;

    MvRequest dataR = in("ODB_DATA");
    if (dataR != nullptr) {
        const char* dc = dataR("PATH");
        dataPath = dc;
    }
    else {
        dataPath = in("ODB_FILENAME");
    }

    if (dataR == nullptr &&
        (dataPath == nullptr || strcmp(dataPath, "OFF") == 0 || strlen(dataPath) == 0)) {
        messageToLog("No ODB file is specified!", LOG_EROR, useLogWindow, out);
        setError(13);
        return;
    }

    if (static_cast<const char*>(in("ODB_QUERY")) == nullptr) {
        messageToLog("No ODB/SQL query is specified!", LOG_EROR, useLogWindow, out);
        setError(13);
        return;
    }
    std::string query(in("ODB_QUERY"));

    /*if(static_cast<const char*>(in("ODB_OUTPUT")) == 0)
    {
        messageToLog("No output format is specified!",LOG_EROR,useLogWindow);
        setError(13);
        return;
    }
    std::string outputFormat(in("ODB_OUTPUT"));*/
    std::string outputFormat("ODB");


    if (static_cast<const char*>(in("ODB_NB_ROWS")) == nullptr) {
        messageToLog("No number of rows is specified!", LOG_EROR, useLogWindow, out);
        setError(13);
        return;
    }
    std::string nb_rows(in("ODB_NB_ROWS"));

    // Find out and check odb format
    std::string odbType = MvOdbType(dataPath, true);
    if (odbType != "ODB_NEW" && odbType != "ODB_OLD") {
        std::stringstream err_s;
        err_s << "Wrong data type: " << odbType << "! Only ODB_DB is accepted!";
        messageToLog(err_s.str(), LOG_EROR, useLogWindow, out);
        setError(13);
        return;
    }
    else if (odbType == "ODB_NEW") {
#ifndef METVIEW_ODB_NEW
        messageToLog("Cannot handle ODB-2 data! ODB-2 support is disabled!", LOG_EROR, useLogWindow, out);
        setError(13);
        return;
#endif
    }
    else if (odbType == "ODB_OLD") {
#ifndef METVIEW_ODB_OLD
        messageToLog("Cannot handle ODB-1 data! ODB-1 support is disabled!", LOG_EROR, useLogWindow, out);
        out.print();
        setError(13);
        return;
#endif
    }

    // Check if the old odb api is able to produce a new odb ouput
    if (odbType == "ODB_OLD" && outputFormat == "ODB") {
#ifndef METVIEW_ODB_OLD
        messageToLog("Cannot handle ODB-1 data! ODB-1 support is disabled!", LOG_EROR, useLogWindow, out);
        setError(13);
        return;
#endif
    }

    bool failOnEmpty = true;
    if (const char* c = in("FAIL_ON_EMPTY_OUTPUT")) {
        if (strcmp(c, "NO") == 0)
            failOnEmpty = false;
    }
    else {
        messageToLog("Parameter FAIL_ON_EMPTY_OUTPUT is not defined",
                     LOG_EROR, useLogWindow, out);
        setError(13);
        return;
    }

    // Outfile name
    std::string outFile = marstmp();

    // Odbsql script
    std::string odbsqlScript;
    if (odbType == "ODB_OLD") {
        char* mvbin = getenv("METVIEW_BIN");
        if (mvbin == nullptr) {
            messageToLog("No METVIEW_BIN env variable is defined. Cannot locate mv_odbsql script to run query for ODB-1 databases!",
                         LOG_EROR, useLogWindow, out);
            setError(13);
            return;
        }
        else {
            odbsqlScript = std::string(mvbin) + "/mv_odbsql";
        }
    }

    //-------------------------
    // Performs filter
    //-------------------------

    // std::string cmd="odbsql -q '" <<  query << "' -o " << outFile << " ";

    if (outputFormat == "ODB") {
        if (odbType == "ODB_NEW") {
            bool notEmpty = true;
            // MvOdb odb(dataPath);
            try {
                notEmpty = MvOdb::retrieveToFile(query, dataPath, outFile);
            }
            catch (eckit::Exception e)

            {
                std::stringstream err_s;
                err_s << "Failed to perform retrieval: " << e.what();
                messageToLog(err_s.str(), LOG_EROR, useLogWindow, out);
                setError(13);
                return;
            }

            if (!notEmpty && failOnEmpty) {
                messageToLog("Query result is empty!", LOG_EROR, useLogWindow, out);
                setError(13);
                return;
            }
        }
        else {
#ifdef METVIEW_ODB_OLD
            std::string queryForShell, dataPathForShell;
            getShellArgument(query, queryForShell);
            getShellArgument(dataPath, dataPathForShell);

            std::string cmd = odbsqlScript + " -q \"" + queryForShell + "\" -o " + outFile +
                              " -i " + "\"" + dataPathForShell + "\"" + " -f newodb";

            if (nb_rows != "-1") {
                cmd += " -m " + nb_rows;
            }

            // marslog(LOG_INFO,"Execute command: %s",cmd.c_str());

            int ret = system(cmd.c_str());

            if (ret == -1 || WEXITSTATUS(ret) != 0) {
                std::stringstream err_s;
                err_s << "Failed to perform retrieval: odbsql exited with value of " << WEXITSTATUS(ret);
                messageToLog(err_s.str(), LOG_EROR, useLogWindow, out);
                setError(13);
                return;
            }

            outFile += ".odb";

            struct stat st;
            if (stat(outFile.c_str(), &st) != 0 || st.st_size == 0) {
                return;
            }
#endif
        }

        MvRequest rdata("ODB_DB");
        rdata("PATH") = outFile.c_str();
        rdata("TEMPORARY") = 1;
        rdata.print();
        out = rdata;
    }

    else if (outputFormat == "GEO_STANDARD" ||
             outputFormat == "GEO_XYV" ||
             outputFormat == "GEO_XY_VECTOR" ||
             outputFormat == "GEO_POLAR_VECTOR") {
        if (odbType == "ODB_NEW") {
            std::string tmpFile = marstmp();

            try {
                MvOdb::retrieveToFile(query, dataPath, tmpFile);
                MvOdb odb(tmpFile, dataPath, query);
                if (odb.toGeopoints(outFile, outputFormat) == false) {
                    messageToLog("Failed to convert retrieval result into geopoints",
                                 LOG_EROR, useLogWindow, out);
                    setError(13);
                    return;
                }
            }
            catch (eckit::Exception e) {
                std::stringstream err_s;
                err_s << "Failed to perform retrieval: " << e.what();
                messageToLog(err_s.str(), LOG_EROR, useLogWindow, out);
                setError(13);
                return;
            }
        }
        else {
#ifdef METVIEW_ODB_OLD
            std::string queryForShell;
            getShellArgument(query, queryForShell);

            std::string tmpFile = marstmp();
            std::string cmd = odbsqlScript + " -q \"" + queryForShell + "\" -o " + tmpFile +
                              " -i " + dataPath + " -f newodb ";

            if (nb_rows != "-1") {
                cmd += " -m " + nb_rows;
            }

            int ret = system(cmd.c_str());

            if (ret == -1 || WEXITSTATUS(ret) != 0) {
                std::stringstream err_s;
                err_s << "Failed to perform retrieval: odbsql exited with value of " << WEXITSTATUS(ret);
                messageToLog(err_s.str(), LOG_EROR, useLogWindow, out);
                setError(13);
                return;
            }

            tmpFile += ".odb";

            try {
                MvOdb odb(tmpFile, dataPath, query);
                if (odb.toGeopoints(outFile, outputFormat) == false) {
                    messageToLog("Failed to convert query result into geopoints", LOG_EROR, useLogWindow, out);
                    setError(13);
                    return;
                }
            }
            catch (eckit::Exception e) {
                std::stringstream err_s;
                err_s << "Failed to convert retrieval result into geopoints: " << e.what();
                messageToLog(err_s.str(), LOG_EROR, useLogWindow, out);
                setError(13);
                return;
            }
#endif
        }

        MvRequest rdata("GEOPOINTS");
        rdata("PATH") = outFile.c_str();
        rdata("TEMPORARY") = 1;
        rdata.print();
        out = rdata;
    }

    std::cout << "---OdbFilter::serve() OUT---" << std::endl;
    out.print();
}

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);

    OdbFilter odb;

    theApp.run();
}
