/***************************** 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 "code.h"
#include <signal.h>

ExternFunction::ExternFunction(const char* nam, Context* s) :
    UserFunction(nam, s)
{
    cmd = nullptr;
    file = nullptr;
    compiled = 0;
}


ExternFunction::~ExternFunction()
{
    if (file)
        unlink(file);
    strfree(cmd);
    strfree(file);
}

Value ExternFunction::Execute(int arity, Value* arg)
{
    const char* p;
    request* r = nullptr;
    request* s = nullptr;
    request* u = nullptr;

    if (Compile() != 0)
        return Error("Failed to compile inline");

    for (int i = 0; i < arity; i++) {
        switch (arg[i].GetType(&p)) {
            case tnumber:
            case tstring:
            case tgrib:
                //			case tbufr:
            case timage:
            case tvector:
                arg[i].GetValue(u);
                if ((u = clone_all_requests(u))) {
                    if (r == nullptr)
                        r = u;
                    else
                        s->next = u;
                    while (u) {
                        s = u;
                        u = u->next;
                    }
                }
                break;

            default:
                free_all_requests(r);
                return Error(
                    "Type '%s' is not yet supported"
                    " for external Fortran functions",
                    p);
        }
    }

    char* tmp = marstmp();
    char buf[1024];

    FILE* f = fopen(tmp, "w");
    save_all_requests(f, r);
    fclose(f);
    free_all_requests(r);

    const char* FortranDbg = getenv("MACRO_FORTRAN_DBG");
    int n = -1;

    if (FortranDbg) {
        marslog(LOG_INFO, "MACRO_FORTRAN_DBG is set to %s", FortranDbg);
        marslog(LOG_INFO, "Now starting %s, get ready...", FortranDbg);

        sprintf(buf, "env PATH=$PATH:$METVIEW_BIN MNAME=%s MREQUEST=%s %s %s 2>&1",
                Name(), tmp, FortranDbg, cmd ? cmd : Name());

        signal(SIGCHLD, SIG_DFL);

        n = system(buf);  //-- return value from debugger?
    }
    else {
        sprintf(buf, "env PATH=$PATH:$METVIEW_BIN MNAME=%s MREQUEST=%s %s 2>&1",
                Name(), tmp, cmd ? cmd : Name());

        signal(SIGCHLD, SIG_DFL);

        f = popen(buf, "r");

        if (f) {
            while (fgets(buf, sizeof(buf), f))
                std::cout << buf;
            /* note: if SIGCLD has been trapped, pclose will always return -1 */
            std::cout << std::flush;
            n = pclose(f);
        }
    }

    r = read_request_file(tmp);
    unlink(tmp);

    if (n) {
        free_all_requests(r);
        return Error("Error %d when executing external Fortran function %s",
                     n, Name());
    }

    Value v;
    v.SetContent(r);

    free_all_requests(r);
    return v;
}

void ExternFunction::SetCommand(const char* c, const char* f)
{
    strfree(cmd);
    cmd = strcache(c);
    strfree(file);
    file = strcache(f);
}

int ExternFunction::Compile(void)
{
    if (compiled)
        return 0;

    compiled = 1;
    if (file == nullptr)
        return 0;

    char buf[2048];
    char* newcmd = marstmp();

    const char* mvVersion = getenv("METVIEW_VERSION");
    const char* mvDir = getenv("METVIEW_DIR");


    sprintf(buf, "env METVIEW_VVERSION=%s METVIEW_DDIR=%s $METVIEW_BIN/compile %s \"%s\" %s %s 2>&1",
            mvVersion, mvDir,
            (Context::Trace() > 0) ? "-d" : "",
            cmd, file, newcmd);

    FILE* f = popen(buf, "r");
    if (!f) {
        marslog(LOG_EROR | LOG_PERR, "command %s failed", buf);
        return 1;
    }

    while (fgets(buf, sizeof(buf), f))
        std::cout << buf;

    std::cout << std::flush;

    if (pclose(f) != 0)
        return 1;

    if (getenv("MACRO_FORTRAN_DBG"))
        marslog(LOG_INFO, "Source code not removed - be be used for debugging!");
    else
        unlink(file);

    SetCommand(newcmd, newcmd);
    return 0;
}
