/*****************************************************************************
*
* Copyright (c) 2000 - 2017, Lawrence Livermore National Security, LLC
* Produced at the Lawrence Livermore National Laboratory
* LLNL-CODE-442911
* All rights reserved.
*
* This file is  part of VisIt. For  details, see https://visit.llnl.gov/.  The
* full copyright notice is contained in the file COPYRIGHT located at the root
* of the VisIt distribution or at http://www.llnl.gov/visit/copyright.html.
*
* Redistribution  and  use  in  source  and  binary  forms,  with  or  without
* modification, are permitted provided that the following conditions are met:
*
*  - Redistributions of  source code must  retain the above  copyright notice,
*    this list of conditions and the disclaimer below.
*  - Redistributions in binary form must reproduce the above copyright notice,
*    this  list of  conditions  and  the  disclaimer (as noted below)  in  the
*    documentation and/or other materials provided with the distribution.
*  - Neither the name of  the LLNS/LLNL nor the names of  its contributors may
*    be used to endorse or promote products derived from this software without
*    specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT  LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE
* ARE  DISCLAIMED. IN  NO EVENT  SHALL LAWRENCE  LIVERMORE NATIONAL  SECURITY,
* LLC, THE  U.S.  DEPARTMENT OF  ENERGY  OR  CONTRIBUTORS BE  LIABLE  FOR  ANY
* DIRECT,  INDIRECT,   INCIDENTAL,   SPECIAL,   EXEMPLARY,  OR   CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR
* SERVICES; LOSS OF  USE, DATA, OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
* CAUSED  AND  ON  ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT
* LIABILITY, OR TORT  (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY  WAY
* OUT OF THE  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*****************************************************************************/

// ************************************************************************* //
//  File: avtProgrammableOpFilter.C
// ************************************************************************* //

#include <avtProgrammableOpFilter.h>
#include <avtPythonFilterEnvironment.h>
#include <Python.h>
#include <vtkDataSet.h>
#include <vtkDoubleArray.h>
//#include <avtRFilter.h>
#include <vector>
#include <map>
#include <ProgrammableOperation.h>
#include <avtOriginatingSource.h>
#include <avtProgrammableOperation.h>

#include <vtkDataArray.h>
#include <vtkPointData.h>
#include <vtkCellData.h>

#include <avtParallel.h>
//#include <avtTECAProgrammableOperation.h>

struct ScriptData
{
    //avtTecaProgrammableOperation* tfilter;
    //avtRFilter* rfilter;
    avtProgrammableOperation* sfilter;
    std::map<std::string,ProgrammableOperation*> operations;
};

PyObject *
visit_functions(PyObject *self, PyObject *args);

static PyMethodDef myMethods[] =
{
    {"visit_functions",visit_functions},
    {NULL,NULL}
};

// ****************************************************************************
//  Method: avtProgrammableOpFilter constructor
//
//  Programmer: hari -- generated by xml2avt
//  Creation:   Wed Jan 16 18:16:39 PST 2013
//
// ****************************************************************************
/*
PyMethodDef conv(const char* name, PyObject* (*func)(PyObject*,PyObject*))
{
    PyMethodDef def;
    def.ml_name = name;
    def.ml_meth = func;
    def.ml_flags = 0;
    def.ml_doc = 0;
    return def;
}
*/
avtProgrammableOpFilter::avtProgrammableOpFilter()
:pyEnv(NULL)
{

    /// initialize data
    scriptData = new ScriptData();
    pyEnv = new avtPythonFilterEnvironment();

    /// initialize environment
    if(!pyEnv->Initialize())
        cout << "Failed to initialize python environment.." << endl;

    /// initialize internal functions..
    PyObject* module = Py_InitModule("visit_internal_funcs",myMethods);
    PyObject* script_object = PyCObject_FromVoidPtr(this,NULL);
    PyModule_AddObject(module,"_C_API",script_object);

    /// execute state
    std::ostringstream script;
    script  << "import sys\n"
            << "import os\n"
            << "import json\n"
            << "from flow import *\n"
            << "from flow.filters import script_pipeline\n"
            << "import visit_internal_funcs\n";

    /// initialize environment
    if(!pyEnv->Interpreter()->RunScript(script.str()))
        cout << "Initialization Script Failed.." << endl;

#ifdef HAVE_LIB_R

    /// register VisIt scriptable functions..
    std::ostringstream renv;

    renv << "import rpy2, rpy2.robjects\n"
         << "rpy2.robjects.r('visit_internal_funcs = new.env()')\n";

    if(!pyEnv->Interpreter()->RunScript(renv.str()))
        cout << "R Initialization Script Failed.." << endl;

    //scriptData->rfilter = dynamic_cast<avtRFilter*>(avtRFilter::Create());
    //scriptData->rfilter->RegisterOperations(this);

#endif

    scriptData->sfilter = new avtProgrammableOperation();
    scriptData->sfilter->RegisterOperations(this);

    //scriptData->tfilter = new avtTecaProgrammableOperation();
    //scriptData->tfilter->RegisterOperations(this);
}

// ****************************************************************************
//  Method: avtProgrammableOpFilter destructor
//
//  Programmer: hari -- generated by xml2avt
//  Creation:   Wed Jan 16 18:16:39 PST 2013
//
//  Modifications:
//
// ****************************************************************************

avtProgrammableOpFilter::~avtProgrammableOpFilter()
{
    if(scriptData)
    {
        delete scriptData->sfilter;
        //delete scriptData->rfilter;
        //delete scriptData->tfilter;
        delete scriptData;
    }
    if(pyEnv)
        delete pyEnv;
}


