/***************************** 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 <map>
#include <string>

#include <math.h>
#include <ctype.h>
#include <float.h>
#include <string.h>
#include "macro.h"
#include "arith.h"
#include "MvPath.hpp"
#include "ctable.h"
//#include "MvOdb.h"


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

CTable::CTable(request* s) :
    InPool(ttable, s),
    loaded_(false),
    ok_(true)
{
    // odb_=0;
    r = clone_all_requests(s);

    if (r) {
        printf("CTable::CTable(request *s)\n");

        ok_ = table_.setReaderParameters(r);
        print_all_requests(r);
        /*
        const char* path = get_value(r,"PATH",0);
        std::string s(path);
        table_.setPath(s);        */
    }
}


CTable::CTable(const char* p, int temp) :
    InPool(ttable),
    loaded_(false),
    ok_(true)
{
    r = empty_request("TABLE");
    set_value(r, "PATH", "%s", FullPathName(p).c_str());
    set_value(r, "TEMPORARY", "%d", temp);

    if (r) {
        const char* path = get_value(r, "PATH", 0);
        std::string s(path);
        table_.setPath(s);
        /*
        const char *delimiter = get_value(r,"DELIMITER",0);
        if (delimiter)
            table_setDelimiter(delimiter);*/
    }
}


void CTable::load()
{
    if (!loaded_) {
        table_.setDoubleMissingValue(VECTOR_MISSING_VALUE);
        table_.setStringMissingValue("");
        if (!table_.read()) {
            Error("CTable::load() - could not read table file.");
        }
        loaded_ = true;
    }
}


/*
void CTable::unload(void)
{
    if(loaded_)
    {
        delete odb_;
        odb_=0;
    }
}
*/

/*CTable::CTable(const char  *p,int temp) : InPool(tgeopts)
{
    odb_=0;
    r = empty_request("ODB_DB");
    //set_value(r,"PATH","%s",p);
    set_value(r,"PATH","%s",FullPathName(p).c_str());
    set_value(r,"TEMPORARY","%d",temp);
}*/

/*
long CTable::Count()
{
    if(odb_)
    {
        return odb_->rowNum();
    }
    else
    {
        return -1;
    }
}
*/

/*
void CTable::load(void)
{
    if(odb_)
        return;

    if(r)
    {
        const char* path = get_value(r,"PATH",0);
            odb_= MvOdbFactory::make(path);
    }
    //else
    //  is probably a geopoints structure in memory, or with zero count!
}
*/


int CTable::Write(FILE* /*f*/)
{
    // unload();
    // return CopyFile(get_value(r,"PATH",0),f);
    printf("CTable::Write\n");
    return 1;
}


/*CGeopts::CGeopts(long count) : InPool(tgeopts)
{
    r = nullptr;
    gpts.newReservedSize (count);
}*/


/*CGeopts::CGeopts(CGeopts  *p) : InPool(tgeopts)
{
    r = nullptr;
    p->load();
    gpts = p->gpts;
}*/


CTable::~CTable()
{
    /*
    if(r)
    {
        const char *t = get_value(r,"TEMPORARY",0);
        const char *p = get_value(r,"PATH",0);
        if(t && p)
        {
            if(atoi(t)) {
                unlink(p);
            }
        }
    }
    //if(count) delete[] pts;
    free_all_requests(r);

    if(odb_)
        delete odb_; */
}

void CTable::ToRequest(request*& s)
{
    Attach();  // Temp fix: if someone want the request
    // unload();
    s = r;
}

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


bool same_string(const char* p, const char* q)
{
    while (*p) {
        int x = islower(*p) ? toupper(*p) : *p;
        int y = islower(*q) ? toupper(*q) : *q;

        //-- '/' chars have been converted: ignore them!
        if (x != y)
            return 0;

        p++;
        q++;
    }

    return true;
}


const char* upper_string(const char* s)
{
    static char upper[2048];
    char* u = upper;  // pointer to first element

    if (s) {
        while (*s) {
            *u = islower(*s) ? toupper(*s) : *s;
            s++;
            u++;
        }
    }
    *u = '\0';  // terminate the string

    return upper;
}


class TableReadWithParamsFunction : public Function
{
public:
    TableReadWithParamsFunction(const char* n) :
        Function(n)
    {
        info = "Reads a table file with parameters for parsing it.";
    }
    virtual Value Execute(int arity, Value* arg);
    int ValidArguments(int arity, Value* arg);
};

int TableReadWithParamsFunction::ValidArguments(int /*arity*/, Value* /*arg*/)
{
    return true;
}


static void SimpleListToRequestParameter(request* r, const char* param, CList* list)
{
    if (!r || !list || !param)
        return;


    for (int i = 0; i < list->Count(); i++) {
        const char* val;
        (*list)[i].GetValue(val);
        add_value(r, param, val);
    }
}


