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

#include "MvOdb.h"

#include <iostream>
#include <stdexcept>

#include <fcntl.h>

#include "MvScanFileType.h"

#include "MvOdb.h"

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

class OdbVisualiser : public Base
{
public:
    OdbVisualiser(const char* a) :
        Base(a),
        failOnEmpty_(true) {}
    void serve(MvRequest&, MvRequest&);

protected:
    bool getParamValue(std::vector<std::string>&, MvRequest&, std::string);
    bool isTextContained(const char* txt);
    bool columnsHaveExpression(MvAbstractOdb*, const std::vector<const char*>&);
    void buildColumnList(const std::vector<const char*>&, const std::string&, std::vector<std::string>&);
    std::string buildQuery(const std::vector<std::string>&);
    std::string buildQuery(const std::vector<std::string>& columns,
                           const char* vars, const char* uniqueby,
                           const char* from, const char* where, const char* orderby, bool);
    bool callFilter(std::string path, std::string query, std::string& resPath);
    bool updateRequest(MvRequest&, const char*, MvAbstractOdb*, int&);

    bool failOnEmpty_;
};


bool OdbVisualiser::getParamValue(std::vector<std::string>& value, MvRequest& in, std::string parName)
{
    value.clear();

    int cnt = in.countValues(parName.c_str());

    if (cnt == 1) {
        const char* cval = in(parName.c_str());
        if (cval) {
            value.push_back(std::string(cval));
        }
    }
    else {
        std::string val;
        for (int i = 0; i < cnt; i++) {
            const char* cval = in(parName.c_str(), i);
            if (cval) {
                value.push_back(std::string(cval));
            }
        }
    }

    return true;
}

bool OdbVisualiser::isTextContained(const char* txt)
{
    if (!txt)
        return false;
    std::string str(txt);

    return (str.find_last_not_of(" \n\t") != std::string::npos);
}

bool OdbVisualiser::columnsHaveExpression(MvAbstractOdb* db, const std::vector<const char*>& cols)
{
    if (!db)
        return true;

    for (auto col : cols) {
        if (col) {
            std::string s(col);
            if (!db->hasColumn(s))
                return true;
        }
    }

    return false;
}


void OdbVisualiser::buildColumnList(const std::vector<const char*>& cols, const std::string& metadata, std::vector<std::string>& lst)
{
    for (auto col : cols) {
        if (col) {
            std::string s(col);
            lst.push_back(s);
        }
    }

    if (isTextContained(metadata.c_str()))
        lst.push_back(metadata);

    /*Tokenizer parse(",");
    std::vector<std::string> vs;

    parse(metadata,vs);

    for(unsigned int i=0; i < vs.size() ; i++)
    {
        if(isTextContained(vs.at(i).c_str()))
            lst.push_back(vs.at(i));
    }*/
}

std::string OdbVisualiser::buildQuery(const std::vector<std::string>& columns)
{
    std::string query("SELECT");

    bool empty = true;
    for (const auto& column : columns) {
        if (!column.empty()) {
            if (!empty)
                query.append(",\n");
            else
                query.append("\n");

            query.append("\t" + column);

            if (empty)
                empty = false;
        }
    }

    query.append("\n");

    return query;
}


std::string OdbVisualiser::buildQuery(const std::vector<std::string>& columns,
                                      const char* vars, const char* uniqueby, const char* from, const char* where, const char* orderby,
                                      bool probablyNeedAQuery)
{
    std::string query;

    // If the columns do not contain any expressions, we check if there is
    // any need for having a query
    if (!probablyNeedAQuery) {
        if (!from && !isTextContained(from) &&
            !uniqueby && !isTextContained(uniqueby) &&
            !where && !isTextContained(where) &&
            !orderby && !isTextContained(orderby)) {
            return query;
        }
    }

    if (vars)
        query.append(vars);

    query.append("SELECT");

    bool empty = true;

    for (const auto& column : columns) {
        if (!column.empty()) {
            if (!empty)
                query.append(",\n");
            else
                query.append("\n");

            query.append("\t" + column);

            if (empty)
                empty = false;
        }
    }

    query.append("\n");

    if (from && isTextContained(from))
        query.append("FROM \n" + std::string(from) + "\n");

    if (uniqueby && isTextContained(uniqueby))
        query.append("UNIQUEBY \n" + std::string(uniqueby) + "\n");

    if (where && isTextContained(where))
        query.append("WHERE \n" + std::string(where) + "\n");

    if (orderby && isTextContained(orderby))
        query.append("ORDER BY \n" + std::string(orderby) + "\n");


    return query;
}