void
avtProgrammableOpFilter::Register(ProgrammableOperation *op)
{
    ///
    std::string name;
    stringVector args;
    std::vector<ProgrammableOperation::ScriptType> argtypes;

    op->getSignature(name,args,argtypes);

    //std::cout << " registering: " << name << std::endl;
    /// do not register blank..
    if(name == "") return;

    scriptData->operations[name] = op;

    /// create a definition in the modules..
    std::ostringstream cast_to_numpy;
    std::string i_argstring = "";
    std::string o_argstring = "";

    for(size_t i = 0; i < args.size(); ++i)
    {
        std::string arg = args[i];
        i_argstring += arg + (i == args.size() - 1 ? "" : ",");

        /// convert numpy to vtk...
        if(argtypes[i] == ProgrammableOperation::VTK_DATA_ARRAY_TYPE)
        {
            //convert to 1D..
            cast_to_numpy << "    if not isinstance(" << args[i] << ", vtk.vtkAbstractArray):\n"
                          << "        " << args[i] << "= numpy.ascontiguousarray(" << args[i] << ")\n"
                          << "        _shape = " << args[i] << ".shape\n"
                          << "        " << args[i] << "_tmp = vtk.util.numpy_support.numpy_to_vtk(numpy.ravel(" << args[i] << "))\n"
                          << "        " << args[i] << "_in = [list(_shape), " << args[i] << "_tmp]\n"
                          << "    else:\n"
                          << "        " << args[i] << "_in = [[" <<args[i] << ".GetDataSize()], " << args[i] << "]\n";
            o_argstring += arg + std::string("_in");
        }
        else
        {
            o_argstring += arg;
        }

        o_argstring += (i == args.size() - 1 ? "" : ",");
    }

    std::ostringstream str;

    str << "def " << name << "(" << i_argstring << "):\n";
    str << "    import vtk,vtk.util.numpy_support\n";
    if(cast_to_numpy.str().size() > 0)
        str << cast_to_numpy.str();

    if(o_argstring.size() == 0)
        str << "    res = visit_internal_funcs.visit_functions('" << name << "')\n";
    else
        str << "    res = visit_internal_funcs.visit_functions('" << name << "',(" << o_argstring << "))\n";
    str << "    if isinstance(res,list) and len(res) == 2 and isinstance(res[0],list) and isinstance(res[1],vtk.vtkAbstractArray):\n"
        << "        res_shape = tuple(res[0])\n"
        << "        res = vtk.util.numpy_support.vtk_to_numpy(res[1])\n"
        << "        res.shape = res_shape\n";
    str << "    return res\n";

    /// convert from vtk to numpy if needed..

    str << "sys.modules['visit_internal_funcs'].__dict__['"
        << name << "'] = " << name << "\n";

    /// if R is there..
    /// register r version of same python definition..
#ifdef HAVE_LIB_R

    str << "import rpy2\n"
        << "import rpy2.robjects as robjects\n"
        << "import rpy2.robjects.numpy2ri\n"
        << "rpy2.robjects.numpy2ri.activate()\n"
        << "import rpy2.rinterface as ri\n"
        << "import visit_internal_funcs\n"
        << "import numpy\n"
        << "def _r_" << name << "(" << i_argstring << "):\n"
        << "    def my_ri2py(obj):\n"
        << "        res = robjects.default_ri2py(obj)\n"
        << "        if isinstance(res, robjects.Vector) and (len(res) == 1):\n"
        << "            return res[0]\n"
        << "        return numpy.asarray(res)\n"
        << "    import vtk,vtk.util.numpy_support\n";

    for(size_t i = 0; i < args.size(); ++i)
    {
        if( argtypes[i] == ProgrammableOperation::BOOL_VECTOR_TYPE ||
            argtypes[i] == ProgrammableOperation::INT_VECTOR_TYPE ||
            argtypes[i] == ProgrammableOperation::LONG_VECTOR_TYPE ||
            argtypes[i] == ProgrammableOperation::FLOAT_VECTOR_TYPE ||
            argtypes[i] == ProgrammableOperation::DOUBLE_VECTOR_TYPE ||
            argtypes[i] == ProgrammableOperation::STRING_VECTOR_TYPE ||
            argtypes[i] == ProgrammableOperation::VARIANT_VECTOR_TYPE)
        {
            str << "    " << args[i] << " = numpy.asarray(" << args[i] << ").tolist()\n";
        }
        else if(argtypes[i] == ProgrammableOperation::VTK_DATA_ARRAY_TYPE)
        {
            /// convert from R array to Numpy Array then collapse multi dimensional array
            str << "    " << args[i] << " = numpy.ascontiguousarray(" << args[i] << ")\n"
                << "    _shape = " << args[i] << ".shape\n"
                << "    " << args[i] << "_tmp = vtk.util.numpy_support.numpy_to_vtk(numpy.ravel(" << args[i] << "))\n"
                << "    " << args[i] << "_in = [list(_shape), " << args[i] << "_tmp]\n";
        }
        else {
            str << "    " << args[i] << " = my_ri2py(" << args[i] << ")\n";
        }
    }

    if(o_argstring.size() == 0)
        str << "    res = visit_internal_funcs.visit_functions('" << name << "')\n";
    else
        str << "    res = visit_internal_funcs.visit_functions('" << name << "',(" << o_argstring << "))\n";

    str << "    if isinstance(res,list) and len(res) == 2 and isinstance(res[0],list) and isinstance(res[1],vtk.vtkAbstractArray):\n"
        << "        res_shape = tuple(res[0])\n"
        << "        res = vtk.util.numpy_support.vtk_to_numpy(res[1])\n"
        << "        res.shape = res_shape\n";

    str << "    if isinstance(res,vtk.vtkAbstractArray) :\n"
        << "        res = vtk.util.numpy_support.vtk_to_numpy(res)\n";

    str << "    return rpy2.robjects.default_py2ro(res)\n"

         << "sys.modules['visit_internal_funcs'].__dict__['_r_"
         << name << "'] = _r_" << name << "\n"

         << "_rxp_" << name << "= ri.rternalize(visit_internal_funcs._r_" << name << ")\n"
         << "ri.globalenv['" << name << "'] = _rxp_" << name << "\n"
         << "rpy2.robjects.r('assign(\"" << name << "\"," << name << ",visit_internal_funcs)')\n";

#endif
    //std::cout << str.str() << std::endl;
    //std::cout << "--------------------------------" << std::endl;

    if(!pyEnv->Interpreter()->RunScript(str.str()))
        std::cerr << "function : " << name << " registration failed" << std::endl;
}
// ****************************************************************************
//  Method:  avtProgrammableOpFilter::Create
//
//  Programmer: hari -- generated by xml2avt
//  Creation:   Wed Jan 16 18:16:39 PST 2013
//
// ****************************************************************************

