/***************************************************************************
 *   Copyright (C) 2003 by Julian Rockey (linux@jrockey.com)               *
 *   Original code by Torben Weis                                          *
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/


#include "pcop.h"

#include <kdebug.h>

#include <qapplication.h>
#include <qcstring.h>
#include <qdatastream.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qstring.h>

#include <dcopclient.h>

#include <assert.h>

#include "marshaller.h"
#include "importedmodules.h"

namespace PythonDCOP {

  PCOPObject::PCOPObject(PyObject *py_obj) :
    DCOPObject(), m_py_obj(py_obj)
  {
    m_methods.setAutoDelete(true);
  }

  PCOPObject::PCOPObject(PyObject *py_obj, const char *objid) :
    DCOPObject(QCString(objid)), m_py_obj(py_obj)
  {
    m_methods.setAutoDelete(true);
  }

  PCOPObject::~PCOPObject()
  {
  }

  bool PCOPObject::process(const QCString &fun, const QByteArray &data,
                           QCString& replyType, QByteArray &replyData)
  {
    bool result = py_process(fun,data,replyType,replyData);
    if (PyErr_Occurred()) {
      kdDebug(70001) << "Error! About to print..." << endl;
      PyErr_Print();
      kdDebug(70001) << "About to clear..." << endl;
      PyErr_Clear();
      kdDebug(70001) << "Error handled." << endl;
    }
    return result;
  }

  bool PCOPObject::py_process(const QCString &fun, const QByteArray &data,
                           QCString& replyType, QByteArray &replyData)
  {

    kdDebug(70001) << "PCOPObject::process - fun=" << fun << " replyType=" << replyType << endl;

    PCOPMethod  *meth = matchMethod(fun);
    if (!meth) {
      kdDebug(70001) << "Could not match method name" << endl;
    }

    if (meth) {

      kdDebug(70001) << "m_py_obj=" << m_py_obj << " meth->name=" << meth->name() << " meth->name.data=" << meth->name().data() << endl;
      if (meth->name().isNull()) { kdDebug(70001) << "meth name is null" << endl; return false; }
//       if (!PyObject_HasAttrString(m_py_obj, meth->name().data())) {
//         kdDebug(70001) << "Method registered, but no python method found" << endl;
//         return false;
//       }

      QDataStream str_arg(data, IO_ReadOnly);
      PyObject *args = PyTuple_New( meth->paramCount() );
      for(int c=0;c<meth->paramCount();c++) {
        kdDebug(70001) << "Demarshalling type: " << meth->param(c)->signature() << endl;
        PyObject *arg = meth->param(c)->demarshal(str_arg);
        if (!arg) {
          kdDebug(70001) << "Failed to demarshall an argument" << endl;
          return false;
        }
        PyTuple_SetItem(args, c, arg );
      }

      kdDebug(70001) << "args is " << PyTuple_Size(args) << " long" << endl;

//       PyObject *method = PyObject_GetAttrString(m_py_obj, meth->name().data() );
      PyObject *method = meth->pythonMethod();
      if (!PyCallable_Check(method)) {
        kdDebug(70001) << "Expected a callable object, but didn't get one!" << endl;
        return false;
      }

//       PyObject *function = PyMethod_Function(method);
//       PyObject *self = PyMethod_Self(method);
//       Py_INCREF(self);
//       PyTuple_SetItem(args, 0, self );
//       PyObject *result = PyObject_CallObject(function, args);

//       Py_DECREF(method);
      if (PyMethod_Self(method)==NULL)
        kdDebug(70001) << "Warning: self is null!" << endl;

      kdDebug(70001) << "About to call object.." << endl;
      PyObject *result = PyObject_CallObject(method, args);
      kdDebug(70001) << "Finished calling object." << endl;

      if (result) {
        replyType = meth->type()->signature();
        PCOPType repl(replyType);
        if (repl.isMarshallable(result)) {
          QDataStream str_repl(replyData, IO_WriteOnly);
          repl.marshal(result,str_repl);
          Py_DECREF(result);
          return true;
        } else {
          Py_DECREF(result);
          kdDebug(70001) << "Result of python method was not marshallable into " << replyType << endl;
          return false;
        }
      }
      else {
        kdDebug(70001) << "null result from python method call" << endl;
        return false;
      }

    }

    return DCOPObject::process(fun,data,replyType,replyData);

  }

  bool PCOPObject::setMethodList(QAsciiDict<PyObject> meth_list) {
    bool ok = true;

    for(QAsciiDictIterator<PyObject> it(meth_list);
        it.current(); ++it) {

      PCOPMethod *meth = NULL;
      if (ok) {
        meth = new PCOPMethod(QCString(it.currentKey()));

        if (!meth || !meth->setPythonMethod(it.current())) {
          if (meth) delete meth;
          meth=NULL;
          m_methods.clear();
          ok=false;
        }

      }

//       Py_DECREF(it.current());
      if (meth) m_methods.insert(meth->signature(),meth);
    }

    return ok;
  }

  QCStringList PCOPObject::functions() {
    QCStringList funcs = DCOPObject::functions();
    for(QAsciiDictIterator<PCOPMethod> it(m_methods);
        it.current(); ++it) {
      PCOPMethod *meth = it.current();
      QCString func = meth->type()->signature();
      func += ' ';
      func += meth->signature();
      funcs << func;
    }
    return funcs;
  }

  /**
   * For testing
   */
  PyObject *PCOPObject::methodList()  {
    PyObject *result = PyList_New(m_methods.count());
    int c=0;
    for(QAsciiDictIterator<PCOPMethod> it(m_methods);
        it.current(); ++it, ++c) {
      PyObject *tuple = PyTuple_New(2);
      PyList_SetItem(result, c, tuple);
      PyTuple_SetItem(tuple, 0, PyString_FromString(it.currentKey() ) );
      PyTuple_SetItem(tuple, 1, it.current()->pythonMethod() );
    }
    return result;
  }

  PCOPMethod *PCOPObject::matchMethod(const QCString &fun) {
    return m_methods.find(fun);
  }


  PCOPType::PCOPType( const QCString& type )
  {
    m_leftType = NULL;
    m_rightType = NULL;

    int pos = type.find( '<' );
    if ( pos == -1 )
    {
      m_type = type;
      return;
    }

    int pos2 = type.findRev( '>' );
    if ( pos2 == -1 )
      return;

    m_type = type.left( pos );

    // There may be no more than 2 types in the bracket
    int komma = type.find( ',', pos + 1 );
    if ( komma == -1 )
    {
      m_leftType = new PCOPType( type.mid( pos + 1, pos2 - pos - 1 ) );
    }
    else
    {
      m_leftType = new PCOPType( type.mid( pos + 1, komma - pos - 1 ) );
      m_rightType = new PCOPType( type.mid( komma + 1, pos2 - komma - 1 ) );
    }
  }

  PCOPType::~PCOPType()
  {
    if (m_leftType) delete m_leftType;
    if (m_rightType) delete m_rightType;
  }

  QCString PCOPType::signature() const
  {
    QCString str = m_type;
    if ( m_leftType )
    {
      str += "<";
      str += m_leftType->signature();

      if ( m_rightType )
      {
        str += ",";
        str += m_rightType->signature();
      }

      str += ">";
    }

    return str;
  }

  bool PCOPType::marshal( PyObject* obj, QDataStream& str ) const
  {
    return Marshaller::instance()->marshal(*this, obj, str);
  }

  bool PCOPType::isMarshallable( PyObject *obj ) const
  {
    return Marshaller::instance()->canMarshal(*this, obj);
  }

  PyObject* PCOPType::demarshal( QDataStream& str ) const
  {
    return Marshaller::instance()->demarshal(*this, str);
  }

  PCOPMethod::PCOPMethod( const QCString& signature ) :
    m_py_method(NULL)
  {

    m_type = 0;
    m_params.setAutoDelete( TRUE );

    // Find the space that separates the type from the name
    int k  = signature.find( ' ' );
    if ( k == -1 )
      return;

    // Create the return type from the string
    m_type = new PCOPType( signature.left( k ) );

    // Find the brackets
    int i  = signature.find( '(' );
    if ( i == -1 )
      return;
    int j  = signature.find( ')' );
    if ( j == -1 )
      return;

    // Extract the name
    m_name = signature.mid( k + 1, i - k - 1 );

    // Strip the parameters
    QCString p  = signature.mid( i + 1, j - i - 1 ).stripWhiteSpace();

    if ( !p.isEmpty() ) {
      // Make the algorithm  terminate
      p += ",";

      // Iterate over the parameters
      int level = 0;
      int start = 0;
      int len = p.length();
      for( int i = 0; i < len; ++i )
      {
        // Found a comma? Then we reached the end of a parameter
        if ( p[i] == ',' && level == 0 )
        {
          // Find the space that separates name from type.
          int space = p.find( ' ', start );

          if ( space == -1 || space > i ) // unnamed parameter
            space = i;

          PCOPType* type = new PCOPType( p.mid( start, space - start ) );
          m_params.append( type );

          // Start of the next parameter
          start = i + 1;
        }
        else if ( p[i] == '<' )
          ++level;
        else if ( p[i] == '>' )
          --level;
      }
    }

    m_signature = m_name;
    m_signature += "(";

    QListIterator<PCOPType> it( m_params );
    for( ; it.current(); ++it )
    {
      if ( !it.atFirst() )
        m_signature += ',';
      m_signature += it.current()->signature();
    }

    m_signature += ")";

  }

  PCOPMethod::~PCOPMethod()
  {
    delete m_type;
    if (m_py_method) {
      Py_DECREF(m_py_method);
    }
  }

  bool PCOPMethod::setPythonMethod(PyObject *method) {
    if (method && PyMethod_Check(method)) {

      if (m_py_method) {
        Py_DECREF(m_py_method);
      }

      m_py_method = method;
      Py_INCREF(m_py_method);

      return true;
    }
    return false;
  }

  int PCOPMethod::paramCount() const
  {
    return m_params.count();
  }

  PCOPType* PCOPMethod::param( int i )
  {
    return m_params.at( i );
  }

  const PCOPType* PCOPMethod::param( int i ) const
  {
    return ((PCOPMethod*)this)->m_params.at( i );
  }

  PCOPClass::PCOPClass( const QCStringList& methods )
  {
    m_methods.setAutoDelete( true );

    QCStringList::ConstIterator it = methods.begin();
    for( ; it != methods.end(); ++it )
    {
      PCOPMethod* m = new PCOPMethod( *it );
      m_methods.insert( m->m_name, m );
    }
  }

  PCOPClass::~PCOPClass()
  {
  }

  const PCOPMethod* PCOPClass::method( const QCString &name, PyObject *argTuple )
  {
    if ( !argTuple )
      return m_methods[ name ];

    QAsciiDictIterator<PCOPMethod> it( m_methods );
    for (; it.current(); ++it )
      if ( it.currentKey() == name &&
           it.current()->paramCount() == PyTuple_Size( argTuple ) )
      {
        // ok, name and argument count match, now check if the python
        // can be marshalled to the qt/dcop type

        PCOPMethod *m = it.current();

        bool fullMatch = true;

        for ( int i = 0; i < m->paramCount(); ++i )
          if ( !m->param( i )->isMarshallable( PyTuple_GetItem( argTuple, i ) ) )
          {
            fullMatch = false;
            break;
          }

        if ( fullMatch )
          return m;
      }

    return 0;
  }


  // Client

  Client::Client() : m_dcop(NULL), m_qapp(NULL)
  {
    ImportedModules::setInstance( new ImportedModules );
    int argc = 0;
    char **argv = NULL;
    m_qapp = new QApplication(argc,argv,false);
  }

  Client::~Client()
  {
//     if (m_qapp) delete m_qapp;
    if (m_dcop) delete m_dcop;
  }

  void Client::processEvents() {
    if (m_qapp) {
//       kdDebug(70001) << "Processing events..." << endl;
      m_qapp->processEvents();
    }
  }

  DCOPClient *Client::dcop() {
    if ( !m_dcop ) {
      m_dcop = new DCOPClient;
      if ( !m_dcop->attach() )
        kdWarning(70001) << "Could not attach to DCOP server";
    }
    return m_dcop;
  }

  Client *Client::instance() { return s_instance; }
  Client *Client::s_instance = new Client;


