/***************************** 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 "opcodes.h"
//#include "values.h"

Step* OpPush::Execute(void)
{
    Context* s = Owner();

    marslog(LOG_DBUG, "Push %s", Name());

    switch (type) {
        case PUSH_TIME: {
            int h = 0, m = 0, z = 0;
            sscanf(Name(), "%d:%d:%d", &h, &m, &z);
            s->Push(Value((h * 3600 + m * 60 + z) / 86400.0));
        } break;

        case PUSH_DATE: {  // sgi needs the {
            Date d(Name());
            s->Push(Value(d));
        } break;


        case PUSH_NUMBER:
            s->Push(Value(atof(Name())));
            break;

        case PUSH_NEGATIVE:
            s->Push(Value(-atof(Name())));
            break;

        case PUSH_IDENT:
            s->Push(s->Fetch(Name()));
            break;

        case PUSH_STRING:
            s->Push(Value(Name()));
            break;

        case PUSH_NIL:
            s->Push(Value());
            break;
    }
    return Next();
}

Step* OpPop::Execute(void)
{
    Context* s = Owner();
    s->Pop();
    return Next();
}

Step* OpCall::Execute(void)
{
    Context* s = Owner();
    marslog(LOG_DBUG, "Call %s %d", Name(), Arity);
    s->CallFunction(Name(), Arity);
    return Next();
}

Step* OpGoto::Execute(void)
{
    marslog(LOG_DBUG, "Goto");
    return Branch;
}


bool OpTest::Pass(Value& v)  // determines whether the given value passes for true/false
{
    double d;

    switch (v.GetType()) {
        case tnil:
            d = 0;
            break;

        case tnumber:
            v.GetValue(d);
            break;

        case tlist:  // a list is false if any element is false
            CList* list;
            v.GetValue(list);
            if (list->Count()) {
                bool all_pass = true;
                int count = list->Count();
                for (int i = 0; i < count; i++) {
                    all_pass = all_pass && Pass((*list)[i]);  // recursive call to test the element

                    if (!all_pass)  // short-circuit
                        break;
                }

                d = all_pass;
            }
            else
                d = 0;  // empty list means 'false'
            break;

        default: /* all other are true */
            d = 1;
            break;

            // Maybe compare with []
    }

    return (d != 0);
}


Step* OpTest::Execute(void)
{
    Value v = Owner()->Pop();

    marslog(LOG_DBUG, "Test");

    if (Pass(v))
        return Next();
    else
        return OpGoto::Execute();
}

Step* OpReturn::Execute(void)
{
    marslog(LOG_DBUG, "Return");
    return 0;  // No more lines
}

Step* OpStore::Execute(void)
{
    Context* s = Owner();
    marslog(LOG_DBUG, "Store %s [%d]", Name(), Count);
    s->Store(Name(), s->Pop(), Count);
    return Next();
}

Step* OpParam::Execute(void)
{
    Context* s = Owner();

    marslog(LOG_DBUG, "Param %s", Name());
    // Make sure argument masks global variables

    // s->AddLocal(new Variable(Name,Value(0.0)));
    // s->Store(Name,s->NextParameter(),0);
    s->AddParameter(Name());
    return Next();
}

Value UserFunction::Execute(int arity, Value* a)
{
    return Address->Run(0, arity, a);
}

Value ObjectFunction::Execute(int arity, Value* a)
{
    Value v = Address->Run(0, arity, a);
    // What shall we do with v?
    return Value(new CObject(Name(), Address));
}

void Context::AddExtern(const char* nam, const char* cmd)
{
    auto* c = new Context(nam, 0);
    auto* f = new ExternFunction(nam, c);
    AddContext(c);
    AddFunction(f);
    f->SetCommand(cmd, 0);
}