avtFilter *
avtProgrammableOpFilter::Create()
{
    return new avtProgrammableOpFilter();
}


// ****************************************************************************
//  Method:      avtProgrammableOpFilter::SetAtts
//
//  Purpose:
//      Sets the state of the filter based on the attribute object.
//
//  Arguments:
//      a        The attributes to use.
//
//  Programmer: hari -- generated by xml2avt
//  Creation:   Wed Jan 16 18:16:39 PST 2013
//
// ****************************************************************************

void
avtProgrammableOpFilter::SetAtts(const AttributeGroup *a)
{
    atts = *(const ProgrammableOpAttributes*)a;
}


// ****************************************************************************
//  Method: avtProgrammableOpFilter::Equivalent
//
//  Purpose:
//      Returns true if creating a new avtProgrammableOpFilter with the given
//      parameters would result in an equivalent avtProgrammableOpFilter.
//
//  Programmer: hari -- generated by xml2avt
//  Creation:   Wed Jan 16 18:16:39 PST 2013
//
// ****************************************************************************

bool
avtProgrammableOpFilter::Equivalent(const AttributeGroup *a)
{
    return (atts == *(ProgrammableOpAttributes*)a);
}


// ****************************************************************************
//  Method: avtProgrammableOpFilter::ExecuteData
//
//  Purpose:
//      Sends the specified input and output through the Script filter.
//
//  Arguments:
//      in_ds      The input dataset.
//      <unused>   The domain number.
//      <unused>   The label.
//
//  Returns:       The output dataset.
//
//  Programmer: hari -- generated by xml2avt
//  Creation:   Wed Jan 16 18:16:39 PST 2013
//
// ****************************************************************************

vtkDataSet *
avtProgrammableOpFilter::ExecuteData(vtkDataSet *in_ds, int d, std::string s)
{
    //std::cout << PAR_Rank() << " ExecuteData is called" << std::endl;
    if(!SetupFlowWorkspace())
        return in_ds;

    PyObject* py_ds_in = pyEnv->WrapVTKObject(in_ds,"vtkDataSet");
    pyEnv->Interpreter()->SetGlobalObject(py_ds_in,"ds_in");
    pyEnv->Interpreter()->SetGlobalObject(PyString_FromString(primaryVariable.c_str()),
                                          "pvar");
    pyEnv->Interpreter()->SetGlobalObject(PyInt_FromLong(d),
                                          "ds_domain");
    pyEnv->Interpreter()->SetGlobalObject(PyInt_FromLong(PAR_Rank()),
                                          "mpi_rank");
    pyEnv->Interpreter()->SetGlobalObject(PyInt_FromLong(PAR_Size()),
                                          "mpi_size");
    std::string script = "";
    script += "ctx.init(ds_in,pvar)\n";
    script += "w.registry_add(':mesh',ds_in)\n";
    script += "w.registry_add(':pvar',pvar)\n";
    script += "w.registry_add(':mesh_domain',ds_domain)\n";
    script += "w.registry_add(':mpi_rank',mpi_rank)\n";
    script += "w.registry_add(':mpi_size',mpi_size)\n";
    script += "res = w.execute()\n";
    if(!pyEnv->Interpreter()->RunScript(script))
    {
        cout << "ExecuteData Script Failed.." << endl;
        cout << pyEnv->Interpreter()->ErrorMessage() << endl;
        throw VisItException("Script failed to run properly");
    }
    // we can assume a vtkDataSet as output
    PyObject *py_res = pyEnv->Interpreter()->GetGlobalObject("res");
    if(py_ds_in == NULL)
        cout << "BAD ERROR" <<endl;
    vtkDataSet *res = (vtkDataSet*)pyEnv->UnwrapVTKObject(py_res,"vtkDataSet");
    res->Register(NULL);

//    avtDataAttributes &inAtts      = GetInput()->GetInfo().GetAttributes();
//    avtDataAttributes &outAtts     = GetOutput()->GetInfo().GetAttributes();

//    res->Print(cout);
    double bounds[6];
    res->GetBounds(bounds);

//    if(bounds[4] == bounds[5])
//    {
//        GetOutput()->GetInfo().GetAttributes().SetSpatialDimension(2);
//        GetOutput()->GetInfo().GetAttributes().SetTopologicalDimension(2);
//        if (inAtts.GetSpatialDimension() == 3)
//        {
//            outAtts.SetSpatialDimension(2);
//            outAtts.SetTopologicalDimension(2);
//        }
//        GetOutput()->GetInfo().GetValidity().InvalidateSpatialMetaData();
//        GetOutput()->GetInfo().GetValidity().InvalidateNodes();
//        GetOutput()->GetInfo().GetValidity().InvalidateZones();
//        GetOutput()->GetInfo().GetValidity().InvalidateDataMetaData();
//        GetOutput()->GetInfo().GetValidity().InvalidateOperation();
//    }
    /// get data array for active variable..
    vtkDataArray* array = res->GetPointData()->GetScalars(primaryVariable.c_str());

    double range[2] = { 0, 0 };
    if(array)
    {
        array->GetRange(range);
    }
    else
    {
        array = res->GetCellData()->GetScalars(primaryVariable.c_str());
        if(array)
        {
            array->GetRange(range);
        }
    }

    avtDataAttributes &dataatts = GetOutput()->GetInfo().GetAttributes();
    avtExtents* e;

//    e = dataatts.GetActualDataExtents();
//    e->Set(range);

//    e = dataatts.GetThisProcsActualDataExtents();
//    e->Set(range);

//    e = dataatts.GetThisProcsOriginalSpatialExtents();
//    e->Set(range);
    e = dataatts.GetThisProcsOriginalDataExtents();
    e->Set(range);
    e = dataatts.GetThisProcsActualDataExtents();
    e->Set(range);

    vtkImageData* data = vtkImageData::SafeDownCast(res);

    if(data)
    {
        int arg[6];
        data->GetExtent(arg);
        double ex[6] = { arg[0], arg[1],
                         arg[2], arg[3],
                         arg[4], arg[5] };

        e = dataatts.GetOriginalSpatialExtents();
        e->Set(ex);

        e = dataatts.GetActualSpatialExtents();
        e->Set(ex);
    }

    //GetOutput()->GetInfo().GetAttributes().SetTopologicalDimension(2);
    GetOutput()->GetInfo().GetAttributes().SetSpatialDimension(2);

    std::cout << "setting: " << range[0] << " " << range[1] << std::endl;
//    return res;
//    if(PAR_Rank() == 0)
//    {
        return res;
//    }
//    else
//    {
//        return NULL;
//    }
}