////////////////////////////////////////////////
//
// Methods accessed by python
//
////////////////////////////////////////////////

  PyObject* dcop_call( PyObject* /*self*/, PyObject* args )
  {
    char *arg1;
    char *arg2;
    char *arg3;
    PyObject* tuple;

    if ( !PyArg_ParseTuple( args, (char*)"sssO", &arg1, &arg2, &arg3, &tuple ) )
      return NULL;

    if ( !PyTuple_Check( tuple ) )
      return NULL;

    QByteArray replyData;
    QCString replyType;
    QByteArray data;
    QDataStream params( data, IO_WriteOnly );

    QCString appname( arg1 );
    QCString objname( arg2 );
    QCString funcname( arg3 );

    //
    // Remove escape characters
    //
    if ( objname[0] == '_' )
      objname = objname.mid( 1 );
    if ( funcname[0] == '_' )
      funcname = funcname.mid( 1 );

    DCOPClient* dcop = Client::instance()->dcop();

    //
    // Determine which functions are available.
    //
    bool ok = false;
    QCStringList funcs = dcop->remoteFunctions( appname, objname, &ok );
    if ( !ok )
    {
      PyErr_SetString( PyExc_RuntimeError, "Object is not accessible." );
      return NULL;
    }

    // for ( QCStringList::Iterator it = funcs.begin(); it != funcs.end(); ++it ) {
    // qDebug( "%s", (*it).data() );
    // }

    //
    // Create a parse tree and search for the method
    //
    // ### Check wether that is sane
    PCOPClass c( funcs );

    // qDebug("Parsing done.");

    // Does the requested method exist ?
    const PCOPMethod* m = c.method( funcname, tuple );
    if ( !m )
    {
      PyErr_SetString( PyExc_RuntimeError, "DCOP: Unknown method." );
      return NULL;
    }

    QCString signature = m->signature();
    kdDebug(70001) << "The signature is " << signature.data() << endl;

    kdDebug(70001) << "The method takes " << m->paramCount() << " parameters" << endl;

    //
    // Marshal the parameters.
    //

    int param_count = m->paramCount();
    for( int p = 0; p < param_count; ++p )
    {
      PyObject* o = PyTuple_GetItem( tuple, p );
      // #### Check for errors
      if ( !m->param( p )->marshal( o, params ) )
      {
        kdDebug(70001) << "QD: Could not marshal paramater %i" << p << endl;
        PyErr_SetString( PyExc_RuntimeError, "DCOP: marshaling failed" );
        return NULL;
      }
    }

    kdDebug(70001) << "Calling " << appname.data() << " " << objname.data() << " " << signature.data() << endl;

//     ASSERT( Client::instance()->dcop() != 0 );
    ASSERT(dcop);

    if ( !dcop->call( appname, objname, signature, data, replyType, replyData ) )
    {
      PyErr_SetString( PyExc_RuntimeError, "DCOP: call failed" );
      return NULL;
    }

    kdDebug(70001) << "The return type is " << replyType.data() << endl;

    //
    // Now decode the return type.
    //
    // ### Check wether that was sane
    PCOPType type( replyType );
    QDataStream reply(replyData, IO_ReadOnly);
    return type.demarshal( reply );

  }

  PyObject* application_list( PyObject */*self*/, PyObject */*args*/ )
  {
    QCStringList apps = Client::instance()->dcop()->registeredApplications();

    PyObject *l = PyList_New( apps.count() );

    QCStringList::ConstIterator it = apps.begin();
    QCStringList::ConstIterator end = apps.end();
    unsigned int i = 0;
    for (; it != end; ++it, i++ )
      PyList_SetItem( l, i, PyString_FromString( (*it).data() ) );

    return l;
  }

  PyObject *object_list( PyObject */*self*/, PyObject *args) {
    const char *app;
    if (PyArg_ParseTuple(args, (char*)"s", &app)) {
      QCStringList objects = Client::instance()->dcop()->remoteObjects(QCString(app));
      return make_py_list(objects);
    }
    return NULL;
  }

  PyObject *method_list( PyObject */*self*/, PyObject *args) {
    const char *app, *obj;
    if (PyArg_ParseTuple(args, (char*)"ss", &app, &obj)) {
      QCStringList methods = Client::instance()->dcop()->remoteFunctions(QCString(app), QCString(obj) );
      return make_py_list(methods);
    }
    return NULL;
  }

  PyObject *register_as( PyObject */*self*/, PyObject *args) {
    const char *appid;
    int add_pid = 1;
    if (PyArg_ParseTuple(args, (char*)"s|i", &appid, &add_pid)) {
      QCString actual_appid = Client::instance()->dcop()->registerAs(QCString(appid), add_pid!=0);
      return PyString_FromString(actual_appid.data());
    }
    return NULL;
  }

  PyObject *create_dcop_object( PyObject */*self*/, PyObject *args) {
    PyObject *py_dcop_object;
    const char *objid = NULL;
    if (PyArg_ParseTuple(args, (char*)"O|s", &py_dcop_object, &objid)) {
      Py_INCREF(py_dcop_object);
      PCOPObject *obj = objid ? new PCOPObject(py_dcop_object, objid) : new PCOPObject(py_dcop_object);
      return PyCObject_FromVoidPtr( (void*)obj, delete_dcop_object );
    }
    return NULL;
  }

  /**
   * pcop.set_method_list( <dcopobject cobject>, <method list> )
   * where <method list> is a list of tuples
   * [ ('method signature', python method), ... ]
   */
  PyObject *set_method_list( PyObject */*self*/, PyObject *args) {
    PyObject *c_obj;
    PyObject *method_list;
    if (PyArg_ParseTuple(args, (char*)"OO", &c_obj, &method_list) &&
        PyCObject_Check(c_obj) &&
        PyList_Check(method_list)) {

      // extract each tuple from the list, aborting if any is invalid
      QAsciiDict<PyObject> meth_list;
      int size = PyList_Size(method_list);
      for(int c=0;c<size;c++) {
        PyObject *tuple = PyList_GetItem(method_list,c);
        const char *method_signature = NULL;
        PyObject *py_method = NULL;
        if (!PyArg_ParseTuple(tuple, (char*)"sO", &method_signature, &py_method))
          return NULL;
        Py_INCREF(py_method);
        meth_list.insert(method_signature, py_method);
      }

      PCOPObject *obj = (PCOPObject*)PyCObject_AsVoidPtr(c_obj);
      if (obj) {
        if (!obj->setMethodList(meth_list)) return NULL;
      }
      Py_INCREF(Py_None);
      return Py_None;
    }
    return NULL;
  }

  PyObject *get_method_list(PyObject */*self*/, PyObject *args) {
    PyObject *c_obj;
    if (PyArg_ParseTuple(args, (char*)"O", &c_obj)) {
      if (PyCObject_Check(c_obj)) {
        PCOPObject *obj = (PCOPObject*)PyCObject_AsVoidPtr(c_obj);
        return obj->methodList();
      }
    }
    return NULL;
  }

  PyObject *connect_DCOP_Signal( PyObject */*self*/, PyObject *args) {
    const char *sender;
    const char *senderObj;
    const char *signal;
    const char *receiverObj;
    const char *slot;

    int volint = 0;
    if (PyArg_ParseTuple(args, (char*)"sssss|i", &sender, &senderObj, &signal, &receiverObj, &slot, &volint)) {
      bool success = Client::instance()->dcop()->connectDCOPSignal(QCString(sender), QCString(senderObj), QCString(signal), QCString(receiverObj), QCString(slot), (volint == 1)?true:false);
      return Py_BuildValue("i", success?1:0);
    }
    return NULL;
  }

  PyObject *disconnect_DCOP_Signal( PyObject *self, PyObject *args) {
    const char *sender;
    const char *senderObj;
    const char *signal;
    const char *receiverObj;
    const char *slot;

    if (PyArg_ParseTuple(args, (char*)"sssss", &sender, &senderObj, &signal, &receiverObj, &slot)) {
      bool success = Client::instance()->dcop()->disconnectDCOPSignal(QCString(sender), QCString(senderObj), QCString(signal), QCString(receiverObj), QCString(slot));
      return Py_BuildValue("i", success?1:0);
    }
    return NULL;
	
  }



  void delete_dcop_object(void *vp) {
    if (vp) {
      PCOPObject *obj = (PCOPObject*)vp;
      delete obj;
    }
  }

  PyObject *process_events( PyObject */*self*/, PyObject */*args*/) {
    Client::instance()->processEvents();
    Py_INCREF(Py_None);
    return Py_None;
  }

  // helpers

  PyObject *make_py_list( const QCStringList &qt_list) {
    PyObject *l = PyList_New(qt_list.count());
    uint c=0;
    for(QCStringList::ConstIterator it = qt_list.begin();
        it!=qt_list.end();
        ++it,c++)
      PyList_SetItem(l, c, PyString_FromString( (*it).data() ) );
    return l;
  }

}