Value TableReadWithParamsFunction::Execute(int arity, Value* arg)
{
    // the user supplies a request such as
    //
    // my_table = read_table
    // (
    //     filename : '/home/graphics/dummy-user/metview//Tests/Tables/sample3.csv',
    //     delimiter : 'q',
    //     columns   : 6,
    //     combine_delimiters : 'on'
    // )
    //
    // we get this as a set of arguments, in this case 8.


    // first sanity check: ensure that there are an even number of arguments
    // - this is probably impossible, but just in case...

    if (arity % 2 != 0) {
        return Error("read_table: incorrect input parameters");
    }


    // create an empty request - we will fill it with the user's parameters
    // and then pass it to the CTable constructor

    request* r = empty_request("TABLE");


    // loop through the supplied parameters, 2 at a time

    int i;
    const char* param;

    for (i = 0; i < arity; i += 2) {
        const char* value = nullptr;
        CList* list;

        // get the parameter name

        if (arg[0].GetType() != tstring)
            return Error("read_table: incorrect input parameters");

        arg[i].GetValue(param);


        // get the value - we know it will be there because we already checked
        // that we have an even number of parameters

        vtype type = arg[i + 1].GetType();

        if (type == tstring || type == tnumber)
            arg[i + 1].GetValue(value);
        else if (type == tlist)
            arg[i + 1].GetValue(list);


        // which parameter is it?
        const char* upper_param = upper_string(param);

        if (!strcmp(upper_param, "TABLE_FILENAME"))
            set_value(r, "PATH", value);

        else if (!strcmp(upper_param, "TABLE_DELIMITER"))
            set_value(r, upper_string(param), value);

        else if (!strcmp(upper_param, "TABLE_COMBINE_DELIMITERS"))
            set_value(r, upper_param, value);

        else if (!strcmp(upper_param, "TABLE_HEADER_ROW"))
            set_value(r, upper_param, value);

        else if (!strcmp(upper_param, "TABLE_DATA_ROW_OFFSET"))
            set_value(r, upper_param, value);

        else if (!strcmp(upper_param, "TABLE_COLUMNS"))
            if (type == tlist)
                SimpleListToRequestParameter(r, upper_param, list);
            // set_list(r, upper_param, list);
            else
                set_value(r, upper_param, value);

        else if (!strcmp(upper_param, "TABLE_COLUMN_TYPES"))
            if (type == tlist)
                SimpleListToRequestParameter(r, upper_param, list);
            // set_list(r, upper_param, list);
            else {
                char paramCopy[1024];
                strcpy(paramCopy, upper_param);
                set_value(r, paramCopy, upper_string(value));
            }

        else if (!strcmp(upper_param, "TABLE_META_DATA_ROWS"))
            if (type == tlist)
                SimpleListToRequestParameter(r, upper_param, list);
            // set_list(r, upper_param, list);
            else
                set_value(r, upper_param, value);

        else
            return Error("read_table: %s is not a valid input parameter", param);
    }

    // find the file that we want to load

    // const char *p = get_value(request,"FILENAME",0);
    auto* table = new CTable(r);

    if (!table->constructedOk()) {
        return Error("read_table: could not read table because of previous error");
    }


    return Value(table);
}


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

class TableCountFunction : public Function
{
public:
    TableCountFunction(const char* n) :
        Function(n, 1, ttable) {}
    virtual Value Execute(int arity, Value* arg);
};

Value TableCountFunction::Execute(int, Value* arg)
{
    CTable* t;
    arg[0].GetValue(t);
    t->load();
    return Value(t->Count());
}


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