void
avtProgrammableOpFilter::UpdateDataObjectInfo()
{
    //GetOutput()->GetInfo().GetAttributes().SetSpatialDimension(2);
    //GetOutput()->GetInfo().GetAttributes().SetTopologicalDimension(2);
    //GetOutput()->GetInfo().GetValidity().InvalidateSpatialMetaData();
    //GetOutput()->GetInfo().GetValidity().InvalidateNodes();
    //GetOutput()->GetInfo().GetValidity().InvalidateZones();
    //GetOutput()->GetInfo().GetValidity().InvalidateDataMetaData();
//    GetOutput()->GetInfo().GetValidity().InvalidateOperation();
}


bool avtProgrammableOpFilter::SetupFlowWorkspace()
{
    std::string script = "";
    //JSONNode node(atts.GetScriptMap());

    //if(!node.HasEntry("filter")) return false;

    std::string json_string = atts.GetScriptMap(); //node["filter"].AsString();

    if(json_string.length() == 0) /// empty string, maybe initializing empty instance..
        return false;

    //std::cout << "json_string: " << json_string << std::endl;
    JSONNode node;
    node.Parse(json_string);

    //std::cout << node.ToString() << std::endl;
    //std::cout << "Structure: " << json_string << std::endl;

    // create python string
    PyObject *py_json_str = PyString_FromString(json_string.c_str());
    pyEnv->Interpreter()->SetGlobalObject(py_json_str,"sdef_json");
    script = "";
    script += "def removeUnicode(input):\n";
    script += "    if isinstance(input, dict):\n";
    script += "        return { removeUnicode(key): removeUnicode(value) for key, value in input.iteritems()}\n";
    script += "    elif isinstance(input, list):\n";
    script += "        return [ removeUnicode(element) for element in input]\n";
    script += "    elif isinstance(input, unicode):\n";
    script += "        return input.encode('utf-8')\n";
    script += "    else:\n";
    script += "        return input\n";

    script += "sdef = json.loads(sdef_json)\n";
    script += "sdef = removeUnicode(sdef)\n";

    //script += "print json.dumps(sdef,indent=2)\n";
    // create a workspace
    script += "w = Workspace()\n";
    // register the scripts
    script += "script_pipeline.register_scripts(sdef['scripts'])\n";
    script += "w.register_filters(script_pipeline)\n";
    script += "ctx = w.add_context('script_pipeline','<default_context>')\n";
    // put the input mesh into the registry
    // load the data flow
    script += "w.load_dict(sdef)\n";
    if(!pyEnv->Interpreter()->RunScript(script))
    {
        cout << "Script Failed.." << endl;
        cout << pyEnv->Interpreter()->ErrorMessage() << endl;
        throw VisItException("Workspace setup has failed");
    }
    return true;
}