extern "C" {

void update_branch(void* a, void* b)
{
    auto* g = (OpGoto*)a;
    auto* s = (OpGoto*)b;
    g->SetBranch(s);
    Context::Current->NewStep(0);
    if (mars.debug)
        printf("---- update branch from %d to %d\n", g->line, g->line);
}


void new_context(const char* name, int handler, int user, int oo)
{
    auto* c = new Context(name, oo);
    Function* f;

    if (oo)
        f = new ObjectFunction(name, c);
    else if (user)
        f = new UserFunction(name, c);
    else
        f = new ExternFunction(name, c);

    c->Macro = f;

    Context::Current->AddContext(c);

    if (handler)
        Context::Current->AddHandler(f);
    else {
        if (user)  // user functions get priority over built-in functions
            Context::Current->AddFunctionToFront(f);
        else
            Context::Current->AddFunction(f);
    }

    Context::Current = c;

    if (mars.debug)
        printf("---- new_context %s\n", name);
}


void start_global(char* name)
{
    Context::Current->AddGlobal(new Variable(name, Value(0.0)));

    // Select the Init context

    Context* c = Context::Current->FindContext(Context::InitGlobals);
    if (c)
        Context::Current = c;
    else
        new_context(Context::InitGlobals, 0, 1, 0);

    if (mars.debug)
        printf("---- stat_global %s\n", name);
}

void end_global(char* s)
{
    // Restore the current context
    Context::Current = (Context*)Context::Current->Owner();
    if (mars.debug)
        printf("---- end_global %s\n", s);
}
void argument_count(int n)
{
    auto* f = (UserFunction*)Context::Current->Macro;
    if (n == 0)
        f->SetTypes(0, 0);
    if (mars.debug)
        printf("---- argument_count %d\n", n);
}

void extern_command(char* s, char* p)
{
    auto* f = (ExternFunction*)Context::Current->Macro;
    f->SetCommand(s, p);
    if (mars.debug)
        printf("---- extern_command %s %s\n", s, p);
}

void argument_type(char* s)
{
    // TODO: find a way to use zzerror
    //extern int zzerror(const char*);
    auto* f = (UserFunction*)Context::Current->Macro;

    vtype t = Value::NameType(s);
    if (t == tnone) {
//        zzerror("Bad argumenent type");
    } else {
        f->AddType(t);
    }

    if (mars.debug)
        printf("---- argument_type %s\n", s);
}

void end_context(char* s)
{
    Context::Current = (Context*)Context::Current->Owner();
    if (mars.debug)
        printf("---- end_context %s\n", s);
}

void* new_code(int opcode, char* carg, int iarg, int line)
{
    Step* s = nullptr;

    switch (opcode) {
        case OP_NOP:
            s = new Step;
            break;
        case OP_PUSH:
            s = new OpPush(carg, iarg);
            break;
        case OP_POP:
            s = new OpPop;
            break;
        case OP_CALL:
            s = new OpCall(carg, iarg);
            break;
        case OP_GOTO:
            s = new OpGoto;
            break;
        case OP_TEST:
            s = new OpTest(carg);
            break;
        case OP_RETURN:
            s = new OpReturn;
            break;
        case OP_STORE:
            s = new OpStore(carg, iarg);
            break;
        case OP_PARAM:
            s = new OpParam(carg);
            break;
        case OP_CONVERT:
            s = new OpConvert(carg, iarg);
            break;
        case OP_CONVERT_FOR:
            s = new OpConvertFor(carg);
            break;
        case OP_CONVERT_LOOP:
            s = new OpConvertLoop(carg);
            break;
        case OP_COMMENT:
            s = new OpComment(carg);
            break;
        case OP_CONVERT_FN_START:
            s = new OpConvertFnStart(carg);
            break;
        default:
            break;
    }

    if (s) {
        if (mars.debug) {
            printf("%04d ", line + 1);
            s->Print();
        }

        s->line = line + 1;
        s->ref = opcode;

        Context::Current->AddStep(s);
    }
    return s;
}

}  // extern C
