/***************************** 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 <fstream>
#include <iostream>

#include "fstream_mars_fix.h"

#include "macro.h"

// the 'memory' function does not compile on the Mac
#if !defined(__APPLE__) || !defined(__MACH__)
#define COMPILE_MEMORY_FUNCTION
#endif

#ifdef COMPILE_MEMORY_FUNCTION
#include <malloc.h>
#endif

#include <MvStopWatch.h>


void Context::Dump(int level)
{
    std::cout << "### Dump of context " << Name() << std::endl;

    if (argc)
        std::cout << "# Arguments:\n";

    for (int i = 0; i < argc; i++) {
        std::cout << "# ";
        argv[i].Dump(level);
        std::cout << std::endl;
    }

    auto* v = (Variable*)Locals.Head();

    if (v)
        std::cout << "# Local variables:\n";

    while (v) {
        if (v->Name()[0] != '.') {
            std::cout << "# ";
            v->Dump(level);
            std::cout << std::endl;
        }
        v = (Variable*)v->Next();
    }

    v = (Variable*)Globals.Head();
    if (v)
        std::cout << "# Global variables:\n";

    while (v) {
        std::cout << "# ";
        v->Dump(level);
        std::cout << std::endl;
        v = (Variable*)v->Next();
    }
}

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

class TraceFunction : public Function
{
public:
    TraceFunction(const char* n) :
        Function(n, 1, tnumber)
    {
        info = "Sets program execution tracing on/off";
    }
    virtual Value Execute(int, Value*);
};

Value TraceFunction::Execute(int, Value* arg)
{
    int n = Context::Trace();
    int m;

    arg[0].GetValue(m);
    Context::Trace(m);

    return Value(n);
}

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

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

Value TraceFileFunction::Execute(int, Value* arg)
{
    const char* m;
    arg[0].GetValue(m);

    auto* f = new std::ofstream(m);

    std::cout.rdbuf(f->rdbuf());  // Memory leeak here

    return Value(m);
}

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

class TimerFunction : public Function
{
public:
    TimerFunction(const char* n) :
        Function(n, 1, tnumber) {}
    virtual Value Execute(int, Value*);
};

Value TimerFunction::Execute(int, Value* arg)
{
    int m;
    arg[0].GetValue(m);

    if (m) {
        start_timer();
        return Value();
    }
    else {
        char info[1024];
        stop_timer(info);
        if (*info)
            return Value(info);
        else
            return Value("Time interval to small (less than 1 sec)");
    }
}
//=============================================================================

class StopWatchFunction : public Function
{
public:
    StopWatchFunction(const char* n, int a) :
        Function(n),
        action(a)
    {
        info = "Provides stopwatch timings";
    }
    virtual int ValidArguments(int arity, Value* arg);
    virtual Value Execute(int, Value*);

private:
    int action;
    static MvStopWatch* watch;
};

MvStopWatch* StopWatchFunction::watch = 0;

int StopWatchFunction::ValidArguments(int arity, Value* arg)
{
    if (arity > 1)
        return false;
    if (action != 4 && arg[0].GetType() != tstring)
        return false;
    return true;
}

Value StopWatchFunction::Execute(int, Value* arg)
{
    const char* txt;
    arg[0].GetValue(txt);

    switch (action) {
        case 1:  //-- stopwatch_start
            if (watch) {
                marslog(LOG_WARN, "stopwatch_start - watch already running, replace old watch!");
                delete watch;
            }
            watch = new MvStopWatch(txt);
            break;

        case 2:  //-- stopwatch_laptime
            if (!watch) {
                marslog(LOG_WARN, "stopwatch_laptime - watch not running, starting now!");
                watch = new MvStopWatch("tictac");
            }
            else
                watch->lapTime(txt);
            break;

        case 3:  //-- stopwatch_reset
            if (!watch) {
                marslog(LOG_WARN, "stopwatch_reset - watch not running, starting now!");
                watch = new MvStopWatch("tictac");
            }
            else
                watch->reset(txt);
            break;

        case 4:  //-- stopwatch_stop
            if (watch) {
                delete watch;
                watch = 0;
            }
            else
                marslog(LOG_WARN, "stopwatch_stop - no watch running!");
            break;
    }
    return Value("");
}

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

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


Value DumpFunction::Execute(int arity, Value* arg)
{
    if (arity) {
        for (int i = 0; i < arity; i++) {
            std::cout << "dump : ";
            arg[i].Dump(1);
            std::cout << std::endl;
        }
    }
    else {
        auto* c = (Context*)Context::Instruction->Owner();
        while (c) {
            c->Dump(1);
            c = (Context*)c->Owner();
        }
    }

    return Value(0.0);
}

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

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

/* extern "C" void print_alloc(FILE*,int,int); */

Value MemFunction::Execute(int, Value*)
{
#ifdef COMPILE_MEMORY_FUNCTION
    static long last = 0;
    struct mallinfo minfo = mallinfo();

    long used = minfo.uordblks + minfo.usmblks;

    request* r = empty_request(0);

    set_value(r, "total", "%d", minfo.arena);
    set_value(r, "used", "%d", used);
    set_value(r, "free", "%d", minfo.arena - used);
    set_value(r, "delta", "%d", used - last);

    last = used;

    return Value(r);
#endif
    return Value();
}


static void install(Context* c)
{
    c->AddFunction(new DumpFunction("dump"));
    c->AddFunction(new TraceFunction("trace"));
    c->AddFunction(new TraceFileFunction("tracefile"));
    c->AddFunction(new TimerFunction("timer"));
    c->AddFunction(new MemFunction("memory"));
    c->AddFunction(new StopWatchFunction("stopwatch_start", 1));
    c->AddFunction(new StopWatchFunction("stopwatch_laptime", 2));
    c->AddFunction(new StopWatchFunction("stopwatch_reset", 3));
    c->AddFunction(new StopWatchFunction("stopwatch_stop", 4));
}

static Linkage Link(install);
