/***************************** 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 "script.h"
#include "Cached.h"
#include "MvRequest.h"

svcid* Script::SvcId = 0;
Script* Script::Compiled = 0;
Value Script::Output = (request*)0;  // Default uPlot output/device
Value Script::Device = (request*)0;  // Default VisMod device/output
MvRequest Script::PlotReq;           // Plot request
char* Script::macropath = 0;
char* Script::macroMainPath = 0;

extern "C" {

int parse_macro(void);
void zzerror(char* msg)
{
    extern int zzlineno;
    Script::Compiled->CompileError(msg, zzlineno + 1);
}
}

void Script::PutMessage(int /*code*/, const char* msg)
{
    if (Script::SvcId) {
        // if(code != LOG_INFO) set_svc_msg(Script::SvcId,msg);
        send_progress(Script::SvcId, msg, 0);
    }
    printf("%s\n", msg);
}

void Script::PutExtendedMessage(int /*code*/, const char* msg)
{
    if (Script::SvcId) {
        // if(code != LOG_INFO) set_svc_msg(Script::SvcId,msg);
        request* r = empty_request("SEND_PROGRESS");
        set_value(r, "PROGRESS", "%s", msg);
        send_progress(Script::SvcId, msg, r);
        free_all_requests(r);
    }
    printf("%s\n", msg);
}

void Script::CompileError(const char* msg, int line)
{
    marslog(LOG_EROR, "Line %d: %s", line, msg);
    SetError(1);
}

Script::Script(const char* name) :
    Context(name)
{
    runmode = 0;
    Linkage::Install(this);
    SetRunMode("batch");
}

int Script::Compile(const char* filename)
{
    extern FILE* zzin;
    extern int zzlineno;
    //	extern int zzdebug;
    /* zzdebug = 1; */

    Cached oldDir(MacroMainPath());  //-- save previous path

    char pwd[1024];
    getcwd(pwd, sizeof(pwd) - 1);

    MacroPath(makepath(pwd, filename));
    MacroMainPath(makepath(pwd, filename));  //-- E.Monreal INM. 2003-02-26

    if (filename)
        zzin = fopen(filename, "r");
    else
        zzin = stdin;

    if (zzin == 0) {
        marslog(LOG_EROR | LOG_PERR, "Cannot open %s", filename);
        SetError(1);
        return GetError();
    }

    zzlineno = 0;

    Step* i = Instruction;
    Context* c = Current;
    Script* s = Compiled;
    Instruction = 0;
    Current = Compiled = this;
    parse_macro();
    Instruction = i;
    Current = c;
    Compiled = s;
    fclose(zzin);

    MacroMainPath((const char*)oldDir);  //-- revert to previous path

    return GetError();
}

Script::~Script()
{
    EmptyStack();
    strfree(runmode);
    ASync::Disconnect();

    // Clean output request
    Script::Output = (request*)0;
}

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

void Batch::CompileError(const char* p, int n)
{
    Script::CompileError(p, n);
    stop_all(batch, "Syntax error in macro", 1);
}

void Batch::RuntimeError(const char* p, int n)
{
    Script::RuntimeError(p, n);
    stop_all(batch, "Runtime error in macro", 1);
}

void Batch::BatchError(int err, void* data)
{
    if (err) {
        std::cout << "Macro Batch::BatchError: fatal error encountered!" << std::endl;

        int li = CurrentLine();
        std::cout << "Macro Batch::BatchError: error around line " << li << std::endl;

        ((Batch*)data)->RuntimeError("Panic, aborting", li);
    }
}

Batch::Batch(const char* name) :
    Script(name)
{
    install_exit_proc(BatchError, this);

    // In batch, connect to event so we are known
    svc_connect(batch = create_service("batch"));
}

Batch::~Batch()

{
    remove_exit_proc(BatchError, this);
}

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

void Terminal::NewStep(Step* s)
{
    if (last == 0) {
        last = s;
        s = 0;
    }
    while ((Instruction = last) && last->Ready())
        last = last->Execute();
    if (last == 0)
        last = s;
}

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

