// PyRun.cpp --- 
// 
// Filename: PyRun.cpp
// Description: 
// Author: subha
// Maintainer: 
// Created: Sat Oct 11 14:47:22 2014 (+0530)
// Version: 
// Last-Updated: Fri Jun 19 18:56:06 2015 (-0400)
//           By: Subhasis Ray
//     Update #: 15
// URL: 
// Keywords: 
// Compatibility: 
// 
// 

// Commentary: 
// 
// 
// 
// 

// Change log:
// 
// 
// 
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 3, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING.  If not, write to
// the Free Software Foundation, Inc., 51 Franklin Street, Fifth
// Floor, Boston, MA 02110-1301, USA.
// 
// 

// Code:

#include "Python.h"
#include "../basecode/header.h"
#include "PyRun.h"

const int PyRun::RUNPROC = 1;
const int PyRun::RUNTRIG = 2;
const int PyRun::RUNBOTH = 0;

static SrcFinfo1< double >* outputOut()
{
    static SrcFinfo1< double > outputOut( "output",
                                          "Sends out the value of local variable called `output`. Thus, you can"
                                          " have Python statements which compute some value and assign it to the"
                                          " variable called `output` (which is defined at `reinit` call). This"
                                          " will be sent out to any target connected to the `output` field.");
    return &outputOut;
}


const Cinfo * PyRun::initCinfo()
{
    static ValueFinfo< PyRun, string > runstring(
        "runString",
        "String to be executed at each time step.",
        &PyRun::setRunString,
        &PyRun::getRunString);

    static ValueFinfo< PyRun, string > initstring(
        "initString",
        "String to be executed at initialization (reinit).",
        &PyRun::setInitString,
        &PyRun::getInitString);
    static ValueFinfo< PyRun, string > inputvar(
        "inputVar",
        "Name of local variable in which input balue is to be stored. Default"
        " is `input_` (to avoid conflict with Python's builtin function"
        " `input`).",
        &PyRun::setInputVar,
        &PyRun::getInputVar);

    static ValueFinfo< PyRun, string > outputvar(
        "outputVar",
        "Name of local variable for storing output. Default is `output`",
        &PyRun::setOutputVar,
        &PyRun::getOutputVar);

    static ValueFinfo< PyRun, int > mode(
        "mode",
        "Flag to indicate whether runString should be executed for both trigger and process, or one of them",
        &PyRun::setMode,
        &PyRun::getMode);

    // static ValueFinfo< PyRun, PyObject* > globals(
    //     "globals",
    //     "Global environment dict",
    //     &PyRun::setGlobals,
    //     &PyRun::getGlobals);

    // static ValueFinfo< PyRun, PyObject* > locals(
    //     "locals",
    //     "Local environment dict",
    //     &PyRun::setLocals,
    //     &PyRun::getLocals);

    static DestFinfo trigger(
        "trigger",
        "Executes the current runString whenever a message arrives. It stores"
        " the incoming value in local variable named"
        " `input_`, which can be used in the"
        " `runString` (the underscore is added to avoid conflict with Python's"
        " builtin function `input`). If debug is True, it prints the input"
        " value.",
        new EpFunc1< PyRun, double >(&PyRun::trigger));
    
    static DestFinfo run(
        "run",
        "Runs a specified string. Does not modify existing run or init strings.",
        new EpFunc1< PyRun, string >(&PyRun::run));

    static DestFinfo process(
        "process",
        "Handles process call. Runs the current runString.",
        new ProcOpFunc< PyRun >(&PyRun::process));

    static DestFinfo reinit(
        "reinit",
        "Handles reinit call. Runs the current initString.",
        new ProcOpFunc< PyRun >( &PyRun::reinit ));
    
    static Finfo * processShared[] = { &process, &reinit };
    static SharedFinfo proc(
        "proc",
        "This is a shared message to receive Process messages "
        "from the scheduler objects."
        "The first entry in the shared msg is a MsgDest "
        "for the Process operation. It has a single argument, "
        "ProcInfo, which holds lots of information about current "
        "time, thread, dt and so on. The second entry is a MsgDest "
        "for the Reinit operation. It also uses ProcInfo. ",
        processShared, sizeof( processShared ) / sizeof( Finfo* ));

    static Finfo * pyRunFinfos[] = {
        &runstring,
        &initstring,
        &mode,
        &inputvar,
        &outputvar,
        &trigger,
        outputOut(),
        // &locals,
        // &globals,
        &run,
        &proc,
    };

    static string doc[] = {
        "Name", "PyRun",
        "Author", "Subhasis Ray",
        "Description", "Runs Python statements from inside MOOSE."};
    static Dinfo< PyRun > dinfo;
    static Cinfo pyRunCinfo(
        "PyRun",
        Neutral::initCinfo(),
        pyRunFinfos,
        sizeof(pyRunFinfos) / sizeof(Finfo*),
        &dinfo,
        doc,
        sizeof(doc) / sizeof(string));
    return &pyRunCinfo;
}

static const Cinfo * pyRunCinfo = PyRun::initCinfo();

PyRun::PyRun():mode_(0), initstr_(""), runstr_(""),
               globals_(0), locals_(0),
               runcompiled_(0), initcompiled_(0),
               inputvar_("input_"), outputvar_("output")
{
    locals_ = PyDict_New();
    if (!locals_){
        cerr << "Could not initialize locals dict" << endl;
        return;
    }
    PyObject * value = PyFloat_FromDouble(0.0);
    if (!value && PyErr_Occurred()){
        PyErr_Print();
        return;
    }
    if (PyDict_SetItemString(locals_, inputvar_.c_str(), value)){
        PyErr_Print();
    }    
}

