/***************************************************************************
 * pythonsecurity.cpp
 * This file is part of the KDE project
 * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, 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
 * Library General Public License for more details.
 * You should have received a copy of the GNU Library 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.
 ***************************************************************************/

#include "pythonsecurity.h"
#include "pythoninterpreter.h"
#include "pythonmodule.h"

using namespace Kross::Python;

PythonSecurity::PythonSecurity(PythonInterpreter* interpreter)
    : Py::ExtensionModule<PythonSecurity>("PythonSecurity")
    , m_interpreter(interpreter)
    , m_pymodule(0)
{
    add_varargs_method("_getattr_", &PythonSecurity::_getattr_, "Secure wapper around the getattr method.");
    initialize("The PythonSecurity module used to wrap the RestrictedPython functionality.");

    /* TESTCASE
    initRestrictedPython();
    compile_restricted(
        "a = 2 + 5\n"
        "import os\n"
        "import sys\n"
        "b = sys.path\n"
        "print \"######### >>>testcase<<< #########\" \n"
        ,
        "mytestcase", // filename
        "exec" // 'exec' or 'eval' or 'single'
    );
    */

}

PythonSecurity::~PythonSecurity()
{
    delete m_pymodule;
}

void PythonSecurity::initRestrictedPython()
{
    try {
        Py::Dict mainmoduledict = ((PythonInterpreter*)m_interpreter)->mainModule()->getDict();
        PyObject* pymodule = PyImport_ImportModuleEx(
            "RestrictedPython", // name of the module being imported (may be a dotted name)
            mainmoduledict.ptr(), // reference to the current global namespace
            mainmoduledict.ptr(), // reference to the local namespace
            0 // PyObject *fromlist
        );
        if(! pymodule)
            throw Py::Exception();
        m_pymodule = new Py::Module(pymodule, true);

        PyObject* pyrun = PyRun_String(
            //"import os\n"
            //"import sys\n"
            "import __main__\n"
            "import PythonSecurity\n"
            "from RestrictedPython import compile_restricted, PrintCollector\n"
            "from RestrictedPython.Eval import RestrictionCapableEval\n"
            "from RestrictedPython.RCompile import RModule\n"

            "setattr(__main__, '_getattr_', PythonSecurity._getattr_)\n"
            "setattr(__main__, '_print_', PrintCollector)\n"
            ,
            Py_file_input,
            m_pymodule->getDict().ptr(),
            m_pymodule->getDict().ptr()
        );
        if(! pyrun)
            throw Py::Exception();

        krossdebug("PythonSecurity::PythonSecurity SUCCESSFULLY LOADED");
    }
    catch(Py::Exception& e) {
        TQString err = Py::value(e).as_string().c_str();
        e.clear();
        throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Failed to initialize PythonSecurity module: %1").arg(err) ) );
    }
}

Py::Object PythonSecurity::_getattr_(const Py::Tuple& args)
{
    krossdebug("PythonSecurity::_getattr_");
    for(uint i = 0; i < args.size(); i++) {
        Py::Object o = args[i];
        krossdebug( o.as_string().c_str() );
    }
    return Py::None();
}

PyObject* PythonSecurity::compile_restricted(const TQString& source, const TQString& filename, const TQString& mode)
{
    krossdebug("PythonSecurity::compile_restricted");
    if(! m_pymodule)
        initRestrictedPython(); // throws exception if failed

    try {
        Py::Dict mainmoduledict = ((PythonInterpreter*)m_interpreter)->mainModule()->getDict();

        PyObject* func = PyDict_GetItemString(m_pymodule->getDict().ptr(), "compile_restricted");
        if(! func)
            throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("No such function '%1'.").arg("compile_restricted")) );

        Py::Callable funcobject(func, true); // the funcobject takes care of freeing our func pyobject.

        if(! funcobject.isCallable())
            throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Function '%1' is not callable.").arg("compile_restricted")) );

        Py::Tuple args(3);
        args[0] = Py::String(source.utf8());
        args[1] = Py::String(filename.utf8());
        args[2] = Py::String(mode.utf8());

        Py::Object result = funcobject.apply(args);

        PyObject* pycode = PyEval_EvalCode(
            (PyCodeObject*)result.ptr(),
            mainmoduledict.ptr(),
            mainmoduledict.ptr()
        );
        if(! pycode)
            throw Py::Exception();

        /*
        Py::List ml = mainmoduledict;
        for(Py::List::size_type mi = 0; mi < ml.length(); ++mi) {
            krossdebug( TQString("dir() = %1").arg( ml[mi].str().as_string().c_str() ) );
            //krossdebug( TQString("dir().dir() = %1").arg( Py::Object(ml[mi]).dir().as_string().c_str() ) );
        }
        */

        Py::Object code(pycode);
        krossdebug( TQString("%1 callable=%2").arg(code.as_string().c_str()).arg(PyCallable_Check(code.ptr())) );
        Py::List l = code.dir();
        for(Py::List::size_type i = 0; i < l.length(); ++i) {
            krossdebug( TQString("dir() = %1").arg( l[i].str().as_string().c_str() ) );
            //krossdebug( TQString("dir().dir() = %1").arg( Py::Object(l[i]).dir().as_string().c_str() ) );
        }

        return pycode;
    }
    catch(Py::Exception& e) { 
        TQString err = Py::value(e).as_string().c_str();
        e.clear();
        throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(TQString("Function '%1' failed with python exception: %2").arg("compile_restricted").arg(err) ) );
    }
}

#if 0
void PythonSecurity::compile_restricted_function(const Py::Tuple& /*args*/, const TQString& /*body*/, const TQString& /*name*/, const TQString& /*filename*/, const Py::Object& /*globalize*/)
{
    //TODO
}

void PythonSecurity::compile_restricted_exec(const TQString& /*source*/, const TQString& /*filename*/)
{
    //TODO
}

void PythonSecurity::compile_restricted_eval(const TQString& /*source*/, const TQString& /*filename*/)
{
    //TODO
}
#endif