PyMethodDef PCOPMethods[] = {
  { (char*)"dcop_call",  PythonDCOP::dcop_call, METH_VARARGS, (char*)"Make a call to DCOP." },
  { (char*)"app_list", PythonDCOP::application_list, METH_VARARGS, (char*)"Return a list of DCOP registered application." },
  { (char*)"obj_list", PythonDCOP::object_list, METH_VARARGS, (char*)"Return a list of objects for a DCOP registered application."},
  { (char*)"method_list", PythonDCOP::method_list, METH_VARARGS, (char*)"Return a list of methods for a DCOP object."},
  { (char*)"register_as", PythonDCOP::register_as, METH_VARARGS, (char*)"Register the application with DCOP."},
  { (char*)"create_dcop_object", PythonDCOP::create_dcop_object, METH_VARARGS, (char*)"Creates a DCOP Object instance."},
  { (char*)"process_events", PythonDCOP::process_events, METH_VARARGS, (char*)"Processes QT events."},
  { (char*)"set_method_list", PythonDCOP::set_method_list, METH_VARARGS, (char*)"Set the list of methods for a DCOP server object."},
  { (char*)"connect_dcop_signal", PythonDCOP::connect_DCOP_Signal, METH_VARARGS, (char*)"Connect a dcop signal."},
  { (char*)"disconnect_dcop_signal", PythonDCOP::disconnect_DCOP_Signal, METH_VARARGS, (char*)"Disconnect a dcop signal."},
  { NULL, NULL, 0, NULL }        /* Sentinel */
};

extern "C"
{

  void initpcop()
  {
    (void) Py_InitModule( (char*)"pcop", PCOPMethods );
  }

}


