#include <qcstring.h>
#include <qdatastream.h>
#include <dcopclient.h>
#include <dcopref.h>

#include <qfile.h>
#include <qtextstream.h>

#include "pcop.h"

#include <assert.h>

static PyObject* createObject( const char* appname, const char* objname );

////////////////////////////////////////////
//
// Some headers from the qt bindings
//
////////////////////////////////////////////

QRect* pyqt_qrect_toCpp( PyObject* obj );
PyObject* pyqt_qrect_toPython( const QRect& ptr );

////////////////////////////////////////////
//
// ParseTree
//
////////////////////////////////////////////

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

    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()
{
    delete m_leftType;
    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
{
    if ( m_type == "QString" )
    {
        if ( PyString_Check( obj ) )
        {
            QString s( PyString_AsString( obj ) );
            str << s;
        }
        else
            return FALSE;
    }
    else if ( m_type == "QCString" )
    {
        if ( PyString_Check( obj ) )
        {
            QCString s( PyString_AsString( obj ) );
            str << s;
        }
        else
            return FALSE;
    }
    else if ( m_type == "QValueList" || m_type == "QStringList" || m_type == "QCStringList" )
    {
        if ( PyList_Check( obj ) )
        {
            PCOPType *type = m_leftType;
            if ( m_type == "QStringList" )
                type = new PCOPType( "QString" );
            if ( m_type == "QCStringList" )
                type = new PCOPType( "QCString" );

            int count = PyList_Size( obj );

            str << (Q_INT32)count;

            for ( int i = 0; i < count; ++i )
                type->marshal( PyList_GetItem( obj, i ), str );

            if ( type != m_leftType )
                delete type;
        }
        else
            return FALSE;
    }
    else if ( m_type == "QRect" )
    {
        QRect* rect = pyqt_qrect_toCpp( obj );
        if ( !rect )
            return FALSE;
        str << *rect;
    }
    else if ( m_type == "QMap" )
    {
        if ( PyDict_Check( obj ) )
        {
            int count = PyDict_Size( obj );

            str << (Q_INT32)count;

            int pos = 0;

            PyObject *key, *val;

            while ( PyDict_Next( obj, &pos, &key, &val ) == 1 )
            {
                m_leftType->marshal( key, str );
                m_rightType->marshal( val, str );
            }
        }
        else
            return FALSE;
    }
    else if ( m_type == "DCOPRef" )
    {
        // ### leak: PyObject_Str
        QCString refStr = PyString_AsString( PyObject_Str( obj  ) );
        if ( refStr.left( 11 ) != "DCOPObject(" )
            return FALSE;

        refStr = refStr.mid( 11 );
        int commaPos = refStr.find( ',' );

        if ( commaPos == -1 )
            return FALSE;

        QCString appId = refStr.left( commaPos );

        refStr = refStr.mid( commaPos + 1 );

        if ( refStr.right( 0 ) != ')' )
            return FALSE;

        refStr.truncate( refStr.length() - 1 );

        DCOPRef ref( appId, refStr );
        str << ref;

        return TRUE;
    }
    else if ( m_type == "bool" )
    {
        bool b;

        // ### leak: PyObject_Str
        QCString boolStr = PyString_AsString( PyObject_Str( obj ) );

        if ( boolStr.lower() == "true" || boolStr == "1" )
            b = TRUE;
        else if ( boolStr.lower() == "false" || boolStr == "0" )
            b = FALSE;
        else
            return FALSE;

        str << (Q_INT8)b;

        return TRUE;
    }
    else if ( m_type == "int" )
    {
        if ( PyInt_Check( obj ) )
        {
            // ### FIXME: 64 bit systems
            str << (Q_INT32)PyInt_AsLong( obj );
            return TRUE;
        }
        else
            return FALSE;
    }
    else if ( m_type == "uint" )
    {
        if ( PyInt_Check( obj ) )
        {
            // ### FIXME: 64 bit systems
            str << (Q_UINT32)PyInt_AsLong( obj );
            return TRUE;
        }
        else
            return FALSE;
    }
    else if ( m_type == "double" )
    {
        if ( PyFloat_Check( obj ) )
        {
            str << PyFloat_AsDouble( obj );
            return true;
        }
        else
            return false;
    }
    else if ( m_type == "QByteArray" )
    {
        // Marshal obj using the buffer interface.
        // As of python 1.5.2 strings and buffer objects
        // implement the buffer interface 
        PyBufferProcs *pb = obj->ob_type->tp_as_buffer;
                
        if ( pb && pb->bf_getreadbuffer && pb->bf_getsegcount )
        {
            // Get the number of buffer segments 
            int seg_count = (pb->bf_getsegcount)(obj, 0);
         
            if ( seg_count != 1 )
                // Can't handle more (or less) than 1 buffer segment
                // at the moment
                return FALSE;

            // Get buffer size and data
            void *data;
            int size;

            if ( (size = (pb->bf_getreadbuffer)(obj, 0, &data)) < 0 )
                // Umm... what, no buffer??
                return FALSE;
            
            QByteArray a;
            a.setRawData( (const char*)data, size );
            str << a;
            a.resetRawData( (const char*)data, size );

            return TRUE;
        }
        else 
            // obj does not implement the buffer interface
            return FALSE;
    }
    else
        return FALSE;

    return TRUE;
}

PyObject* PCOPType::demarshal( QDataStream& str )
{
    if ( m_type == "QString" )
    {
        QString s;
        str >> s;
        return PyString_FromString( s.utf8().data() );
    }
    else if ( m_type == "QCString" )
    {
        QCString s;
        str >> s;
        return PyString_FromString( s.data() );
    }
    else if ( m_type == "QRect" )
    {
        QRect r;
        str >> r;
        return pyqt_qrect_toPython( r );
    }
    else if ( m_type == "QValueList" )
    {
        Q_UINT32 count;
        str >> count;

        PyObject* l = PyList_New( count );
        if ( !m_leftType )
            return l;

        for( int i = 0; i < (int)count; ++i )
        {
            PyList_SetItem( l, i, m_leftType->demarshal( str ) );
        }

        return l;
    }
    else if ( m_type == "QStringList" )
    {
        Q_UINT32 count;
        str >> count;

        PyObject* l = PyList_New( count );

        for( int i = 0; i < (int)count; ++i )
        {
            QString s;
            str >> s;
            PyObject* o = PyString_FromString( s.utf8().data() );
            PyList_SetItem( l, i, o );
        }

        return l;
    }
    else if ( m_type == "QCStringList" )
    {
        Q_UINT32 count;
        str >> count;

        PyObject* l = PyList_New( count );

        for( int i = 0; i < (int)count; ++i )
        {
            QCString s;
            str >> s;
            PyObject* o = PyString_FromString( s.data() );
            PyList_SetItem( l, i, o );
        }

        return l;
    }
    else if ( m_type == "QMap" )
    {
        PyObject* d = PyDict_New();

        if ( !m_leftType || !m_rightType )
            return d;

        Q_INT32 count;
        str >> count;

        for (int i = 0; i < (int)count; ++i )
        {
            PyObject *key = m_leftType->demarshal( str );
            PyObject *val = m_rightType->demarshal( str );
            PyDict_SetItem( d, key, val );
        }

        return d;
    }
    else if ( m_type == "DCOPRef" )
    {
        DCOPRef ref;
        str >> ref;
        return createObject( ref.app(), ref.object() );
    }
    else if ( m_type == "bool" )
    {
        Q_INT8 i;
        str >> i;
        return PyInt_FromLong( (long)i );
    }
    else if ( m_type == "int" )
    {
        Q_INT32 i;
        str >> i;
        return PyInt_FromLong( (long)i );
    }
    else if ( m_type == "double" )
    {
        double d;
        str >> d;
        return PyFloat_FromDouble( d );
    }
    else if ( m_type == "QByteArray" )
    {
        // Demarshal to a writable buffer object
        QByteArray a;
        str >> a;
        
        uint size = a.size();
        char *data = a.data();

        // Create a new buffer object and copy the data.
        // Don't use PyBuffer_FromMemory() and the likes since
        // that wouldn't give correct allocation and deallocation.

        PyObject *buffer_obj = PyBuffer_New( size );

        if ( !buffer_obj )
            return NULL;

        PyBufferProcs *pb = buffer_obj->ob_type->tp_as_buffer;
     
        void *buffer_data;

        (pb->bf_getwritebuffer)( buffer_obj, 0, &buffer_data );

        for ( uint i = 0; i < size; i++ )
            ((char*)buffer_data)[i] = data[i];

        return buffer_obj;
    }
    // By default return an empty string.
    return PyString_FromString( "" );
}

bool PCOPType::isMarshallable( PyObject *obj )
{
    if ( m_type == "QString" || m_type == "QCString" )
        return PyString_Check( obj ) == 1;

    if ( m_type == "QRect" ) // ### CHECK MISSING!!!
        return true;

    if ( m_type == "QValueList" )
        return PyList_Check( obj ) == 1; // ### check m_leftType!

    if ( m_type == "QStringList" || m_type == "QCStringList" )
        return PyList_Check( obj ) == 1; // ### check list items!

    if ( m_type == "QMap" )
        return PyDict_Check( obj ) == 1; // ### check m_leftType/m_rightType!

    if ( m_type == "DCOPRef" )
        return true; // ### CHECK MISSING!!!

    if ( m_type == "double" )
        return PyFloat_Check( obj ) == 1;

    if ( m_type == "int" || m_type == "bool" )
        return PyInt_Check( obj );

    if ( m_type == "QByteArray" ) {
        // Look for an implemented buffer interface
        PyBufferProcs *pb = obj->ob_type->tp_as_buffer;
        return (pb != 0 &&
                pb->bf_getreadbuffer != 0 &&
                pb->bf_getsegcount != 0);
    }

    return false;
}

PCOPMethod::PCOPMethod( const QCString& signature )
{
    // qDebug("Parsing %s", signature.data() );

    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 commata? 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 += ")";

    // qDebug("Parsing successfull: name=%s sig=%s", m_name.data(), m_signature.data() );
}

PCOPMethod::~PCOPMethod()
{
    delete m_type;
}

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

QCString PCOPMethod::signature() const
{
    return m_signature;
}

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;
}

