/***************************** 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 "macro.h"
#include "cbufr.h"
#include "cgptset.h"
#include "cnetcdf.h"
#include "ctable.h"

#ifdef METVIEW_ODB
#include "codb.h"
#endif

#include <math.h>
#include <ctype.h>
#include <stdarg.h>

static struct
{
    vtype t;
    const char* n;
} types[] = {
    {
        tnumber,
        "number",
    },
    {
        tstring,
        "string",
    },
    {
        tdate,
        "date",
    },
    {
        tgrib,
        "fieldset",
    },
    {
        tbufr,
        "observations",
    },
    {
        tmatrix,
        "matrix",
    },
    {
        tlist,
        "list",
    },
    {
        trequest,
        "definition",
    },
    {
        tfile,
        "file",
    },
    {
        tdefered,
        "defered",
    },
    {
        terror,
        "error",
    },
    {
        timage,
        "image",
    },
    {
        tvector,
        "vector",
    },
    {
        tgeopts,
        "geopoints",
    },
    {
        tnetcdf,
        "netcdf",
    },
    {
        tobject,
        "object",
    },
    {
        tnil,
        "nil",
    },
    {
        todb,
        "odb",
    },
    {
        ttable,
        "table",
    },
    {
        tgptset,
        "geopointset",
    },
};
//=============================================================================

class SimpleNotify : public Notify
{
    char* module;
    char* notify;
    virtual void trigger(void*);

public:
    SimpleNotify(const char*, const char*);
    virtual ~SimpleNotify();
};

SimpleNotify::SimpleNotify(const char* m, const char* n)
{
    module = strcache(m);
    notify = strcache(n);
}

SimpleNotify::~SimpleNotify()
{
    strfree(module);
    strfree(notify);
}

void SimpleNotify::trigger(void* data)
{
    auto* c = (Content*)data;

    request* r = 0;
    request* s = empty_request(notify);

    c->ToRequest(r);

    s->next = r;

    Value v(module, s);  // Notify module

    s->next = 0;
    free_all_requests(s);

    delete this;
}

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

int Content::Check(int fail, Value& v, int arity, Value* arg, vtype t, int cnt, ...)
{
    int ok = 1;
    const char* p;

    if (arity != cnt) {
        if (fail)
            Error("Wrong number of indices for a %s, number is %d and should be %d",
                  TypeName(), arity, cnt);
        return 0;
    }

    if ((v.GetType(&p) & t) == 0) {
        if (fail)
            Error("Values of type '%s' cannot assigned to a %s element",
                  p, TypeName());
        return 0;
    }

    va_list list;
    va_start(list, cnt);

    for (int i = 0; i < cnt; i++) {
        vtype t = va_arg(list, vtype);
        if ((arg[i].GetType(&p) & t) == 0) {
            if (fail)
                Error("Values of type '%s' cannot be used as an index to a %s", p, TypeName());
            ok = 0;
        }
    }

    va_end(list);
    return ok;
}

int Content::CopyFile(const char* fname, FILE* to)
{
    FILE* from = fopen(fname, "r");
    if (!from)
        return Error("-Cannot open file %s", fname);

    int n;
    char buf[10240];
    while ((n = fread(buf, 1, sizeof(buf), from)) > 0)
        fwrite(buf, 1, n, to);

    int e = ferror(from) || ferror(to);
    fclose(from);
    return e;
}

int Content::Error(const char* msg, ...)
{
    va_list list;
    char buf[1024];
    va_start(list, msg);
    vsprintf(buf, msg, list);
    va_end(list);
    if (Context::Current)
        Context::Current->Error("%s", buf);
    else
        marslog(LOG_EROR | LOG_PERR, "%s", buf);
    return -1;
}

int Content::Write(FILE*)
{
    return Error("Cannot write a %s", TypeName());
}

void Content::Fail(const char* s)
{
    if (isupper(*s))
        Error("Oops... can't %s a %s", TypeName());
    else
        Error("Oops... can't convert %s to %s", TypeName(), s);
}

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

InPool::InPool(vtype t, const char* n) :
    Content(t)
{
    iconName = strcache(n ? n : Context::UniqueName());
    isIcon = false;
}

InPool::InPool(vtype t, request* r) :
    Content(t)
{
    iconName = strcache(get_value(r, "_NAME", 0));
    isIcon = true;  // This comes from MetviewUI

    if (iconName == 0)
        iconName = strcache(Context::UniqueName());

    // Check if we asked this data to be created
    const char* p = get_value(r, "_CALLED_FROM_MACRO", 0);
    if (p)
        isIcon = boolean(!atoi(p));
}

InPool::~InPool()
{
    //   if(iconName && !isIcon)
    //      //ASync::IconStatus(iconName,"MODIFIED");
    //      marslog(LOG_INFO,"VERY IMPORTANT *********** InPool Destructor. The data might not be removed: %s . CHECK PLEASE *****************", iconName);

    strfree(iconName);
}

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

void CError::Print()
{
    std::cout << "<error: " << msg << ">";
}

void CError::ToRequest(request*& x)
{
    static request* s = 0;
    if (!s)
        s = empty_request("ERROR");
    set_value(s, "MESSAGE", "%s", msg);
    set_value(s, "CODE", "%d", error);
    x = s;
}

//=============================================================================
svc* ASync::Svc = 0;
int ASync::RequestMax = 3;
int ASync::RequestCnt = 0;
char* ASync::Name = 0;

void ASync::Connect()
{
    if (Svc == 0) {
        Name = strcache(Context::UniqueName());
        Svc = create_service(Name);
        add_reply_callback(Svc, 0, Reply, 0);
        add_service_callback(Svc, "DEFINITION", Definition, 0);
        keep_alive(Svc, 1);  // Stay alive until macro dies
                             //		add_progress_callback(Svc,0,Progress,0);
    }
}

void ASync::Store(const char* name, request* r)
{
    Connect();
    pool_store(Svc, name, "macro", r);
}

request* ASync::Fetch(const char* name)
{
    Connect();
    return pool_fetch(Svc, name, "macro");
}

void ASync::IconStatus(const char* name, const char* status, const char* icon)
{
    Connect();
    if (Svc) {
        static request* r = 0;
        if (r == 0)
            r = empty_request("STATUS");
        set_value(r, "NAME", "%s", name);
        set_value(r, "STATUS", "%s", status);
        if (icon)
            set_value(r, "ICON_NAME", "%s", icon);
        send_message(Svc, r);
    }
}

void ASync::Progress(svcid* id, request*, void*)
{
    int n = 0;
    const char* p;

    // marslog(LOG_INFO,"Progress from %s:", get_svc_target(id));
    while ((p = get_svc_msg(id, n++)))
        marslog(LOG_INFO, "%s", p);
}

void ASync::Definition(svcid* id, request* s, void*)
{
    print_all_requests(s);
    set_svc_err(id, 1);
    send_reply(id, 0);
}


void ASync::Reply(svcid* id, request* s, void*)
{
    auto* a = (ASync*)get_svc_ref(id);

    RequestCnt--;

    if (mars.debug) {
        printf("Got reply from %s\n", get_svc_target(id));
        print_all_requests(s);
    }

    if (a) {
        err e;

        if ((e = get_svc_err(id))) {
            request* r = empty_request("ERROR");
            const char* msg;
            int n = 0;

            set_value(r, "CODE", "%d", e);

            while ((msg = get_svc_msg(id, n++)))
                add_value(r, "MESSAGE", "%s", msg);

            a->SetRequest(r);

            free_all_requests(r);
        }
        else {
            const char* p = get_value(s, "_NOTIFY", 0);
            if (p)
                set_value(s, "_FROM", "%s", get_svc_target(id));

            // Print any information coming from the target module(s).
            // If this is too many information send to the user,
            // uncomment the line below.
            // if(mars.debug)
            {
                const char* msg;
                int n = 0;
                while ((msg = get_svc_msg(id, n++)))
                    marslog(LOG_INFO, "%s\n", msg);
            }
            a->SetRequest(s);
        }

        a->Ready = true;
        a->Detach();
    }
}

request* ASync::Wait()
{
    if (!Ready && Context::Trace())
        std::cout << "# Line " << Context::CurrentLine()
                  << ": Waiting for asynchronious request" << std::endl;

    while (!Ready && service_sync(Svc))
        ;

    return GetRequest();
}

ASync::ASync(const char* n, request* s) :
    CRequest(s)
{
    Ready = false;
    Connect();

    while (RequestCnt >= RequestMax) {
        if (Context::Trace())
            std::cout << "# Line " << Context::CurrentLine()
                      << ": " << RequestCnt << " request" << (RequestCnt > 1 ? "s " : " ")
                      << "out. Maximum is " << RequestMax << ", waiting..." << std::endl;
        service_sync(Svc);
    }

    // add a line to tell the module which directory to run from
    char cd[1024];
    getcwd(cd, 1024);
    set_value(s, "_CWD", cd);


    if (mars.debug) {
        printf("Sending to %s\n", n);
        print_all_requests(s);
    }

    if (!get_value(s, "_CALLED_FROM_MACRO", 0))
        set_value(s, "_CALLED_FROM_MACRO", "1");  // Mark it as being  ours

    RequestCnt++;
    call_service(Svc, n, s, (long)this);
    Attach();  // 1 for the ref in call_service
}

ASync::~ASync() = default;

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

Value::NilValue* Value::nilvalue = 0;

Value::Value(const char* name, request* r)
{
    c = new ASync(name, r);
    c->Attach();
}

Value::Value(const char* name, request* r, std::vector<Content*> attachedContent)
{
    c = new ASync(name, r);
    c->Attach();
    auto* async = (ASync*)c;
    async->SetAttachedContent(attachedContent);  // - see comments for attachedContent in value.h.
}

void Value::Copy(const Value& v)
{
    c = v.c;
    c->Attach();
}

void Value::SetContent(Content* x)
{
    Content* y = c;
    c = x;
    c->Attach();
    y->Detach();
}

void ASync::Sync(Value* v)
{
    request* s = Wait();
    Content* vc = v->GetContent();
    vc->Attach();  // so we don't lose it in the next line
    v->SetContent(s);


    // check for consistency of field->gribfile pointers

    AvoidDuplication(v->GetContent());


    // detach any data which was an input to the computation for this variable
    // (meaning in reality that we've called another module to do the computation
    // and we now want to release the input data)
    // - see comments for attachedContent in value.h.

    for (auto c : attachedContent) {
        c->Detach();
    }

    vc->Detach();  // now we can delete it
}

void ASync::AvoidDuplication(Content* c)
{
    // A problem we can have with temporary files is that when we pass
    // a GRIB to the READ module, both fieldsets (input, stored in the
    // attached content, and output, the result of the command) will have
    // different gribfile objects contained in their fieldsets, but both
    // will point to the same file. This can be an issue when it comes to
    // deleting temporary files because one fieldset can 'think' that it has
    // finished with the gribfile when it is destroyed, and it will remove
    // the file without realising that the other fieldset still needs it.
    // If we want the reference counting to work properly, then we need
    // to ensure that if two fieldsets use the same (temporary) GRIB file,
    // then they should be using the same gribfile object. The problem
    // comes from the fact that the result fieldset is constructed from
    // a request, and knows nothing of other fieldsets or gribfiles.

    if (c->GetType(nullptr) == tgrib) {
        // loop through all the attached content and only consider GRIBs

        for (auto a : attachedContent) {
            if ((a) && a->GetType(nullptr) == tgrib)  // both are GRIB
            {
                auto* cg1 = (CGrib*)c;  // the returned data
                auto* cg2 = (CGrib*)a;  // the attached (input) data

                fieldset* fs1 = cg1->GetFieldset();
                fieldset* fs2 = cg2->GetFieldset();

                // loop through the fields to compare the gribfile members
                // Since we're doing this mainly for the situation where we've
                // passed a GRIB file to the GRIB Filter module, the result
                // should be smaller (less fields) than the input, so we should
                // loop on the result first (assuming that we are likely to
                // find the corresponding gribfile quicky).

                for (int i = 0; i < fs1->count; i++) {
                    field* f1 = fs1->fields[i];
                    gribfile* g1 = f1->file;

                    for (int j = 0; j < fs2->count; j++) {
                        gribfile* g2 = fs2->fields[j]->file;
                        if (!strcmp(g2->fname, g1->fname))  // point to the same physical file?
                        {
                            // point f1 to g2
                            g2->refcnt++;
                            g1->refcnt--;
                            f1->file = g2;
                            break;  // don't need to search fs1 any further
                        }
                    }
                }

            }  // both are GRIB

        }  // for each attached content
    }
}


void Value::CleanUp()
{
    c->Detach();
}

Value& Value::operator=(const Value& v)
{
    SetContent(v.c);
    return *this;
}

Value::Value(const Value& v)
{
    Copy(v);
}

Value::~Value()
{
    CleanUp();
}

const char* Value::TypeName(vtype t)
{
    for (auto& type : types)
        if (type.t == t)
            return type.n;
    return "unknown";
}

vtype Value::NameType(const char* n)
{
    if (n == 0)
        return tany;
    for (auto& type : types)
        if (strcmp(type.n, n) == 0)
            return type.t;
    return tnone;
}

void Value::SetContent(request* r)
{
    request* s = r;

    if (r == 0) {
        SetContent(new CNil);
        return;
    }


    if (strcmp(r->name, "ERROR") == 0) {
        int n = count_values(r, "MESSAGE");
        for (int i = 0; i < n; i++)
            Context::Current->Error("%s", get_value(r, "MESSAGE", i));
        SetContent(new CError(1, get_value(r, "MESSAGE", 0)));
        return;
    }

    char* from = strcache(get_value(r, "_FROM", 0));
    char* notify = strcache(get_value(r, "_NOTIFY", 0));

    unset_value(r, "_NOTIFY");
    unset_value(r, "_FROM");

    int n = 0;
    while (s) {
        s = s->next;
        n++;
    };

    Content* c = nullptr;
    CList* l = n != 1 ? new CList(n) : nullptr;

    int i = 0;
    while (r) {
        // If a requests returns more that once item
        // then should all have a different name ...

        if (n > 1)
            set_value(r, "_NAME", "%s", Context::UniqueName());

        s = r->next;
        r->next = 0;

        char* p = r->name;

        if (p && strcasecmp(p, "GRIB") == 0)
            c = new CGrib(r);
        else if (p && strcasecmp(p, "BUFR") == 0)
            c = new CBufr(r);
        else if (p && strcasecmp(p, "GEOPOINTS") == 0)
            c = new CGeopts(r);
        else if (p && strcasecmp(p, "GEOPOINTSET") == 0)
            c = new CGeoptSet(r);
        else if (p && strcasecmp(p, "NETCDF") == 0)
            c = new CNetCDF(r);
        else if (p && strcasecmp(p, "IMAGE") == 0)
            c = new CImage(r);
        else if (p && strcasecmp(p, "NUMBER") == 0)
            c = new CNumber(atof(get_value(r, "VALUE", 0)));
        else if (p && strcasecmp(p, "STRING") == 0)
            c = new CString(get_value(r, "VALUE", 0));
        else if (p && strcasecmp(p, "VECTOR") == 0)
            c = new CVector(r);
        else if (p && strcasecmp(p, "TABLE") == 0)
            c = new CTable(r);
#ifdef METVIEW_ODB
        else if (p && strcasecmp(p, "ODB_DB") == 0)
            c = new COdb(r);
#endif
        else if (p && (strcasecmp(p, "SCM_INPUT_DATA") == 0 || strcasecmp(p, "SCM_OUTPUT_DATA") == 0))
            c = new CNetCDF(r);
        else
            c = new CRequest(r);

        r->next = s;
        r = s;

        if (n != 1)
            (*l)[i++] = Value(c);
    }

    if (n != 1)
        c = l;


    if (from && notify)
        c->SetNotify(new SimpleNotify(from, notify));

    strfree(from);
    strfree(notify);

    SetContent(c);
}

void Value::SetContentRequest(request* r)
{
    if (r == 0) {
        SetContent(new CNil);
        return;
    }

    request* s = r;
    int n = 0;
    while (s) {
        s = s->next;
        n++;
    };

    Content* c = nullptr;
    auto* l = new CList(n);

    int i = 0;
    while (r) {
        s = r->next;
        r->next = 0;

        c = new CRequest(r);
        (*l)[i++] = Value(c);

        r->next = s;
        r = s;
    }

    c = l;
    SetContent(c);
}

const char* Content::TypeName()
{
    return Value::TypeName(type);
}

void Content::Dump(int level)
{
    if (level == 1)
        Dump1();
    else
        Dump2();
}

void Content::Perl(request* r)
{
    request* s = r;
    std::cout << "\n";
    if (r->next)
        std::cout << "[\n";

    while (s) {
        parameter* p = s->params;
        std::cout << "bless({\n";
        while (p) {
            if (*p->name != '_') {
                std::cout << p->name << " => ";


                value* v = p->values;
                if (v && v->next)
                    std::cout << '[';
                value* w = v;
                int vflg = 0;

                while (w) {
                    if (vflg)
                        std::cout << ',';
                    std::cout << "'" << w->name << "'";
                    vflg++;
                    w = w->next;
                }

                if (v && v->next)
                    std::cout << ']';
                std::cout << ",\n";
            }
            p = p->next;
        }
        std::cout << "}, 'metview::" << s->name << "'),\n";
        s = s->next;
    }
    if (r->next)
        std::cout << "\n]";
    std::cout << "\n";
}

void CNil::Dump2()
{
    std::cout << "undef";
}
