/***************************** 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 <stdlib.h>
#include <time.h>
#include <MvRequest.h>

#include "macro.h"
#include "script.h"

#include "MvFlextra.h"
#include "MvMiscellaneous.h"

//===========================================================================

class FlextraCountFunction : public Function
{
public:
    FlextraCountFunction(const char* n) :
        Function(n, 1, tnumber)
    {
        info = "Causes Macro to always wait(1)/not wait(0) for functions to complete before continuing.";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int, Value*);
};

int FlextraCountFunction::ValidArguments(int arity, Value* arg)
{
    // arguments can be: (FLEXTRA_FILE definition)

    if (arity != 1)
        return false;

    if (arg[0].GetType() != trequest)
        return false;

    request* r;
    arg[0].GetValue(r);

    if (r && strcmp(r->name, "FLEXTRA_FILE") == 0)
        return true;

    return false;
}

Value FlextraCountFunction::Execute(int /*arity*/, Value* arg)
{
    request* r;
    arg[0].GetValue(r);
    const char* path = get_value(r, "PATH", 0);

    if (path) {
        std::string s(path);
        MvFlextra flx(s);
        return Value(flx.blockNum());
    }

    int zero = 0;
    return Value(zero);
}

//===========================================================================

class FlextraGroupGetFunction : public Function
{
public:
    FlextraGroupGetFunction(const char* n) :
        Function(n, 3, tnumber)
    {
        info = "Returns a list of available meta data keys for the given table";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int arity, Value* arg);
};

int FlextraGroupGetFunction::ValidArguments(int arity, Value* arg)
{
    // arguments can be: (FLEXTRA_FILE definition, number)const map

    if (arity != 2)
        return false;

    if (arg[0].GetType() != trequest)  // first must be a request
        return false;

    if (arg[1].GetType() != tstring && arg[1].GetType() != tlist) {
        return false;
    }

    request* r;
    arg[0].GetValue(r);

    if (r && strcmp(r->name, "FLEXTRA_FILE") == 0)
        return true;

    return false;
}

Value FlextraGroupGetFunction::Execute(int /*arity*/, Value* arg)
{
    request* r;
    arg[0].GetValue(r);
    const char* path = get_value(r, "PATH", 0);

    if (!path)
        return Value();

    std::string s(path);
    MvFlextra flx(s);

    if (flx.blockNum() == 0)
        return Value();

    int id = 0;
    if (id < 0 || id >= flx.blockNum())
        return Value();

    const std::map<std::string, std::string>& metaData = flx.blocks().at(id)->metaData();

    std::map<std::string, std::string>::const_iterator it;

    if (arg[1].GetType() == tstring) {
        const char* key;
        arg[1].GetValue(key);
        if ((it = metaData.find(key)) != metaData.end())
            return Value((*it).second.c_str());
    }
    else if (arg[1].GetType() == tlist) {
        CList* keys;
        arg[1].GetValue(keys);
        auto* values = new CList(keys->Count());

        for (int i = 0; i < keys->Count(); i++) {
            const char* key;
            (*keys)[i].GetValue(key);
            if ((it = metaData.find(key)) != metaData.end())
                (*values)[i] = Value((*it).second.c_str());
        }

        return values;
    }

    return Value();
}

//===========================================================================

class FlextraTrGetFunction : public Function
{
public:
    FlextraTrGetFunction(const char* n) :
        Function(n, 3, tnumber)
    {
        info = "Returns a list of available meta data keys for the given table";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int arity, Value* arg);
};

int FlextraTrGetFunction::ValidArguments(int arity, Value* arg)
{
    // arguments can be: (FLEXTRA_FILE definition, number)

    if (arity != 3)
        return false;

    if (arg[0].GetType() != trequest)  // first must be a request
        return false;

    if (arg[1].GetType() != tnumber)  // second must be a number
        return false;

    if (arg[2].GetType() != tstring && arg[2].GetType() != tlist) {
        return false;
    }

    request* r;
    arg[0].GetValue(r);

    if (r && strcmp(r->name, "FLEXTRA_FILE") == 0)
        return true;

    return false;
}