class TableValuesFunction : public Function
{
    bool fieldFromName_{false};

public:
    TableValuesFunction(const char* n) :
        Function(n)
    {
        info = "Returns a list of values from the given table column.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


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

    if (arg[0].GetType() != ttable)  // first arg should always be a table
        return false;

    if (arg[1].GetType() == tnumber)  // second arg could be a number
    {
        fieldFromName_ = false;
        return true;
    }

    if (arg[1].GetType() == tstring)  // second arg could be a string
    {
        fieldFromName_ = true;
        return true;
    }

    return false;
}


Value TableValuesFunction::Execute(int, Value* arg)
{
    CTable* ctable;
    arg[0].GetValue(ctable);  // first arg is a table
    MvTableColumn* col;
    ctable->load();


    if (fieldFromName_) {
        const char* colName;
        arg[1].GetValue(colName);  // second arg is the name of the desired column
        std::string strColName(colName);
        col = ctable->column(strColName);  // get the relevant column

        if (!col) {
            return Error("Table has no column named %s.", colName);
        }
    }

    else {
        int colNum;
        arg[1].GetValue(colNum);  // second arg is the index of the desired column
        int baseIndex = Context::BaseIndex();
        int colIndex = colNum - baseIndex;  // our internal indexes are zero-based


        if (colIndex < 0 || colIndex > ctable->Count() - 1)  // index bounds check
        {
            return Error("Table has column indexes %d to %d - cannot access column %d.",
                         baseIndex, ctable->Count() - (1 - baseIndex), colNum);
        }


        col = ctable->column(colIndex);  // get the relevant column
    }


    if (!col || col->count() <= 0)  // no column? no values? return 'nil'
        return Value();


    // now fill either a vector of numbers or a list of strings

    Content* c = 0;

    if (col->type() == MvTableColumn::COL_NUMBER) {
        auto* v = new CVector(col->count());  // vectors can only hold numbers
        c = v;
        for (int i = 0; i < col->count(); i++) {
            v->setIndexedValue(i, col->dVals().at(i));
        }
    }
    else if (col->type() == MvTableColumn::COL_STRING) {
        auto* v = new CList(col->count());  // we need a list to hold strings
        c = v;
        for (int i = 0; i < col->count(); i++) {
            (*v)[i] = col->sVals().at(i).c_str();
        }
    }


    return Value(c);
}


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

class TableNameFunction : public Function
{
public:
    TableNameFunction(const char* n) :
        Function(n, 2, ttable, tnumber)
    {
        info = "Returns the name of the given table column.";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value TableNameFunction::Execute(int, Value* arg)
{
    CTable* ctable;
    arg[0].GetValue(ctable);  // first arg is a table
    ctable->load();

    int colNum;
    arg[1].GetValue(colNum);    // second arg is the index of the desired column
    int colIndex = colNum - 1;  // our indexes are zero-based


    if (colIndex < 0 || colIndex > ctable->Count() - 1)  // index bounds check
    {
        return Error("Table has column indexes 1 to %d - cannot access column %d.", ctable->Count(), colNum);
    }


    MvTableColumn* col = ctable->column(colIndex);  // get the relevant column

    if (!col || col->name().empty())  // no name? return 'nil'
        return Value();


    // now return the name of the column

    return Value(col->name().c_str());
}


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

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

Value TableMetaDataKeysFunction::Execute(int, Value* arg)
{
    CTable* ctable;
    arg[0].GetValue(ctable);  // first arg is a table
    ctable->load();

    std::map<std::string, std::string> metadata = ctable->userMetaData();


    if (metadata.empty())  // no meta-data? return 'nil'
        return Value();


    // now construct a list of keys from the meta-data

    auto* v = new CList(metadata.size());  // we need a list to hold strings

    auto iter = metadata.begin();
    auto iter_end = metadata.end();

    int i = 0;
    while (iter != iter_end) {
        (*v)[i++] = (*iter).first.c_str();
        iter++;
    }

    return v;
}


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

class TableMetaDataValueFunction : public Function
{
    std::map<std::string, std::string> metadata_;
    enum eModeType
    {
        SINGLE,
        LIST
    };
    eModeType mode{SINGLE};

public:
    TableMetaDataValueFunction(const char* n) :
        Function(n, 2, ttable, tstring)
    {
        info = "Returns the value of then given meta data key for a table";
    }
    Value GetMetaDataFromKey(const char* key);
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};


int TableMetaDataValueFunction::ValidArguments(int arity, Value* arg)
{
    // arguments can be: (table, string)  OR  (table, [list of strings])

    if (arg[0].GetType() != ttable)  // first arg should always be a table
        return false;

    if (arity != 2)  // must have two arguments
    {
        return false;
    }

    if (arg[1].GetType() == tstring)  // second arg could be a string
    {
        mode = SINGLE;
        return true;
    }

    if (arg[1].GetType() == tlist)  // second arg could be a list
    {
        mode = LIST;
        return true;
    }

    return false;
}


Value TableMetaDataValueFunction::GetMetaDataFromKey(const char* key)
{
    std::string value;

    if (metadata_.count(key)) {
        value = metadata_[std::string(key)];
        return Value(value.c_str());
    }
    else {
        marslog(LOG_WARN, "Table has no metadata associated with key '%s'", key);
        return Value();  // return 'nil'
    }
}

Value TableMetaDataValueFunction::Execute(int, Value* arg)
{
    CTable* ctable;
    arg[0].GetValue(ctable);  // first arg is a table
    ctable->load();

    metadata_ = ctable->userMetaData();


    if (metadata_.empty())  // no meta-data? return 'nil'
    {
        marslog(LOG_WARN, "Table has no metadata");
        return Value();
    }


    if (mode == SINGLE)  // returning a single value from a single key
    {
        const char* key;
        arg[1].GetValue(key);
        return GetMetaDataFromKey(key);
    }

    else /*if (mode == LIST)*/  // returning a list of values from a list of keys
    {
        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);
            (*values)[i] = GetMetaDataFromKey(key);
        }
        return values;
    }
}


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


static void install(Context* c)
{
    c->AddFunction(new TableReadWithParamsFunction("read_table"));
    c->AddFunction(new TableCountFunction("count"));
    c->AddFunction(new TableValuesFunction("values"));
    c->AddFunction(new TableNameFunction("name"));
    c->AddFunction(new TableMetaDataKeysFunction("metadata_keys"));
    c->AddFunction(new TableMetaDataValueFunction("metadata_value"));
}

static Linkage linkage(install);
