/***************************** LICENSE START ***********************************

 Copyright 2017 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 *************************************/

/*
 * Experimental Python interface routines
 */


#include <stdio.h>
#ifdef METVIEW_ODB_NEW
#include <eckit/runtime/Main.h>
#endif
#include "macro.h"
#include "script.h"
#include "cbufr.h"
#include "codb.h"

class PythonScript : public Script
{
public:
    PythonScript() :
        Script("PythonScript")
    {
        Current = this;
        Step* s = new Step;
        AddStep(s);
        SetInstruction(s);
        Context::BaseIndex(0);            // Python uses zero-based indexing
        Context::BaseLanguage("python");  // Python uses zero-based indexing
        AddBaseLanguageGlobalVar();
    }
};


#ifdef METVIEW_ODB_NEW
void init_odb(int argc, char** argv)
{
    eckit::Main::initialise(argc, argv);
}
#endif

extern "C" {

PythonScript* metviewPythonScript;
Value* metviewPythonResult;
bool doneInit = false;

char* progName;
char* argv[1];

const char* noneString = "NONE";

int mp_init(int argc, char** argv)
{
    if (!doneInit) {
        marsinit(&argc, argv, 0, 0, 0);
#ifdef METVIEW_ODB_NEW
        init_odb(argc, argv);
#endif
        metviewPythonScript = new PythonScript();

        // if we don't do this, then for some requests sent to uPlot,
        // _PATH will not be defined, and we will not have an absolute path to
        // the data file
        char pwd[1024];
        getcwd(pwd, sizeof(pwd) - 1);
        // Script::MacroPath(makepath(pwd,"PythonScript.py"));  // dummy filename
        Script::MacroMainPath(makepath(pwd, "PythonScript.py"));  // dummy filename
        doneInit = true;
    }
    return 0;
}


// each time we pass a Value back to Python, we should create a local
// copy of it on the heap - the content will not be duplicated, but
// its reference counter will be increased, so that it will not be destroyed
// until the Python destructor asks for the Value to be destroyed
Value* local_copy_of_value(const Value& v)
{
    return new Value(v);
}

void p_init()
{
    // printf("Metview::p_init called from Python\n");
    progName = "Python";
    argv[0] = progName;
    mp_init(1, argv);
}


const char* p_hello_world(int argc)
{
    char* h = strcache("hello");
    printf("Hello World from Macro to Python!\n");
    metviewPythonScript->CallFunction(h, argc);
    return h;
}


const char* p_call_function(const char* name, int arity)
{
    // printf("MARS: %s\n", mars.langfile);
    // printf("MARS: %s\n", mars.mars_home);

    char* fname = strcache(name);
    // printf("Calling function %s with %d args!\n", name, arity);
    metviewPythonScript->SetError(0);  // reset the error flag
    metviewPythonScript->CallFunction(fname, arity);
    // printf("Calling finished!\n\n");

    // if there was an error, we should return a CError value
    if (metviewPythonScript->GetError()) {
        auto* ce = new CError(1, "Error calling Metview function");
        metviewPythonResult = new Value(ce);
        return fname;
    }

    // create a new value on the heap - it will live until Python asks for it to be destroyed
    metviewPythonResult = local_copy_of_value(metviewPythonScript->Pop());

    // special treatment for the plot() and metzoom() commands - they are
    // not executed immediately when run from Macro, but
    // we would like them to be executed immediately here.
    if (fname == strcache("plot") || fname == strcache("metzoom")) {
        if (Script::PlotReq) {
            // Special case if metzoom is defined: always call uPlotManager.
            // If setouput is also defined then ignore it.
            MvRequest req;
            if (fname == strcache("metzoom")) {
                req = Script::PlotReq;
                PlotterFunction::SetPlotter("uPlotManager");
            }
            else {
                // Concatenate Output Driver and Plot request
                request* reqdrive;
                Script::Output.GetValue(reqdrive);
                MvRequest req_aux(reqdrive);
                req = req_aux;
                req = req + Script::PlotReq;
            }

            // Flush
            Value v(PlotterFunction::Plotter(), req);
            v.Sync();  // Force sync

            // empty request
            Script::PlotReq.clean();

            // if there was an error, we should return a CError value
            if (metviewPythonScript->GetError()) {
                auto* ce = new CError(1, "Error calling Metview function");
                metviewPythonResult = new Value(ce);
                return fname;
            }
        }
    }

    return fname;
}


// ------------------------------------------------------------------------------------------
// The 'push' functions take objects from Python and push them onto Macro's operand stack
// (Python -> Macro)
// ------------------------------------------------------------------------------------------

void p_push_number(double n)
{
    metviewPythonScript->Push(n);
}

void p_push_string(const char* str)
{
    metviewPythonScript->Push(str);
}

void p_push_request(void* req)
{
    request* r = *(MvRequest*)req;
    metviewPythonScript->Push(r);
}

void p_push_value(void* value)
{
    auto* val = (Value*)value;
    metviewPythonScript->Push(*val);
}

void p_push_datestring(const char* str)
{
    // The string is in "yyyy-mm-ddTHH:MM:SS" format
    if (strlen(str) >= 19) {
        // We need to convert it to "yyyy-mm-dd HH:MM:SS" format
        // for the Date constructor
        static char mdateStr[20];
        strncpy(mdateStr, str, 19);
        mdateStr[19] = '\0';
        mdateStr[10] = ' ';
        Date mdate(mdateStr);
        metviewPythonScript->Push(mdate);
    }
}

void p_push_list(void* lst)
{
    auto* l = (CList*)lst;
    metviewPythonScript->Push(l);
}

void p_push_vector_from_double_array(double* vals, int size, double numpy_missing_value)
{
    auto* v = new CVector(vals, size, CArray::VALUES_F64);
    v->Replace(numpy_missing_value, VECTOR_MISSING_VALUE);  // convert missing values
    metviewPythonScript->Push(v);
}

void p_push_vector_from_float32_array(float* vals, int size, float numpy_missing_value)
{
    auto* v = new CVector(vals, size, CArray::VALUES_F32);
    v->Replace(numpy_missing_value, VECTOR_F32_MISSING_VALUE);  // convert missing values
    metviewPythonScript->Push(v);
}

void p_push_nil()
{
    CNil* nil = new CNil();
    metviewPythonScript->Push(nil);
}


// ------------------------------------------------------------------------------------------
// These functions take objects from Macro and return them to Python
// (Macro -> Python)
// ------------------------------------------------------------------------------------------

static int value_type_to_python_int(vtype t)
{
    switch (t) {
        case tnumber:
            return 0;
        case tstring:
            return 1;
        case tgrib:
            return 2;
        case trequest:
            return 3;
        case tbufr:
            return 4;
        case tgeopts:
            return 5;
        case tlist:
            return 6;
        case tnetcdf:
            return 7;
        case tnil:
            return 8;
        case terror:
            return 9;
        case tdate:
            return 10;
        case tvector:
            return 11;
        case todb:
            return 12;
        case ttable:
            return 13;
        case tgptset:
            return 14;
        case tfile:
            return 15;
        default:
            return 99;
    }
    return 99;
}

/*
int p_result_type(void)
{
    argtype t = metviewPythonResult.GetType();
    metviewPythonReturnedVariables->Add(metviewPythonResult);
    return (value_type_to_python_int(t));
}
*/

Value* p_result_as_value(void)
{
    // force a sync in order to get the real result (required when calling
    // another module)
    metviewPythonResult->GetType();
    return metviewPythonResult;
}

int p_value_type(Value* val)
{
    vtype t = val->GetType();
    return (value_type_to_python_int(t));
}


const char* p_value_as_string(Value* val)
{
    const char* res;
    val->GetValue(res);
    return res;
}

double p_value_as_number(Value* val)
{
    double res;
    val->GetValue(res);
    return res;
}

const char* p_error_message(Value* val)
{
    CError* error;
    val->GetValue(error);
    return error->Message();
}

char* p_value_as_datestring(Value* val)
{
    Date mdate;
    val->GetValue(mdate);
    static char res[20];
    mdate.Format("yyyy-mm-ddTHH:MM:SS", res);
    return res;
}

CVector* p_value_as_vector(Value* val, double numpy_missing_value)
{
    CVector* vec;
    val->GetValue(vec);
    vec->Replace(VECTOR_MISSING_VALUE, numpy_missing_value);
    return vec;
}

// XXX should be 'long' for future robustness
int p_vector_count(CVector* v)
{
    return v->Count();
}

// may need revision in the future when we have 32-bit floats as well
int p_vector_elem_size(CVector* v)
{
    if (v->valuesType() == CArray::VALUES_F64)
        return sizeof(double);
    else
        return sizeof(float);
}

double* p_vector_double_array(CVector* v)
{
    return v->ValuesAsDoubles(0);
}

float* p_vector_float32_array(CVector* v)
{
    return v->ValuesAsFloat32s(0);
}

CList* p_value_as_list(Value* val)
{
    CList* lst;
    val->GetValue(lst);
    return lst;
}

int p_list_count(CList* lst)
{
    return lst->Count();
}


Value* p_list_element_as_value(CList* lst, int i)
{
    if (i < 0 || i > lst->Count() - 1) {
        // XXX should report an error
        err e = 1;
        return new Value(e, "Internal: bad list index");
        // printf("p_list_element_as_value: %d\n", i);
        // return 0;
    }

    return local_copy_of_value((*lst)[i]);
}


CList* p_new_list(int size)
{
    // XXX potential memory leak
    auto* lst = new CList(size);
    return lst;
}

void p_add_value_from_pop_to_list(CList* lst, int i)
{
    Value val = metviewPythonScript->Pop();
    (*lst)[i] = val;
}


/*MvRequest *p_value_as_request(Value *val)
{
    request *r;
    val->GetValue(r);
    MvRequest *req = new MvRequest(r);
    return req;
}*/

MvRequest* p_new_request(const char* verb)
{
    auto* req = new MvRequest(verb);
    return req;
}

void p_set_value(MvRequest* req, const char* param, const char* value)
{
    req->setValue(param, value);
}

void p_set_request_value_from_pop(MvRequest* req, const char* param)
{
    Value* val = local_copy_of_value(metviewPythonScript->Pop());
    request* r = (*req);
    SetValue(r, param, *val);
}


void p_add_value(MvRequest* req, const char* param, const char* value)
{
    req->addValue(param, value);
}

const char* p_get_req_verb(Value* val)
{
    request* r;
    val->GetValue(r);
    const char* verb = (r) ? r->name : 0;  // could have a NULL pointer for the request
    // XXXX it's not clear why we have to 'strdup' here, but it crashes otherwise
    return (verb) ? verb : strdup(noneString);
}

int p_get_req_num_params(Value* val)
{
    request* r;
    val->GetValue(r);
    if (r) {
        MvRequest req(r, false, false);     // clone=false, free_req_on_destroy=false
        return req.countParameters(false);  // includeHidden=false
    }
    else
        return 0;  // NULL request pointer -> no parameters
}

const char* p_get_req_param(Value* val, int i)
{
    request* r;
    val->GetValue(r);
    MvRequest req(r, false, false);  // clone=false, free_req_on_destroy=false
    return req.getParameter(i);
}

const char* p_get_req_value(Value* val, char* param)
{
    request* r;
    val->GetValue(r);
    MvRequest req(r, false, false);  // clone=false, free_req_on_destroy=false

    const char* cval;
    req.getValue(cval, param);
    return cval;
}

// e.g. fieldset[3] = myfield+1
// NOTE: as of metview-python 1.4.0, this function will no longer be required
// as p_set_subvalue_from_arg_stack does the same and more
void p_set_subvalue(Value* val, int index, Value* subval)
{
    Value indexVal(index);
    val->SetSubValue(*subval, 1, &indexVal);
}

// e.g. gpts['latitude'] = [3, 5, 6]
// before calling this function, the index and the subvalue
// should have been pushed onto the argument stack in that order
void p_set_subvalue_from_arg_stack(Value* val)
{
    Value subval(metviewPythonScript->Pop());
    Value indexVal(metviewPythonScript->Pop());
    val->SetSubValue(subval, 1, &indexVal);
}


void p_set_temporary(Value* /*val*/, int /*flag*/)
{
    // implementation pending
}

const char* p_data_path(Value* val)
{
    request* req;
    val->GetValue(req);
    request* finalreq = req;

    // in the case where this is the result of a GRIB filter, we will need to
    // make a copy of the fieldset and write it to disk, and then return the
    // path to that instead of the original file

    const char* fff = get_value(req, "FIELDSET_FROM_FILTER", 0);
    if (fff && !strcmp(fff, "1")) {
        fieldset* fs = request_to_fieldset(req);
        fieldset* z = copy_fieldset(fs, fs->count, true);
        save_fieldset(z);
        finalreq = fieldset_to_request(z);
    }

    const char* path = get_value(finalreq, "PATH", 0);
    return path;
}

void p_destroy_value(Value* val)
{
    val->Destroy();
}
}

int main(int /*argc*/, char** /*argv*/)
{
    /*
    p_init();
    p_push_number(7);
//    const char *pp = p_hello_world(1);
    const char *pp = p_call_function("print", 1);
    const char *rt = p_result_type();
    printf("Return type: %s\n", rt);

    p_push_number(1);
    p_call_function("waitmode", 1);

    void* r = p_new_request("RETRIEVE");
    p_push_request(r);
    const char *pr = p_call_function("retrieve", 1);
    rt = p_result_type();
    printf("Return type: %s\n", rt);
    //printf("Return path: %s\n", p_result_as_grib_path());
    printf("Done\n");

*/
    // MvRequest* r = p_new_request("MCOAST");
    // p_set_value(r, "map_coastline_colour", "red");
    // p_push_request(r);
    // const char *pr = p_call_function("plot", 1);

    return 0;
}