// ****************************************************************************
//  Method:  avtProgrammableOpFilter::ModifyContract
//
//  Purpose:
//    Adds secondary variable request.
//
//  Arguments:
//    spec       the pipeline specification
//
//  Programmer:  Cyrus Harrison
//  Creation:    Mon Feb  4 15:26:10 PST 2013
//
//  Modifications:
//
// ****************************************************************************
avtContract_p
avtProgrammableOpFilter::ModifyContract(avtContract_p spec)
{
    //
    // Get the old specification.
    //
    avtDataRequest_p ds = spec->GetDataRequest();
    
    // set this so we can use the name in exec data
    primaryVariable = std::string(ds->GetVariable());
    //cout <<PAR_Rank() << ": primaryVariable = " << primaryVariable <<endl;
    //
    // Make a new one
    //
    avtDataRequest_p nds = new avtDataRequest(ds);

    /// if setup has failed then return immediately...
    if(!SetupFlowWorkspace()) return new avtContract(spec, nds);

    // we need to find out what visit vars we need to request
    std::string script = "";
    script += "__vars = w.filter_names()\n";
    script += "__vars = [ var[1:] for var in __vars if var[0] == ':' and var != ':mesh' and var != ':pvar'";
    script += " and var != ':mpi_rank' and var != ':mpi_size' and var != ':mesh_domain']\n";
    if(!pyEnv->Interpreter()->RunScript(script))
    {
        cout << "Modify Contract Script Failed.." << endl;
        cout << pyEnv->Interpreter()->ErrorMessage() << endl;
        throw VisItException("Contract extraction failed..");
    }
    
    PyObject *py_vars   = pyEnv->Interpreter()->GetGlobalObject("__vars");
    PyObject *py_r_vars = PySequence_Fast(py_vars,"Expected Sequence");

    int n_vars          = PySequence_Size(py_r_vars);    
    // process all vars
    for(int i = 0; i < n_vars ; i++)
    {
        PyObject *py_var_str = PySequence_Fast_GET_ITEM(py_r_vars,i);  // borrowed
        std::string var_str = std::string(PyString_AsString(py_var_str));
        if (primaryVariable != var_str && var_str != std::string("default"))
        {
            cout << "Adding \"" << var_str << "\" as secondary var" <<endl;
            nds->AddSecondaryVariable(var_str.c_str());
        }
    }
    
    //
    // Create the new pipeline spec from the data spec, and return
    //
    avtContract_p rv = new avtContract(spec, nds);
    //rv->SetOnDemandStreaming(true);
    rv->SetReplicateSingleDomainOnAllProcessors(true);
    return rv;
}


/// Python script filter functions..

void addToVariant(ProgrammableOperation::ScriptType& subtype, Variant& result, Variant& v)
{
    if(subtype == ProgrammableOperation::BOOL_TYPE)
        result.AsBoolVector().push_back(v.AsBool());
    else if(subtype == ProgrammableOperation::CHAR_TYPE)
        result.AsCharVector().push_back(v.AsChar());
    else if(subtype == ProgrammableOperation::UNSIGNED_CHAR_TYPE)
        result.AsUnsignedCharVector().push_back(v.AsUnsignedChar());
    else if(subtype == ProgrammableOperation::INT_TYPE)
        result.AsIntVector().push_back(v.AsInt());
    else if(subtype == ProgrammableOperation::LONG_TYPE)
        result.AsLongVector().push_back(v.AsLong());
    else if(subtype == ProgrammableOperation::FLOAT_TYPE)
        result.AsFloatVector().push_back(v.AsFloat());
    else if(subtype == ProgrammableOperation::DOUBLE_TYPE)
        result.AsDoubleVector().push_back(v.AsDouble());
    else if(subtype == ProgrammableOperation::STRING_TYPE)
        result.AsStringVector().push_back(v.AsString());
}