Value FlextraTrGetFunction::Execute(int /*arity*/, Value* arg)
{
    request* r;
    arg[0].GetValue(r);
    const char* path = get_value(r, "PATH", 0);

    if (!path)
        return Value();

    std::string s(path);
    MvFlextra flx(s);

    if (flx.blockNum() == 0)
        return Value();

    int trId;
    arg[1].GetValue(trId);
    trId -= Context::BaseIndex();

    if (trId < 0 || trId >= flx.blocks().at(0)->itemNum())
        return Value();

    MvFlextraItem* item = flx.blocks().at(0)->items().at(trId);

    const std::map<std::string, std::string>& metaData = item->metaData();

    std::map<std::string, std::string>::const_iterator it;

    // A single key is specified
    if (arg[2].GetType() == tstring) {
        const char* key;
        arg[2].GetValue(key);
        if ((it = metaData.find(key)) != metaData.end())
            return Value((*it).second.c_str());
        else {
            std::vector<std::string> vec;
            MvFlextraItem::DataType type;
            item->pointData(key, vec, type);
            if (vec.size() > 0) {
                if (type == MvFlextraItem::IntType ||
                    type == MvFlextraItem::FloatType) {
                    auto* values = new CVector(vec.size());
                    for (unsigned int i = 0; i < vec.size(); i++) {
                        std::istringstream iss(vec.at(i));
                        double d;
                        iss >> d;
                        values->setIndexedValue(i, d);
                    }

                    return values;
                }
                else if (type == MvFlextraItem::DateType) {
                    auto* values = new CList(vec.size());
                    for (unsigned int i = 0; i < vec.size(); i++) {
                        Date d(vec.at(i).c_str());
                        (*values)[i] = Value(d);
                    }
                    return values;
                }
            }
        }
    }

    else if (arg[2].GetType() == tlist) {
        CList* keys;
        arg[2].GetValue(keys);
        auto* values = new CList(keys->Count());

        for (int i = 0; i < keys->Count(); i++) {
            const char* key;
            (*keys)[i].GetValue(key);
            if ((it = metaData.find(key)) != metaData.end())
                (*values)[i] = Value((*it).second.c_str());

            else {
                std::vector<std::string> vec;
                MvFlextraItem::DataType type;
                item->pointData(key, vec, type);

                if (type == MvFlextraItem::IntType ||
                    type == MvFlextraItem::FloatType) {
                    auto* data = new CVector(vec.size());
                    for (unsigned int j = 0; j < vec.size(); j++) {
                        std::istringstream iss(vec.at(j));
                        double d;
                        iss >> d;
                        data->setIndexedValue(j, d);
                    }

                    (*values)[i] = data;
                }
                else if (type == MvFlextraItem::DateType) {
                    auto* data = new CList(vec.size());
                    for (unsigned int j = 0; j < vec.size(); j++) {
                        Date d(vec.at(j).c_str());
                        (*data)[j] = Value(d);
                    }
                    (*values)[i] = data;
                }
            }
        }

        return values;
    }

    return Value();
}

//=============================================================================

class FlextraElemFunction : public Function
{
public:
    FlextraElemFunction(const char* n) :
        Function(n, 2, tlist, tnumber) {}
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int FlextraElemFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;

    if (arg[0].GetType() != trequest)
        return false;

    if (arg[1].GetType() != tnumber)
        return false;

    request* r;
    arg[0].GetValue(r);

    if (r && strcmp(r->name, "FLEXTRA_FILE") == 0)
        return true;

    return false;
}

Value FlextraElemFunction::Execute(int arity, Value* arg)
{
    request* r;
    arg[0].GetValue(r);
    const char* path = get_value(r, "PATH", 0);

    if (!path)
        return Value();

    std::string s(path);
    MvFlextra flx(s);

    if (flx.blockNum() == 0)
        return Value();

    int id = 0;
    if (arity == 1)
        id = 0;
    else if (arity == 2) {
        arg[1].GetValue(id);
        id -= Context::BaseIndex();
    }
    else {
        return Value();
    }

    if (id < 0 || id >= flx.blockNum())
        return Value();

    if (id == 0 && flx.blockNum() == 1) {
        return Value(r);
    }
    else {
        std::string outfile(marstmp());
        flx.write(outfile, id);

        request* outr = empty_request("FLEXTRA_FILE");
        set_value(outr, "PATH", outfile.c_str());

        return Value(outr);
    }

    return Value();
}


//=============================================================================

class FlexpartTrConvertFunction : public Function
{
public:
    FlexpartTrConvertFunction(const char* n) :
        Function(n, 2, tstring, tstring)
    {
        info = "Converts raw FLEXPART trajectory output to CSV";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int FlexpartTrConvertFunction::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;

    if (arg[0].GetType() != tstring)
        return false;

    if (arg[1].GetType() != tstring)
        return false;

    return true;
}

Value FlexpartTrConvertFunction::Execute(int /*arity*/, Value* arg)
{
    const char* tmpChIn = nullptr;
    arg[0].GetValue(tmpChIn);
    if (!tmpChIn || strlen(tmpChIn) == 0) {
        return Error("%s: invalid first argument", Name());
    }
    const char* tmpChOut = nullptr;
    arg[1].GetValue(tmpChOut);
    if (!tmpChOut || strlen(tmpChOut) == 0) {
        return Error("%s: invalid second argument", Name());
    }

    std::string inPath(tmpChIn);
    std::string outPath(tmpChOut);
    std::string trExe = "flexpartTrajectory";

    char* mvbin = getenv("METVIEW_BIN");
    if (!mvbin) {
        return Error("%s: no METVIEW_BIN env variable is defined. Cannot locate executable %s",
                     Name(), trExe.c_str());
    }

    trExe = std::string(mvbin) + "/" + trExe;

    // Run the conversion
    std::string cmd = trExe + " \"" + inPath + "\" \"" + outPath + "\"";
    // std::cout << cmd << std::endl;

    std::stringstream out;
    std::stringstream err;
    metview::shellCommand(cmd, out, err);

    if (!err.str().empty()) {
        return Error("%s: failed. Error: %s %s", Name(), out.str().c_str(), err.str().c_str());
    }

    return Value();
}


//=============================================================================

static void install(Context* c)
{
    c->AddFunction(new FlextraCountFunction("count"));
    c->AddFunction(new FlextraGroupGetFunction("flextra_group_get"));
    c->AddFunction(new FlextraTrGetFunction("flextra_tr_get"));
    c->AddFunction(new FlextraElemFunction("[]"));
    c->AddFunction(new FlexpartTrConvertFunction("flexpart_convert_trajectory"));
}

static Linkage Link(install);