PyRun::~PyRun()
{
    Py_XDECREF(globals_);
    Py_XDECREF(locals_);
}

void PyRun::setRunString(string statement)
{
    runstr_ = statement;
}

string PyRun::getRunString() const
{
    return runstr_;
}

void PyRun::setInitString(string statement)
{
    initstr_ = statement;
}

string PyRun::getInitString() const
{
    return initstr_;
}

void PyRun::setInputVar(string name)
{
    PyDict_DelItemString(locals_, inputvar_.c_str());
    inputvar_ = name;
}

string PyRun::getInputVar() const
{
    return inputvar_;
}

void PyRun::setOutputVar(string name)
{
    PyDict_DelItemString(locals_, outputvar_.c_str());
    outputvar_ = name;
}

string PyRun::getOutputVar() const
{
    return outputvar_;
}

void PyRun::setMode(int flag)
{
    mode_ = flag;
}

int PyRun::getMode() const
{
    return mode_;
}

void PyRun::trigger(const Eref& e, double input)
{
    if (!runcompiled_){
        return;
    }
    if (mode_ == 1){
        return;
    }
    
    PyObject * value = PyDict_GetItemString(locals_, inputvar_.c_str());
    if (value){
        Py_DECREF(value);
    }
    value = PyFloat_FromDouble(input);
    if (!value && PyErr_Occurred()){
        PyErr_Print();
    }
    if (PyDict_SetItemString(locals_, inputvar_.c_str(), value)){
        PyErr_Print();
    }
    PyEval_EvalCode(runcompiled_, globals_, locals_);
    if (PyErr_Occurred()){
        PyErr_Print ();
    }
    value = PyDict_GetItemString(locals_, outputvar_.c_str());
    if (value){
        double output = PyFloat_AsDouble(value);
        if (PyErr_Occurred()){
            PyErr_Print ();
        } else {
            outputOut()->send(e, output);
        }
    }
}

void PyRun::run(const Eref&e, string statement)
{
    PyRun_SimpleString(statement.c_str());
    PyObject * value = PyDict_GetItemString(locals_, outputvar_.c_str());
    if (value){
        double output = PyFloat_AsDouble(value);
        if (PyErr_Occurred()){
            PyErr_Print ();
        } else {
            outputOut()->send(e, output);
        }
    }
}

void PyRun::process(const Eref & e, ProcPtr p)
{
    // PyRun_String(runstr_.c_str(), 0, globals_, locals_);
    // PyRun_SimpleString(runstr_.c_str());
    if (!runcompiled_ || mode_ == 2){
        return;
    }
    PyEval_EvalCode(runcompiled_, globals_, locals_);
    if (PyErr_Occurred()){
        PyErr_Print ();
    }
    PyObject * value = PyDict_GetItemString(locals_, outputvar_.c_str());
    if (value){
        double output = PyFloat_AsDouble(value);
        if (PyErr_Occurred()){
            PyErr_Print ();
        } else {
            outputOut()->send(e, output);
        }
    }
}

/**
   This is derived from:
   http://effbot.org/pyfaq/how-do-i-tell-incomplete-input-from-invalid-input.htm
 */
void handleError(bool syntax)
{
    PyObject *exc, *val, *trb;
    char * msg;
    
    if (syntax && PyErr_ExceptionMatches (PyExc_SyntaxError)){           
        PyErr_Fetch (&exc, &val, &trb);        /* clears exception! */
        
        if (PyArg_ParseTuple (val, "sO", &msg, &trb) &&
            !strcmp (msg, "unexpected EOF while parsing")){ /* E_EOF */            
            Py_XDECREF (exc);
            Py_XDECREF (val);
            Py_XDECREF (trb);
        } else {                                  /* some other syntax error */
            PyErr_Restore (exc, val, trb);
            PyErr_Print ();
        }
    } else {                                     /* some non-syntax error */
        PyErr_Print ();
    }                
}

void PyRun::reinit(const Eref& e, ProcPtr p)
{
    PyObject * main_module;
    if (globals_ == NULL){
        main_module = PyImport_AddModule("__main__");        
        globals_ = PyModule_GetDict(main_module);
        Py_XINCREF(globals_);
    }
    if (locals_ == NULL){
        locals_ = PyDict_New();
        if (!locals_){
            cerr << "Could not initialize locals dict" << endl;            
        }
    }
    initcompiled_ = (PYCODEOBJECT*)Py_CompileString(
        initstr_.c_str(),
        get_program_name().c_str(),
        Py_file_input);
    if (!initcompiled_){
        cerr << "Error compiling initString" << endl;
        handleError(true);
    } else {
        PyEval_EvalCode(initcompiled_, globals_, locals_);
        if (PyErr_Occurred()){
            PyErr_Print ();
        }
    }
    runcompiled_ = (PYCODEOBJECT*)Py_CompileString(
        runstr_.c_str(),
        get_program_name().c_str(),
        Py_file_input);
    if (!runcompiled_){
        cerr << "Error compiling runString" << endl;
        handleError(true);
    } else {
        PyEval_EvalCode(runcompiled_, globals_, locals_);
        if (PyErr_Occurred()){
            PyErr_Print ();
        }
    }
}


// 
// PyRun.cpp ends here