bool convert(const ProgrammableOperation::ScriptType& type, PyObject* obj, Variant& result)
{
    bool success = true;
    switch(type)
    {
        case ProgrammableOperation::BOOL_TYPE:
        {
            PyBool_Check(obj) ? result = (obj == Py_False ? false: true ) : success = false;
            break;
        }
        case ProgrammableOperation::CHAR_TYPE:
        {
            PyString_Check(obj) && PyString_Size(obj) > 0 ? result = PyString_AsString(obj)[0] : success = false;
            break;
        }
        case ProgrammableOperation::UNSIGNED_CHAR_TYPE:
        {
            PyString_Check(obj)  && PyString_Size(obj) > 0 ? result = (unsigned char) PyString_AsString(obj)[0] : success = false;
            break;
        }
        case ProgrammableOperation::INT_TYPE:
        {
            /// if they come from R they may be double..
            PyInt_Check(obj) ? result = (int)PyInt_AsLong(obj) :
                              (PyFloat_Check(obj) ? result = (int)PyFloat_AsDouble(obj):
                                                    success = false) ;
            break;
        }
        case ProgrammableOperation::LONG_TYPE:
        {

            /// if they come from R they may be double..
            PyLong_Check(obj) ? result = (long)PyLong_AsLong(obj) :
                                (PyFloat_Check(obj) ? result = (long)PyFloat_AsDouble(obj):
                                                      success = false);
            break;
        }
        case ProgrammableOperation::FLOAT_TYPE:
        {
            PyFloat_Check(obj) ? result = (float)PyFloat_AsDouble(obj) : success = false;
            break;
        }
        case ProgrammableOperation::DOUBLE_TYPE:
        {
            PyFloat_Check(obj) ? result = (double)PyFloat_AsDouble(obj) : success = false;
            break;
        }
        case ProgrammableOperation::STRING_TYPE:
        {
            PyString_Check(obj) ? result = (const char*)PyString_AsString(obj) : success = false;
            break;
        }
        case ProgrammableOperation::VARIANT_TYPE:
        {
            if (PyBool_Check(obj)) result = (obj == Py_False ? false: true );
            else if(PyInt_Check(obj)) result = (int)PyInt_AsLong(obj);
            else if(PyLong_Check(obj)) result = (long)PyLong_AsLong(obj);
            else if(PyFloat_Check(obj)) result = (double)PyFloat_AsDouble(obj);
            else if(PyString_Check(obj)) result = (const char*)PyString_AsString(obj);
            else
            {
                success = false;
            }
            break;
        }
        case ProgrammableOperation::BOOL_VECTOR_TYPE:
        case ProgrammableOperation::CHAR_VECTOR_TYPE:
        case ProgrammableOperation::UNSIGNED_CHAR_VECTOR_TYPE:
        case ProgrammableOperation::INT_VECTOR_TYPE:
        case ProgrammableOperation::LONG_VECTOR_TYPE:
        case ProgrammableOperation::FLOAT_VECTOR_TYPE:
        case ProgrammableOperation::DOUBLE_VECTOR_TYPE:
        case ProgrammableOperation::STRING_VECTOR_TYPE:
    {
        if(!PyTuple_Check(obj) && !PyList_Check(obj))
        {
            success = false;
            break;
        }

        ProgrammableOperation::ScriptType subtype;
        switch(type)
        {
            case ProgrammableOperation::BOOL_VECTOR_TYPE: { subtype = ProgrammableOperation::BOOL_TYPE; result = boolVector(); break; }
            case ProgrammableOperation::CHAR_VECTOR_TYPE: { subtype = ProgrammableOperation::CHAR_TYPE; result = charVector(); break; }
            case ProgrammableOperation::UNSIGNED_CHAR_VECTOR_TYPE: { subtype = ProgrammableOperation::UNSIGNED_CHAR_TYPE; result = unsignedCharVector(); break; }
            case ProgrammableOperation::INT_VECTOR_TYPE: { subtype = ProgrammableOperation::INT_TYPE; result = intVector(); break; }
            case ProgrammableOperation::LONG_VECTOR_TYPE: { subtype = ProgrammableOperation::LONG_TYPE; result = longVector(); break; }
            case ProgrammableOperation::FLOAT_VECTOR_TYPE: { subtype = ProgrammableOperation::FLOAT_TYPE; result = floatVector(); break; }
            case ProgrammableOperation::DOUBLE_VECTOR_TYPE:{ subtype = ProgrammableOperation::DOUBLE_TYPE; result = doubleVector(); break; }
            case ProgrammableOperation::STRING_VECTOR_TYPE:{ subtype = ProgrammableOperation::STRING_TYPE; result = stringVector(); break; }
        };

        if(PyTuple_Check(obj))
        {
            // Extract arguments from the tuple.
            for(int i = 0; i < PyTuple_Size(obj); ++i)
            {
                PyObject *item = PyTuple_GET_ITEM(obj, i);
                Variant v;
                if(!convert(subtype,item,v))
                    success = false;
                addToVariant(subtype,result,v);
            }
        }
        else if(PyList_Check(obj))
        {
            // Extract arguments from the list.
            for(int i = 0; i < PyList_Size(obj); ++i)
            {
                PyObject *item = PyList_GET_ITEM(obj, i);
                Variant v;
                if(!convert(subtype,item,v))
                    success = false;
                addToVariant(subtype,result,v);
            }
        }
        else
        {
            std::cerr << "avtProgrammableOpFilter::convert error should not be here.." << std::endl;
            success = false;
        }
        break;
    }
    case ProgrammableOperation::VTK_DATA_ARRAY_TYPE:
    case ProgrammableOperation::VTK_DATASET_TYPE:
    case ProgrammableOperation::VTK_AVTDATASET_TYPE:
    default: break;
    };
    return success;
}