bool OdbVisualiser::callFilter(std::string path, std::string query, std::string& resPath)
{
    MvRequest r("ODB_FILTER");

    r("ODB_FILENAME") = path.c_str();
    r("ODB_QUERY") = query.c_str();
    // r("ODB_OUTPUT")   = "ODB";
    r("ODB_NB_ROWS") = "-1";
    r("FAIL_ON_EMPTY_OUTPUT") = "on";
    r("_CLASS") = "ODB_FILTER";
    r("_ACTION") = "execute";
    r("_SERVICE") = "OdbFilter";
    r("_USELOGWINDOW") = "1";

    int error;
    MvRequest result = MvApplication::waitService("OdbFilter", r, error);

    const char* rPath = result("PATH");
    if (error != 0 || !rPath || std::string(rPath).empty()) {
        if (failOnEmpty_) {
            std::string err = "OdbVisualiser-> Calling ODB Filter failed. ";
            const char* errmsg = result("_FILTER_MESSAGE");
            if (errmsg)
                err += errmsg;

            setError(1, err.c_str());
        }
        return false;
    }

    resPath = std::string(rPath);

    struct stat st;
    if (stat(resPath.c_str(), &st) != 0 || st.st_size == 0) {
        setError(0, "OdbVisualiser-> No data retrieved for the specified ODB/SQL query!");
    }


    return true;
}

bool OdbVisualiser::updateRequest(MvRequest& req, const char* par, MvAbstractOdb* db, int& cnt)
{
    const char* c = req(par);
    if (c) {
        const MvOdbColumn* col = db->column(cnt);
        if (col) {
            req(par) = col->name().c_str();
            cnt++;
            return true;
        }
    }
    return false;
}

