/***************************** 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 <iostream>

#include "macro.h"
#include "script.h"
#include <stdarg.h>

boolean Context::stop = false;
int Context::trace = 0;
int Context::waitmode = 0;
int Context::sendlines = 0;
int Context::pause = 0;
int Context::baseIndex = 1;  // Macro uses 1-based indexing, Python uses 0
const char* Context::baseLanguage = "macro";
Context* Context::Current = 0;
Step* Context::Instruction = 0;
Linkage* Linkage::Links = 0;
const char* Context::InitGlobals = strcache(".init_globals");
const int UN_BUFSIZ = 300;

const char* Context::UniqueName(void)
{
    static char buf[UN_BUFSIZ];
    static int n = 0;
    Context* c = Current;
    while (c && c->Owner())
        c = c->Owner();
    const char* name = (c && c->Name()) ? c->Name() : "?macro?";
    int cnt = sprintf(buf, "/Process@%d/%s, line %d:%d", getpid(), name,
                      Instruction ? Instruction->line : 0, n++);
    if (cnt >= UN_BUFSIZ)
        marslog(LOG_EXIT, "Buffer overflow in Context::UniqueName()");
    return buf;
}

static const char* modes[] = {

    "edit",
    0,

    "hardcopy",
    "batch",
    "batch",
    "execute",
    "execute",
    0,

    "prepare",
    "visualise",
    "examine",
    "visualise",
    "save",
    "visualise",
    "drop",
    "visualise",
    "visualise",
    "execute",

};


const char* Context::FallBackHandler(const char* name)
{
    static boolean first = true;

    if (!name)
        return 0;

    if (first) {
        for (auto& mode : modes)
            mode = strcache(mode);
        first = false;
    }

    for (unsigned int i = 0; i < NUMBER(modes); i += 2)
        if (name == modes[i])
            return modes[i + 1];

    return 0;
}

Value Context::Run(const char* handler, int ac, Value* av)
{
    argn = 0;
    argc = ac;
    argv = av;
    stop = false;

    if (!inited) {
        inited = 1;
        bool dummy;
        Function* f = FindFunction(InitGlobals, dummy, 0, 0);
        if (f && f->Owner() == this) {
            // Because InitGlobals does not return, push a value on the stack
            Push(Value(0.0));
            f->Execute(0, 0);
        }
    }

    Context* savedContext = Current;  // save the previous context
    Current = this;                   // ?????

    SetError(0);

    if (handler) {
        Function* f = FindHandler(handler, argc, argv);
        if (f)
            return f->Execute(argc, argv);
    }

    // Save locals
    List there;

    Locals.Save(there);

    SetInstruction(FirstStep());

    while (Instruction && (GetError() == 0) && !Stopped()) {
        if (Context::SendLines())  // should we send line numbers to the macero editor?
        {
            static char lineMessage[64];
            static int line = -1;

            if (Instruction->line != line) {
                line = Instruction->line;
                sprintf(lineMessage, "MACROEDITOR: LINE %d", line);
                Script::PutMessage(0, lineMessage);  // first param is unused

                if (Script::PauseBetweenLines())  // optional pause between lines?
                    sleep(PauseBetweenLines());
            }
        }
        Instruction = Instruction->Execute();
    }

    Locals.Empty();
    Locals.Restore(there);

    // Restore the pointer to the previous context
    if (savedContext)
        Current = savedContext;

    return Pop();
}

Context* Context::FindContext(const char* name)
{
    return (Context*)Contexts.Find(name, 0);
}

Function* Context::WhichFunction(const char* name, int arity, Value* arg, int method)
{
    // Make sure that all the ASync are up to date
    //	for(int i=0;i<arity;i++) arg[i].Sync();


    bool foundName = false;  // these functions will set to true if the name is valid, even if args are not
    Function* v = FindMethod(name, foundName, arity, arg, GetObject());

    if (!method) {
        if (v == 0)
            v = FindInDictionary(name, foundName, arity, arg);

        if (v == 0)
            v = FindFunction(name, foundName, arity, arg);

        if (!method && v == 0)
            v = FindFallback(name, foundName, arity, arg);

        if (v == 0)
            v = FindHandler(name, arity, arg);

        if (v == 0)
            v = FindLibrary(name, foundName, arity, arg);
    }

    if (v == 0) {
        char buf[1024];
        const char* methodOrFunction = (method) ? "Method" : "Function";
        if (foundName)
            sprintf(buf, "%s '%s' found, but incorrect argument types given: ", methodOrFunction, name);
        else
            sprintf(buf, "%s not found: %s", methodOrFunction, name);
        if (arity) {
            strcat(buf, "(");
            for (int i = 0; i < arity; i++) {
                const char* p;
                arg[i].GetType(&p);
                strcat(buf, p);
                if (i < arity - 1)
                    strcat(buf, ",");
            }
            strcat(buf, ")");
        }
        Error(buf);
    }
    return v;
}

void Context::CallFunction(const char* name, int arity, int method)
{
    Function* v = WhichFunction(name, arity, GetParameters(arity), method);
    Value u;
    int i;
    int line = CurrentLine();

    if (trace && v) {
        if (trace == 1) {
            std::cout << "# Line " << line;
            std::cout << ": Calling " << v->Name() << '(';
        }
        else {
            std::cout << "call('" << v->Name() << "',\n";
        }

        Value* e = GetParameters(arity);

        for (i = 0; i < arity; i++) {
            e[i].Dump(trace);
            if (i != arity - 1)
                std::cout << ',';
        }
        if (trace == 1)
            std::cout << ')' << std::endl;
        else
            std::cout << ");" << std::endl;
    }

    if (v)
        u = v->Execute(arity, GetParameters(arity));

    // Pop parameters

    while (arity--)
        Pop();

    // Push result

    if ((trace || waitmode) && v) {
        if (trace == 1) {
            std::cout << "# Line " << line;
            std::cout << ": " << v->Name() << " returns ";
            u.Dump(trace);
        }

        if (waitmode || trace > 1) {
            u.Sync();
        }

        if (trace > 1) {
            std::cout << "result('" << v->Name() << "',\n";
            u.Dump(trace);
            std::cout << ");";
        }

        if (trace)
            std::cout << std::endl;
    }

    Push(u);
}

Variable* Context::FindGlobal(const char* name)
{
    auto* v = (Variable*)Globals.Find(name);
    if (v)
        return v;
    if (Owner())
        return Owner()->FindGlobal(name);
    return 0;
}

Variable* Context::FindVariable(const char* name)
{
    auto* v = (Variable*)Locals.Find(name);
    if (v)
        return v;
    return FindGlobal(name);
}

Value Context::ImportVariable(const char* name)
{
    Context* c = this;
    auto* v = (Variable*)Locals.Find(name);

    if (v) {
        Error(
            "Cannot import '%s', the name is"
            " already defined at this level",
            name);
        return v->GetValue();
    }

    while (c) {
        Variable* v = c->FindVariable(name);
        if (v) {
            if (v->IsExported())
                Locals.Append(new Variable(name, v));

            return v->GetValue();
        }
        c = c->Owner();
    }

    return Error("Cannot import '%s', variable not found", name);
}

Value Context::ExportVariable(const char* name)
{
    auto* v = (Variable*)Locals.Find(name);

    if (!v)
        return Error("Cannot export '%s', variable not found", name);

    v->Export();
    return v->GetValue();
}

Function* Context::FindFunction(const char* name, bool& nameFound, int arity, Value* arg)
{
    Function* v = 0;

    while ((v = (Function*)Functions.Find(name, v))) {
        nameFound = true;
        if (v->ValidArguments(arity, arg))
            return v;
    }
    if (Owner())
        return Owner()->FindFunction(name, nameFound, arity, arg);
    return 0;
}

Function* Context::FindMethod(const char* name, bool& nameFound, int arity, Value* arg)
{
    Function* v = 0;

    while ((v = (Function*)Methods.Find(name, v))) {
        nameFound = true;
        if (v->ValidArguments(arity, arg))
            return v;
    }

    if (Owner())
        return Owner()->FindMethod(name, nameFound, arity, arg);
    return 0;
}

Function* Context::FindMethod(const char* name, bool& nameFound, int arity, Value* arg, CObject* obj)
{
    if (!obj)
        return 0;

    Context* code[1024];
    int n = 0;
    obj->GetInheritance(code, n);

    if (n > 1024)
        Error("Inheritance is too deep");

    for (int i = 0; i < n; i++) {
        Function* f = code[i]->FindMethod(name, nameFound, arity, arg);
        if (f)
            return f;
    }

    return 0;
}

Function* Context::FindFallback(const char* name, bool& nameFound, int arity, Value* arg)
{
    Function* v = 0;

    while ((v = (Function*)Fallbacks.Find(name, v))) {
        nameFound = true;
        if (v->ValidArguments(arity, arg))
            return v;
    }
    if (Owner())
        return Owner()->FindFallback(name, nameFound, arity, arg);
    return 0;
}

Function* Context::FindHandler(const char* name, int arity, Value* arg)
{
    const char* handler = name;

    while (name) {
        Function* v = 0;
        while ((v = (Function*)(Handlers.Find(name, v)))) {
            if (v->ValidArguments(arity, arg))
                return v;
        }
        name = FallBackHandler(name);
    }

    if (Owner())
        return Owner()->FindHandler(handler, arity, arg);

    return 0;
}

void Context::Store(const char* name, Value x, int arity)
{
    Variable* v = FindVariable(name);
    if (v) {
        v->SetValue(x, arity, GetParameters(arity));
        while (arity--)
            Pop();
    }
    else {
        if (arity == 0)
            Locals.Append(new Variable(name, x));
        else
            Error("Variable not found: %s", name);
    }
}

Value& Context::Fetch(const char* name)
{
    Variable* v = FindVariable(name);

    if (v)
        return v->GetValue();

    char buf[80];
    sprintf(buf, "Variable not found: %s", name);
    return Error(buf);
}

void Context::PrintErrorMessage(const char* msg)
{
    int line = Instruction ? Instruction->line : 0;
    RuntimeError(msg, line);
}

void Context::RuntimeError(const char* msg, int line)
{
    const char* mname = Current->Name();
    if (*msg == '-')
        marslog(LOG_EROR | LOG_PERR, "Line %d in '%s': %s", line, mname, msg + 1);
    else
        marslog(LOG_EROR, "Line %d in '%s': %s", line, mname, msg);
}

Value& Context::Error(const char* msg, ...)
{
    va_list list;
    char buf[1024];
    va_start(list, msg);
    vsprintf(buf, msg, list);
    va_end(list);

    if (Owner())  // Ask the main function to handle the error
        Owner()->SetError(buf);
    else {
        // If it is the main function then print the error message
        SetError(1);
        if (msg)
            this->PrintErrorMessage(buf);
    }

    object = 0;
    static Value e;
    e = Value(1, buf);

    return e;
}

void Context::SetError(const char* msg)
{
    // Function exclusive to the main function
    if (Owner())
        return;  // I am not the main function

    // Set error to myself
    SetError(1);

    // Set error to all contexts
    auto* c = (Context*)Contexts.Head();
    while (c) {
        c->SetError(1);
        c = (Context*)c->Next();
    }

    // Print the error message
    if (msg)
        this->PrintErrorMessage(msg);
}

Context::Context(const char* name, int o) :
    Node(name),
    oo(o)
{
    error = 0;
    argc = 0;
    argv = 0;
    argn = 0;
    Macro = 0;
    inited = 0;
    object = 0;

    if (oo)
        AddGlobal(new Variable(".super", Value(new CList(0))));
}

Context::~Context()
{
    Methods.Empty();
    Functions.Empty();
    Handlers.Empty();
    Steps.Empty();
    Locals.Empty();
    Globals.Empty();
    Contexts.Empty();
}

Value Context::NextParameter(void)
{
    if (argn >= argc)
        return Error("Function was called with too few parameters");
    else
        return argv[argn++];
}

void Context::AddParameter(const char* name)
{
    if (oo) {
        Variable* v = FindGlobal(name);
        if (!v || v->Owner() != this)
            AddGlobal(new Variable(name, Value(0.0)));
    }
    else
        AddLocal(new Variable(name, Value(0.0)));
    Store(name, NextParameter(), 0);
}

CList* Context::GetGlobals()
{
    auto* v = (Variable*)Globals.Head();

    int n = 0;
    while (v) {
        n++;
        v = (Variable*)v->Next();
    }

    auto* c = new CList(n);

    n = 0;
    v = (Variable*)Globals.Head();
    while (v) {
        (*c)[n] = v->GetValue();
        n++;
        v = (Variable*)v->Next();
    }
    return c;
}

void Context::SetGlobals(CList* c)
{
    auto* v = (Variable*)Globals.Head();

    int n = 0;
    while (v) {
        v->SetValue((*c)[n++], 0, 0);
        v = (Variable*)v->Next();
    }
}

CObject* Context::GetObject()
{
    if (object)
        return object;
    if (Owner())
        return Owner()->GetObject();
    return 0;
}