class RunMode1Function : public Function
{
public:
    RunMode1Function(const char* n) :
        Function(n, 0)
    {
        info = "Returns a string describing the current run mode";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value RunMode1Function::Execute(int, Value*)
{
    Node* n = Owner();
    while (n->Owner())
        n = n->Owner();
    auto* s = (Script*)n;
    return Value(s->GetRunMode());
}

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

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

Value RunMode2Function::Execute(int, Value* arg)
{
    Node* n = Owner();
    while (n->Owner())
        n = n->Owner();
    auto* s = (Script*)n;

    const char* m1;
    arg[0].GetValue(m1);

    const char* m2;
    m2 = s->GetRunMode();

    int m = strcasecmp(m1, m2) == 0;

    while (!m) {
        m2 = Context::FallBackHandler(m2);
        if (m2 == 0)
            break;
        m = strcasecmp(m1, m2) == 0;
    }

    return Value(m);
}


//============================================================================
//                                                              for old VisMod
class GetDeviceFunction : public Function
{
public:
    GetDeviceFunction(const char* n) :
        Function(n, 0) {}
    virtual Value Execute(int, Value*) { return Script::Device; }
};

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

class SetDeviceFunction : public Function
{
public:
    SetDeviceFunction(const char* n) :
        Function(n) {}
    virtual Value Execute(int, Value* arg);
};

Value SetDeviceFunction::Execute(int arity, Value* arg)
{
    PlotterFunction::SetPlotter("VisModTrans");
    Value v = Script::Device;
    if (arity)
        Script::Device = arg[0];
    else
        Script::Device = (request*)0;
    return v;
}

//============================================================================
//                                                             for new PlotMod
class GetOutputFunction : public Function
{
public:
    GetOutputFunction(const char* n) :
        Function(n, 0) {}
    virtual Value Execute(int, Value*) { return Script::Output; }
};

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

class SetOutputFunction : public Function
{
public:
    SetOutputFunction(const char* n) :
        Function(n) {}
    virtual Value Execute(int, Value* arg);
};

Value SetOutputFunction::Execute(int arity, Value* arg)
{
    // Get preferences
    //	MvRequest myPref = MvApplication::getPreferences();
    //	const char* myPlotModule = myPref( "DEFAULT_PLOT_MODULE" );

    // Flush previous plot commands
    if (Script::PlotReq) {
        // Concatenate Output Driver and Plot request
        request* reqdrive;
        Script::Output.GetValue(reqdrive);
        MvRequest req(reqdrive);
        req = req + Script::PlotReq;

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

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

    if (arity && arg[0].GetType() == tstring) {
        const char* device;
        arg[0].GetValue(device);
        if (!strcmp(device, "screen")) {
            PlotterFunction::Init();
            Script::Output = (request*)0;
            return Value();
        }
        else {
            return Error(
                "the only string accepted by setoutput is 'screen', "
                "but %s was supplied.",
                device);
        }
    }


    // Set output function
    PlotterFunction::SetPlotter("uPlotBatch");

    Value v = Script::Output;
    if (arity) {
        MvRequest req("PRINTER_MANAGER");
        MvRequest reqdev;
        request* s;
        for (int i = 0; i < arity; i++) {
            arg[i].GetValue(s);
            reqdev = reqdev + s;
        }
        req.setValue("OUTPUT_DEVICES", reqdev);
        Script::Output = Value(req);
    }
    else
        Script::Output = (request*)0;

    return v;
}

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

static void install(Context* c)
{
    c->AddFunction(new SetDeviceFunction("setdevice"));  //-- old VisMod DEVICE
    c->AddFunction(new GetDeviceFunction("getdevice"));
    c->AddFunction(new SetOutputFunction("setoutput"));  //-- new PlotMod DEVICE_DRIVER
    c->AddFunction(new GetOutputFunction("getoutput"));
    c->AddFunction(new RunMode1Function("runmode"));
    c->AddFunction(new RunMode2Function("runmode"));
}

static Linkage linkage(install);