void OdbVisualiser::serve(MvRequest& in, MvRequest& out)
{
    std::cout << "OdbVisualiser::serve in" << std::endl;
    in.print();

#ifndef METVIEW_ODB_PLOT
    setError(1, "OdbVisualiser-> ODB plotting is DISABLED! Magics was built without ODB support!");
    return;
#endif

    const char* type = in("ODB_PLOT_TYPE");

    if (!type) {
        setError(1, "OdbVisualiser-> ODB_PLOT_TYPE is not specified!");
        return;
    }

    std::vector<const char*> cols;
    std::string outReqName;

    if (strcmp(type, "GEO_POINTS") == 0) {
        outReqName = "ODB_GEO_POINTS";

        const char* lat = in("ODB_LATITUDE_VARIABLE");
        cols.push_back(lat);

        const char* lon = in("ODB_LONGITUDE_VARIABLE");
        cols.push_back(lon);

        const char* value = in("ODB_VALUE_VARIABLE");
        cols.push_back(value);
    }

    else if (strcmp(type, "GEO_VECTORS") == 0) {
        outReqName = "ODB_GEO_VECTORS";

        const char* lat = in("ODB_LATITUDE_VARIABLE");
        cols.push_back(lat);

        const char* lon = in("ODB_LONGITUDE_VARIABLE");
        cols.push_back(lon);

        const char* x = in("ODB_X_COMPONENT_VARIABLE");
        cols.push_back(x);

        const char* y = in("ODB_Y_COMPONENT_VARIABLE");
        cols.push_back(y);

        const char* value = in("ODB_VALUE_VARIABLE");
        cols.push_back(value);
    }

    else if (strcmp(type, "XY_POINTS") == 0) {
        outReqName = "ODB_XY_POINTS";

        const char* x = in("ODB_X_VARIABLE");
        cols.push_back(x);

        const char* y = in("ODB_Y_VARIABLE");
        cols.push_back(y);

        const char* v = in("ODB_VALUE_VARIABLE");
        cols.push_back(v);
    }

    else if (strcmp(type, "XY_VECTORS") == 0) {
        outReqName = "ODB_XY_VECTORS";

        const char* x = in("ODB_X_VARIABLE");
        cols.push_back(x);

        const char* y = in("ODB_Y_VARIABLE");
        cols.push_back(y);

        const char* xc = in("ODB_X_COMPONENT_VARIABLE");
        cols.push_back(xc);

        const char* yc = in("ODB_Y_COMPONENT_VARIABLE");
        cols.push_back(yc);

        const char* v = in("ODB_VALUE_VARIABLE");
        cols.push_back(v);
    }

    else if (strcmp(type, "XY_BINNING") == 0) {
        outReqName = "ODB_XY_BINNING";

        const char* x = in("ODB_X_VARIABLE");
        cols.push_back(x);

        const char* y = in("ODB_Y_VARIABLE");
        cols.push_back(y);

        const char* v = in("ODB_VALUE_VARIABLE");
        cols.push_back(v);
    }
    else {
        return;
    }

    const char* vars = in("ODB_PARAMETERS");
    const char* uniqueby = in("ODB_UNIQUEBY");
    const char* from = in("ODB_FROM");
    const char* where = in("ODB_WHERE");
    const char* orderby = in("ODB_ORDERBY");

    // Get metadata columns
    std::string metadata;
    const char* metadataC = in("ODB_METADATA_VARIABLES");
    if (metadataC)
        metadata = std::string(metadataC);

    // Build the final column list
    std::vector<std::string> columns;
    buildColumnList(cols, metadata, columns);

    //-------------------------
    // Check input ODB format
    //------------------------

    std::string odbPath;
    std::string odbType;

    MvRequest dataR = in("ODB_DATA");
    const char* iconType = dataR.getVerb();

    // If no icon is specified
    if (!iconType) {
        // If no path is there either
        const char* path = in("ODB_FILENAME");
        if (!path || strcmp(path, "OFF") == 0) {
            setError(1, "OdbVisualiser-> No data icon or path is specified!");
            return;
        }
        // If the path is defined it checks the filetype. It has to be "ODB_NEW"
        else {
            odbType = MvOdbType(path, true);
            if (odbType != "ODB_OLD" && odbType != "ODB_NEW") {
                setError(1, "OdbVisualiser-> Wrong data type: %s! Only ODB_DB is accepted!", odbType.c_str());
                return;
            }
        }

        odbPath = std::string(path);
    }
    // The icon type can only be "ODB_DB"
    else if (strcmp(iconType, "ODB_DB") != 0) {
        setError(1, "OdbVisualiser-> Wrong data type: %s! Only ODB_DB is accepted", iconType);
        return;
    }
    // The "ODB_DB" icon can only be a new ODB!!
    else {
        const char* path = dataR("PATH");
        if (!path) {
            setError(1, "OdbVisualiser-> No path is specified for ODB_DB icon!");
            return;
        }
        odbType = MvOdbType(path, false);
        odbPath = std::string(path);
    }

    // check the FAIL ON EMPTY parameter set by the user
    if (const char* c = in("FAIL_ON_EMPTY_OUTPUT")) {
        if (strcmp(c, "NO") == 0)
            failOnEmpty_ = false;
    }


    //----------------------------------------
    // Call ODBFilter service if needed and
    // modify the request!
    //----------------------------------------

    MvRequest tmpR = in;
    tmpR.setVerb(outReqName.c_str());
    tmpR("_VERB") = outReqName.c_str();
    tmpR("_CLASS") = outReqName.c_str();

    bool queryWasRun = false;

    // For ODB-1 we always need the filter!!
    std::string query;
    if (odbType == "ODB_OLD") {
        // Build the query
        query = buildQuery(columns, vars, uniqueby, from, where, orderby, true);

        MvRequest r;
        tmpR("ODB_DATA") = r;

        std::string res;
        if (!callFilter(odbPath, query, res)) {
            out = r;
            return;
        }
        tmpR("ODB_FILENAME") = res.c_str();

        queryWasRun = true;
    }

    // For ODB-2
    if (odbType == "ODB_NEW") {
        // If we have metadata columns we surely need to run a query
        bool needAQuery = isTextContained(metadata.c_str());

        // Get odb object to access metadata
        MvAbstractOdb* db = MvOdbFactory::make(odbPath);

        // Check if any of the specified columns (in the request) contains an expression. If does we
        // we need to run a query.
        if (!needAQuery && columnsHaveExpression(db, cols)) {
            needAQuery = true;
        }

        if (db)
            delete db;

        // Get the query
        query = buildQuery(columns, vars, uniqueby, nullptr, where, orderby, needAQuery);

        // The query is empty if the columns do not contain any expressions
        // and there are no SET, WHERE, ORDERBY, UNIQUEBY statments specified
        if (!query.empty()) {
            MvRequest r;
            tmpR("ODB_DATA") = r;

            std::string res;
            if (!callFilter(odbPath, query, res)) {
                out = r;
                return;
            }
            tmpR("ODB_FILENAME") = res.c_str();
            queryWasRun = true;
        }
        else {
            tmpR("ODB_FILENAME") = odbPath.c_str();
        }
    }


    // Column names can change during the query. E.g. ODB_VALUE_VARIABLE=an_depar-fg_depar
    // changes to -(an_depar,fg_depar) in the resulting ODB file. This causes magics++ crash
    // because it uses the column name  specified in ODB_VALUE_VARIABLE etc to extract the columns
    // data from the ODB.
    // As a workaround wee need to replace all the request column names to the actual ones in
    // the resulting ODB! We suppose that the column order in the resulting ODB is exactly
    // the same as in the qurey!!

    if (queryWasRun == true) {
        const char* pR = tmpR("ODB_FILENAME");
        std::string outPath;
        if (pR)
            outPath = std::string(pR);

        // Get odb object to access metadata
        MvAbstractOdb* db = MvOdbFactory::make(outPath);
        if (!db) {
            setError(1, "OdbVisualiser-> ODB object not found");
            return;
        }

        if (outReqName == "ODB_GEO_POINTS") {
            int cnt = 0;
            updateRequest(tmpR, "ODB_LATITUDE_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_LONGITUDE_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_VALUE_VARIABLE", db, cnt);
        }
        else if (outReqName == "ODB_GEO_VECTORS") {
            int cnt = 0;
            updateRequest(tmpR, "ODB_LATITUDE_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_LONGITUDE_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_X_COMPONENT_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_Y_COMPONENT_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_VALUE_VARIABLE", db, cnt);
        }
        else if (outReqName == "ODB_XY_POINTS") {
            int cnt = 0;
            updateRequest(tmpR, "ODB_X_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_Y_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_VALUE_VARIABLE", db, cnt);
        }
        else if (outReqName == "ODB_XY_VECTORS") {
            int cnt = 0;
            updateRequest(tmpR, "ODB_X_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_Y_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_X_COMPONENT_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_Y_COMPONENT_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_VALUE_VARIABLE", db, cnt);
        }
        else if (outReqName == "ODB_XY_BINNING") {
            int cnt = 0;
            updateRequest(tmpR, "ODB_X_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_Y_VARIABLE", db, cnt);
            updateRequest(tmpR, "ODB_VALUE_VARIABLE", db, cnt);
        }

        if (db)
            delete db;
    }

    out = tmpR;

    std::cout << "OdbVisualiser::serve out" << std::endl;
    out.print();

    return;
}

//--------------------------------------------------------

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    OdbVisualiser vis("ODB_VISUALISER");

    theApp.run();
}