PyObject *
visit_functions(PyObject *self, PyObject *args)
{
    char* name = 0;
    PyObject* scriptArgs = NULL;

    ///if args is just the function call..
    if(PyString_Check(args))
        name = PyString_AsString(args);
    else
        PyArg_ParseTuple(args,"sO",&name,&scriptArgs);

    if(!name) return NULL;

    /// Get reference to module..
    PyObject *m = PyImport_ImportModule("visit_internal_funcs");

    /// TODO: return error..
    if (!m) { Py_INCREF(Py_None); return NULL; }

    PyObject *c_api_object = PyObject_GetAttrString(m, "_C_API");

    /// TODO: return error..
    if (!c_api_object) { Py_INCREF(Py_None); Py_DECREF(m); return NULL; }

    avtProgrammableOpFilter *scriptFilter = (avtProgrammableOpFilter *)PyCObject_AsVoidPtr(c_api_object);

    /// Parse arguments..

    //std::cout << "running: " << scriptFilter << std::endl;

    ScriptData* sd = scriptFilter->GetScriptData();

    if(sd->operations.count(name) == 0) return NULL;

    ProgrammableOperation* op = sd->operations[name];

    std::string op_name;
    stringVector op_argnames;
    std::vector<ProgrammableOperation::ScriptType> op_argtypes;

    ProgrammableOperation::ResponseType op_resp = op->getSignature(op_name,op_argnames,op_argtypes);

    //std::cout << op_name << " " << op_resp << std::endl;
    std::vector<Variant> variantArgs;
    //std::map<int,void*> datamap;
    std::map<int,vtkShapedDataArray> dataArrayMap;
    std::map<int, std::vector<Variant> > variantVecMap;

    // Extract arguments from the tuple.
    /// scriptArgs need to ensure case against no parameters..
    if(scriptArgs && op_argtypes.size() < 2)
    {
        /// TODO remove this duplication of code..
        if(scriptArgs && op_argtypes.size() == 1)
        {
            Variant v;
            int i = 0;
            PyObject* item = scriptArgs;

            if(op_argtypes[i] == ProgrammableOperation::VTK_DATA_ARRAY_TYPE)
            {
                /// Handle multi-dimensional array..
                if(PyTuple_Check(item) || PyList_Check(item))
                {
                    PyObject* output_shape = PyTuple_Check(item) ? PyTuple_GetItem(item,0) :
                                                                   PyList_GetItem(item,0);
                    PyObject* output_data = PyTuple_Check(item) ? PyTuple_GetItem(item,1) :
                                                                  PyList_GetItem(item,1);

                    //Py_INCREF(output_shape);
                    //Py_INCREF(output_data);

                    vtkShapedDataArray output;

                    for(int j = 0; j < PyList_Size(output_shape); ++j)
                    {
                        int value = PyInt_AsLong(PyList_GetItem(output_shape,j));
                        output.shape.push_back(value);
                    }

                    void* vobj = scriptFilter->GetPythonEnvironment()->UnwrapVTKObject(output_data, "vtkAbstractArray");
                    output.vtkarray = (vtkAbstractArray*)vobj;
                    dataArrayMap[i] = output;
                }
                else
                {
                    void* vobj = scriptFilter->GetPythonEnvironment()->UnwrapVTKObject(item, "vtkAbstractArray");

                    vtkShapedDataArray output;
                    output.vtkarray = (vtkAbstractArray*)vobj;
                    output.shape.push_back(output.vtkarray->GetDataSize());

                    dataArrayMap[i] = output;
                }
            }
            else if(op_argtypes[i] == ProgrammableOperation::VARIANT_VECTOR_TYPE)
            {
                std::vector<Variant> variantVec;

                if(!PyTuple_Check(item) && !PyList_Check(item))
                {
                    variantArgs.push_back(v); ///null..
                    variantVecMap[i] = variantVec;
                }
                else {
                    if(PyTuple_Check(item))
                    {
                        // Extract arguments from the tuple.
                        for(int j = 0; j < PyTuple_Size(item); ++j)
                        {
                            PyObject *itemx = PyTuple_GET_ITEM(item, j);
                            Variant v;
                            convert(ProgrammableOperation::VARIANT_TYPE,itemx,v);
                            variantVec.push_back(v);
                        }
                    }
                    else
                    {
                        // Extract arguments from the list.
                        for(int j = 0; j < PyList_Size(item); ++j)
                        {
                            PyObject *itemx = PyList_GET_ITEM(item, j);
                            Variant v;
                            convert(ProgrammableOperation::VARIANT_TYPE,itemx,v);
                            variantVec.push_back(v);
                        }
                    }
                    variantVecMap[i] = variantVec;
                }
            }
            else
            {
                convert(op_argtypes[i],item,v);
            }

            variantArgs.push_back(v);
        }
    }
    else if(scriptArgs && op_argtypes.size() >= 2)
    {
        //Py_INCREF(scriptArgs);
        if(PyTuple_Size(scriptArgs) != op_argtypes.size())
        {
            std::cerr << "sizes do not match!" << std::endl;
            return NULL;
        }
        for(int i = 0; i < PyTuple_Size(scriptArgs); ++i)
        {
            Variant v;
            PyObject *item = PyTuple_GET_ITEM(scriptArgs, i);
            //Py_INCREF(item);
            if(op_argtypes[i] == ProgrammableOperation::VTK_DATA_ARRAY_TYPE)
            {
                /// Handle multi-dimensional array..
                if(PyTuple_Check(item) || PyList_Check(item))
                {
                    PyObject* output_shape = PyTuple_Check(item) ? PyTuple_GetItem(item,0) :
                                                                   PyList_GetItem(item,0);
                    PyObject* output_data = PyTuple_Check(item) ? PyTuple_GetItem(item,1) :
                                                                  PyList_GetItem(item,1);
                    //Py_INCREF(output_shape);
                    //Py_INCREF(output_data);

                    vtkShapedDataArray output;

                    for(int j = 0; j < PyList_Size(output_shape); ++j)
                    {
                        int value = PyInt_AsLong(PyList_GetItem(output_shape,j));
                        output.shape.push_back(value);
                    }

                    void* vobj = scriptFilter->GetPythonEnvironment()->UnwrapVTKObject(output_data, "vtkAbstractArray");
                    output.vtkarray = (vtkAbstractArray*)vobj;
                    output.vtkarray->Register(NULL);
                    dataArrayMap[i] = output;
                }
                else
                {
                    void* vobj = scriptFilter->GetPythonEnvironment()->UnwrapVTKObject(item, "vtkAbstractArray");

                    vtkShapedDataArray output;
                    output.vtkarray = (vtkAbstractArray*)vobj;
                    output.shape.push_back(output.vtkarray->GetDataSize());

                    dataArrayMap[i] = output;
                }
            }
            else if(op_argtypes[i] == ProgrammableOperation::VARIANT_VECTOR_TYPE)
            {
                std::vector<Variant> variantVec;

                if(!PyTuple_Check(item) && !PyList_Check(item))
                {
                    variantArgs.push_back(v); ///null..
                    variantVecMap[i] = variantVec;
                    continue;
                }

                if(PyTuple_Check(item))
                {
                    // Extract arguments from the tuple.
                    for(int j = 0; j < PyTuple_Size(item); ++j)
                    {
                        PyObject *itemx = PyTuple_GET_ITEM(item, j);
                        Variant v;
                        convert(ProgrammableOperation::VARIANT_TYPE,itemx,v);
                        variantVec.push_back(v);
                    }
                }
                else
                {
                    // Extract arguments from the list.
                    for(int j = 0; j < PyList_Size(item); ++j)
                    {
                        PyObject *itemx = PyList_GET_ITEM(item, j);
                        Variant v;
                        convert(ProgrammableOperation::VARIANT_TYPE,itemx,v);
                        variantVec.push_back(v);
                    }
                }
                variantVecMap[i] = variantVec;
            }
            else
            {
                convert(op_argtypes[i],item,v);
            }

            //std::cout << op_argnames[i] << " " << op_argtypes[i] << " " <<  v.ToJSON() << std::endl;

            variantArgs.push_back(v);
        }
    }

//    for(int i = 0; i < variantArgs.size(); ++i)
//    {
//        Variant v = variantArgs[i];
//        std::cout << v.ToJSON() << std::endl;
//    }

    ProgrammableOpArguments sargs;

    sargs.input = scriptFilter->GetInput();
    sargs.contract = scriptFilter->GetInput()->GetOriginatingSource()->GetGeneralContract();
    sargs.args = variantArgs;
    sargs.dataArrayMap = dataArrayMap;
    sargs.variantVector = variantVecMap;
    sargs.pythonFilter = scriptFilter->GetPythonEnvironment();

    PyObject* ds_in = scriptFilter->GetPythonEnvironment()->Interpreter()->GetGlobalObject("ds_in");
    vtkDataSet* inputDataSet = (vtkDataSet*)scriptFilter->GetPythonEnvironment()->UnwrapVTKObject(ds_in, "vtkDataSet");

    sargs.input_mesh = inputDataSet;

    PyObject* ds_domain = scriptFilter->GetPythonEnvironment()->Interpreter()->GetGlobalObject("ds_domain");
    int domain = PyInt_AsLong(ds_domain);

    sargs.input_domain = domain;

    if(op_resp == ProgrammableOperation::AVT_DATA_SET)
    {
        avtDataset_p result;

        if(!op->func(sargs, result))

        {
            std::cerr << "operation failed!" << std::endl;
            Py_DECREF(c_api_object);
            Py_DECREF(m);
            return NULL;
        }
        avtDataTree_p tree = scriptFilter->GetDataTree(result);

        int leaves = 0;
        vtkDataSet** datasets = tree->GetAllLeaves(leaves);

        //std::cout << leaves << std::endl;
        if(leaves != -1)
        {
            PyObject* out = scriptFilter->GetPythonEnvironment()->WrapVTKObject(datasets[0],"vtkDataSet");
            //std::cout << out << std::endl;

            Py_DECREF(c_api_object);
            Py_DECREF(m);

            return out;
        }
    }
    /*else if(op_resp == ProgrammableOperation::VTK_DATA_ARRAY)
    {
        vtkAbstractArray* dataarray;
        if(!op->func(sargs, dataarray))

        {
            std::cerr << name << ": data array operation failed!" << std::endl;
            Py_DECREF(c_api_object);
            Py_DECREF(m);
            return NULL;
        }

        //std::cout << "dataArray: " << dataarray << std::endl;
        PyObject* output = scriptFilter->GetPythonEnvironment()->WrapVTKObject(dataarray, "vtkAbstractArray");
        //std::cout << output << std::endl;
        return output;
    }*/
    else if(op_resp == ProgrammableOperation::VTK_DATASET)
    {
        vtkDataSet* dataset;
        if(!op->func(sargs, dataset))

        {
            std::cerr << name << ": dataset operation failed!" << std::endl;
            Py_DECREF(c_api_object);
            Py_DECREF(m);
            return NULL;
        }

        PyObject* output = scriptFilter->GetPythonEnvironment()->WrapVTKObject(dataset, "vtkDataSet");
        return output;
    }
    else if(op_resp == ProgrammableOperation::VTK_MULTI_DIMENSIONAL_DATA_ARRAY)
    {
        vtkShapedDataArray dataarrayshape;

        if(!op->func(sargs, dataarrayshape))

        {
            std::cerr << name << ": multi data array operation failed!" << std::endl;
            Py_DECREF(c_api_object);
            Py_DECREF(m);
            return NULL;
        }

        //std::cout << "dataArray: " << dataarray << std::endl;
        PyObject* output = PyList_New(2);

        PyObject* outputshape = PyList_New(dataarrayshape.shape.size());
        for(int i = 0; i < dataarrayshape.shape.size(); ++i )
            PyList_SetItem(outputshape,i,PyInt_FromLong(dataarrayshape.shape[i]));

        PyObject* outputdata = scriptFilter->GetPythonEnvironment()->WrapVTKObject(dataarrayshape.vtkarray, "vtkAbstractArray");

        PyList_SetItem(output,0,outputshape);
        PyList_SetItem(output,1,outputdata);

        //std::cout << output << std::endl;
        return output;

    }
    else
    {
        Variant result;
        if(!op->func(sargs, result))

        {
            std::cerr << name << ": constant operation failed!" << std::endl;
            Py_DECREF(c_api_object);
            Py_DECREF(m);
            return NULL;
        }

        PyObject* output = NULL;
        /// convert result to PyObject

        if(result.Type() == Variant::BOOL_TYPE)
            output = PyBool_FromLong(result.AsBool());
        else if(result.Type() == Variant::CHAR_TYPE)
            output = PyString_FromStringAndSize(&result.AsChar(),1);
        else if(result.Type() == Variant::UNSIGNED_CHAR_TYPE)
            output = PyByteArray_FromStringAndSize(&result.AsChar(),1);
        else if(result.Type() == Variant::STRING_TYPE)
            output = PyString_FromString(result.AsString().c_str());
        else if(result.Type() == Variant::INT_TYPE)
            output = PyInt_FromLong(result.AsInt());
        else if(result.Type() == Variant::LONG_TYPE)
            output = PyLong_FromLong(result.AsLong());
        else if(result.Type() == Variant::FLOAT_TYPE)
            output = PyFloat_FromDouble(result.AsFloat());
        else if(result.Type() == Variant::DOUBLE_TYPE)
            output = PyFloat_FromDouble(result.AsDouble());

        return output;
    }

//    Py_INCREF(scriptArgs);
    Py_DECREF(c_api_object);
    Py_DECREF(m);

    Py_INCREF(Py_None);
    return Py_None;
}