////////////////////////////////////////////////
//
// Utilities
//
////////////////////////////////////////////////

static DCOPClient* dcop = 0;

static DCOPClient* dcopClient()
{
    if ( !dcop )
    {
        dcop = new DCOPClient;
        if ( !dcop->attach() )
            qDebug("Could not Attached");
    }

    return dcop;
}

static PyObject* dcop_call( PyObject* self, PyObject* args );
static PyObject* application_list( PyObject *self, PyObject *args );

static PyMethodDef PCOPMethods[] = {
         { "dcop_call",  dcop_call, METH_VARARGS },
         { "application_list", application_list, METH_VARARGS },
         { NULL,      NULL }        /* Sentinel */
};

PyObject* s_dcop_module = 0;
PyObject* s_qt_module = 0;

extern "C"
{

void initpcop()
{
    (void) Py_InitModule( "pcop", PCOPMethods );
    s_dcop_module = PyImport_ImportModule( "dcop" );
    if ( !s_dcop_module )
        qDebug("Could not import dcop module");
    s_qt_module = PyImport_ImportModule( "qt" );
    if ( !s_qt_module )
        qDebug("Could not import qt module");
    dcopClient();
}

}

static PyObject* createObject( const char* appname, const char* objname )
{
    if ( !s_dcop_module )
        return 0;

    PyObject* dict = PyModule_GetDict( s_dcop_module );
    if ( !dict )
        return 0;

    PyObject* cl = PyDict_GetItemString( dict, "DCOPObject" );
    if ( !cl )
        return 0;

    PyObject* args = PyTuple_New( 2 );
    PyTuple_SetItem( args, 0, PyString_FromString( appname ) );
    PyTuple_SetItem( args, 1, PyString_FromString( objname ) );

    return PyObject_CallObject( cl, args );
}

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

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

    if ( !PyArg_ParseTuple( args, "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 = dcopClient();

    //
    // 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();
    qDebug("The signature is %s", signature.data() );

    qDebug("The method takes %i parameters", m->paramCount() );

    //
    // 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 ) )
        {
            qDebug("QD: Could not marshal paramater %i", p );
            PyErr_SetString( PyExc_RuntimeError, "DCOP: marshaling failed" );
            return NULL;
        }
    }

    qDebug("Calling %s %s %s", appname.data(), objname.data(), signature.data() );

    ASSERT( dcopClient() != 0 );

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

    qDebug("The return type is %s", replyType.data() );

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

    // return createObject( "kspread", "fuckmeup" );
    // Py_INCREF(Py_None);
    // return Py_None;
}

static PyObject* application_list( PyObject *self, PyObject *args )
{
    QCStringList apps = dcopClient()->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;
}
