#include <qapplication.h>
#include <qptrdict.h>
#include <qpushbutton.h>
#include <qvariant.h>
#include <qmetaobject.h>
#include <qstringlist.h>
#include <qpointarray.h>

#include "pyqt.h"

/*************************************************************
 *
 * Collection of globals
 *
 *************************************************************/

static PyObject* pyqt_qt_module = 0;
static PyObject* pyqt_qt_dict = 0;

/*************************************************************
 *
 * Classes
 *
 *************************************************************/

enum PyQt_ClassId { Size = 5,
		    Point = 6,
		    Rect = 7,
		    Font = 8,
		    Color = 9,
		    WMatrix = 10,
		    Image = 11
};

struct PyQt_Enum
{
    int         value;
    const char* name;
};

enum PyQt_Owner { Toolkit, Python };

struct PyQt_ClassInfo;

static PyQt_ClassInfo* pyqt_qsize_class = 0;
static PyQt_ClassInfo* pyqt_qpoint_class = 0;
static PyQt_ClassInfo* pyqt_qrect_class = 0;
static PyQt_ClassInfo* pyqt_qfont_class = 0;
static PyQt_ClassInfo* pyqt_qcolor_class = 0;
static PyQt_ClassInfo* pyqt_qwmatrix_class = 0;
static PyQt_ClassInfo* pyqt_qimage_class = 0;

static PyQt_ClassInfo** pyqt_classes[] = {
    &pyqt_qsize_class,
    &pyqt_qpoint_class,
    &pyqt_qrect_class,
    &pyqt_qfont_class,
    &pyqt_qcolor_class,
    &pyqt_qwmatrix_class,
    &pyqt_qimage_class,
    NULL
};

/*************************************************************
 *
 * Class description
 *
 *************************************************************/

typedef PyObject* (*PyQt_Method)( void* ptr, PyObject* obj, PyObject* arg_tuple );
typedef void (*PyQt_Free)( void* );

/**
 * Describes a class, its methods, destructor, signals, ID etc. and
 * its base classes.
 */
struct PyQt_ClassInfo
{
    // Pointer to the jump table of the base class
    PyQt_ClassInfo*     next;
    // Function pointer to the function that can delete the Qt object
    PyQt_Free           freeQt;
    /**
     * Amount of methods in the method table. Each method table is terminated
     * by 0, but this variable is needed to check quickly wether an index to
     * the method table are out of bound.
     */
    int                 methodCount;
    // Pointer to the method table
    PyQt_Method*        methods;
    /**
     * Class ID to compare classes fast. The slow way is
     * to compare the className.
     */
    PyQt_ClassId        classid;
    // The class name
    const char*         className;
    /**
     * A pointer to the corresponding python class object.
     * Do NOT call Py_DECREF on this pointer.
     *
     * This pointer is needed to create new instances of this class.
     */
    PyObject*           classObject;
};

/*************************************************************
 *
 * Pointer mapping
 *
 *************************************************************/

/**
 * This data structure is used to map from qt objects to
 * python objects and the other way round.
 *
 * Python objects hold a pointer to the qt object in their
 * _ptr_ attribute. And with this void* one can query for
 * the corresponding PyQt_ObjectMapping.
 *
 * @see #pyqt_ptrdict
 */
struct PyQt_ObjectMapping
{
    /**
     * The python object associated with the Qt object.
     */
    PyObject*           object;
    /**
     * Tells wether the python object is owned by the toolkit.
     * In this case Py_DECREF has to be called if the toolkits
     * qt object dies, otherwise the qt object is deleted if
     * the python object dies.
     */
    PyQt_Owner          owner;
    /**
     * Class of this object.
     */
    PyQt_ClassInfo*     classInfo;
};

/**
 * Maps qt object pointers to PyQt_ObjectMapping.
 */
QPtrDict<PyQt_ObjectMapping>* pyqt_ptrdict = 0;

static void initPointerDict()
{
    if ( pyqt_ptrdict == 0 )
    {
	pyqt_ptrdict = new QPtrDict<PyQt_ObjectMapping>;
	pyqt_ptrdict->setAutoDelete( TRUE );
    }
}

static void unsetPointer( PyObject* obj )
{
    initPointerDict();

    // Store Qt pointer in Python object
    PyObject* cobj = PyCObject_FromVoidPtr( 0, 0 );
    PyObject_SetAttrString( obj, "_ptr_", cobj );
    Py_DECREF( cobj );
}

/**
 * Called if the python object dies.
 */
static void pyqt_destroy( void* ptr )
{
    if ( !ptr )
	return;

    initPointerDict();

    PyQt_ObjectMapping* mapping = (*pyqt_ptrdict)[ ptr ];
    if ( !mapping )
    {
	qDebug("Could not find mapping");
	return;
    }

    PyQt_Owner owner = mapping->owner;
    PyQt_Free freeqt = mapping->classInfo->freeQt;
    pyqt_ptrdict->remove( ptr );
	
    if ( owner == Python )
	(*freeqt)( ptr );
}

/**
 * Returns the class structure for the class identified by @p classid or 0 if the instance
 * identified by @p mapping does not inherit the requested class.
 */
static PyQt_ClassInfo* getClassInfo( PyQt_ObjectMapping* mapping, PyQt_ClassId classid )
{
    PyQt_ClassInfo* j = mapping->classInfo;
    while( j )
	if ( j->classid == classid )
	    return j;
	else
	    j = j->next;

    return 0;
}

/**
 * Returns the class structure for the class identified by @p class_name or 0 if the
 * class is not supported.
 */
static PyQt_ClassInfo* getClassInfo( PyQt_ClassId classid )
{
    // Loop over all registered classes
    PyQt_ClassInfo* info = 0;
    int i = 0;
    while( !info )
    {
	if ( pyqt_classes[i] == NULL )
	    return NULL;
	if ( (*(pyqt_classes[i]))->classid == classid )
	    info = *(pyqt_classes[i]);
	else
	    ++i;
    }

    return info;
}

/**
 * Returns the class structure for the class identified by @p class_name or 0 if the
 * class is not supported.
 */
static PyQt_ClassInfo* getClassInfo( const char* class_name )
{
    // Loop over all registered classes
    PyQt_ClassInfo* info = 0;
    int i = 0;
    while( !info )
    {
	if ( pyqt_classes[i] == NULL )
	    return NULL;
	if ( strcmp( (*(pyqt_classes[i]))->className, class_name ) == 0 )
	    info = *(pyqt_classes[i]);
	else
	    ++i;
    }

    return info;
}

/**
 * Associates the qt object @p ptr with the python object @p obj.
 *
 * @param class_info identifies the class of @p obj.
 * @param set_pointer tells wether the pointer to the qt object needs to be stored
 *        in the Python object. Set this to FALSE if the Python object already has
 *        the _ptr_ attribute set.
 */
static void mapObject( PyObject* obj, void* ptr, PyQt_ClassInfo* class_info, PyQt_Owner owner = Toolkit, bool set_pointer = TRUE )
{
    initPointerDict();

    // Create mapping from Qt -> Python
    PyQt_ObjectMapping* mapping = new PyQt_ObjectMapping;
    mapping->object = obj;
    mapping->owner = owner;
    mapping->classInfo = class_info;
    pyqt_ptrdict->insert( ptr, mapping );

    if ( set_pointer )
    {
	// Store Qt pointer in Python object
	PyObject* cobj = PyCObject_FromVoidPtr( ptr, pyqt_destroy );
	int i = PyObject_SetAttrString( obj, "_ptr_", cobj );
	ASSERT( i != -1 );
	Py_DECREF( cobj );
    }
}

/**
 * Returns the void pointer associated with the python object.
 */
static void* getPointer( PyObject* obj )
{
    if ( !obj )
	return 0;

    PyObject* cobj = PyObject_GetAttrString( obj, "_ptr_" );
    if ( !cobj )
	return 0;

    void* ptr = PyCObject_AsVoidPtr( cobj );
    Py_DECREF( cobj );

    return ptr;
}

/**
 * Returns the mapping associated with the python object.
 *
 * Calls @ref getPointer.
 */
static PyQt_ObjectMapping* getMapping( PyObject* obj )
{
    initPointerDict();

    if ( !obj )
	return 0;

    void* ptr = getPointer( obj );
    if ( ptr )
	return (*pyqt_ptrdict)[ ptr ];
    return 0;
}

/**
 * Returns the mapping associated with a C++ object.
 */
static PyQt_ObjectMapping* getMapping( void* ptr )
{
    initPointerDict();

    return (*pyqt_ptrdict)[ ptr ];
}

/**
 * Returns the void pointer associated with the python object,
 * but only if @p obj inherits the class specified by @p classid.
 *
 * This is used in the pyqt_XXXXX_toCpp functions.
 */
static void* getPointer( PyObject* obj, PyQt_ClassId classid )
{
    if ( !obj )
	return 0;

    void* ptr = getPointer( obj );
    PyQt_ObjectMapping* mapping = getMapping( ptr );
    if ( !mapping )
	return 0;
    if ( !getClassInfo( mapping, classid ) )
	return 0;
    return ptr;
}

/**
 * Returns a pyobject which is associated with @p object. You need to
 * call Py_DECREF on the returned object.
 *
 * The method first looks in @ref pyqt_ptrdict. If there is no matching
 * python object then a new one is created. If object->className() is
 * not supported by the bindings then 0 is returned.
 *
 * This method is used to qt classes which do not derive QObject like QRect and QSize.
 */
static PyObject* qobjectToPython( void* object, PyQt_ClassId classid )
{
    // Null pointer ?
    if ( object == 0 )
    {
	Py_INCREF( Py_None );
	return Py_None;
    }

    // Do we already have such a PyObject ?
    PyQt_ObjectMapping* mapping = getMapping( object );
    if ( mapping )
    {
	Py_INCREF( mapping->object );
	return mapping->object;
    }

    // Find the most derived type of the object that is supported
    // by this bindings.
    PyQt_ClassInfo* class_info = getClassInfo( classid );
    if ( !class_info )
	return 0;

    // Store the pointer to the qt object
    PyObject* cobj = PyCObject_FromVoidPtr( object, pyqt_destroy );
    // Perpare to call the classes constructor.
    PyObject* arglist = Py_BuildValue("(O)", cobj );
    // Call
    PyObject* result = PyEval_CallObject( class_info->classObject, arglist );
    Py_DECREF( arglist );
    Py_DECREF( cobj );

    // Ok ?
    if ( result )
    {
	// Associate the object with the new PyObject.
	mapObject( result, object, class_info, Python, FALSE );
    }

    return result;
}

/**
 * @return TRUE if the obj inherits the class identified by @p classid.
 */
static bool inherits( PyObject* obj, PyQt_ClassId classid )
{
    if ( !obj )
	return FALSE;

    PyQt_ObjectMapping* mapping = getMapping( obj );
    if ( !obj )
	return FALSE;

    return ( getClassInfo( mapping, classid ) != 0 );
}

/*************************************************************
 *
 * Utilities
 *
 *************************************************************/

/**
 * Counts the methods in a method table. This is done by searching
 * for the terminating NULL entry.
 *
 * This method is usefuk for creating the PyQt_ClassInfo.
 */
static int methodCount( PyQt_Method* methods )
{
    int count = 0;
    while ( *methods )
    {
	++methods;
	++count;
    }

    return count;
}


/**
 * Takes a table of PyQt_Enum entries and searches for the entry with
 * the requested name.
 *
 * @return a pointer to the requested PyQt_Enum struct or 0 on failure.
 */
static PyQt_Enum* getEnumByName( PyQt_Enum* enums, const char* name )
{
    while( enums && enums->name )
    {
	if ( strcmp( name, enums->name ) == 0 )
	    return enums;
	++enums;
    }

    return 0;
}

static bool enumToCpp( PyQt_Enum* enums, const char* name, int* value )
{
    PyQt_Enum* e = getEnumByName( enums, name );
    if ( !e )
	return FALSE;

    *value = e->value;

    return TRUE;
}

/**
 * Takes a table of PyQt_Enum entries and searches for the entry with
 * the requested value.
 *
 * @return a pointer to the requested PyQt_Enum struct or 0 on failure.
 */
static PyQt_Enum* getEnumByValue( PyQt_Enum* enums, int value )
{
    while( enums && enums->name )
    {
	if ( value == enums->value )
	    return enums;
	++enums;
    }

    return 0;
}

static PyObject* enumToPython( PyQt_Enum* enums, int value )
{
    PyQt_Enum* e = getEnumByValue( enums, value );
    if ( !e )
	// Indicate an error
	return NULL;

    return PyString_FromString( e->name );
}

/*************************************************************
 *
 * QByteArray
 *
 *************************************************************/

/**
 * This class is used to wrap QByteArrays for python.
 * The gods of Qt have made dealing with raw data a bit
 * complicated so we optimize a little here.
 */
class PyQt_ByteArray : public QByteArray
{
public:
    PyQt_ByteArray( const char* ptr, int i );
    ~PyQt_ByteArray();

private:
    const char* p;
    int size;
};

PyQt_ByteArray::PyQt_ByteArray( const char* ptr, int i )
    : QByteArray(), p( ptr ), size( i )
{
    setRawData( p, size );
}

PyQt_ByteArray::~PyQt_ByteArray()
{
    resetRawData( p, size );
}

/**
 * Convert a QByteArray to a python string object. Python strings
 * may even include 0 so this is save.
 */
static PyObject* pyqt_qbytearray_toPython( const QByteArray& arr )
{
    return PyString_FromStringAndSize( arr.data(), arr.size() );
}

/*************************************************************
 *
 * QPointArray
 *
 *************************************************************/

static QPoint* pyqt_qpoint_toCpp( PyObject* obj );
static PyObject* pyqt_qpoint_toPython( const QPoint& ptr );

/**
 * A point array is mapped to a list of QPoints.
 */

static bool pointArrayToCpp( PyObject* obj, QPointArray* lst )
{
    if ( !PyList_Check( obj ) )
	return FALSE;

    int max = PyList_Size( obj );
    lst->resize( max );
    for( int i = 0; i < max; ++i )
    {
	PyObject* l = PyList_GetItem( obj, i );
	if ( !l || !inherits( l, Point ) )
	    return FALSE;
	lst->setPoint( i, *pyqt_qpoint_toCpp( l ) );
    }

    return TRUE;
}

static PyObject* pyqt_qpointarray_toPython( const QPointArray& lst )
{
    PyObject* obj = PyList_New( lst.count() );

    for( int i = 0; i < (int)lst.count(); ++i )
	PyList_SetItem( obj, i, pyqt_qpoint_toPython( lst.point( i ) ) );

    return obj;
}

/*************************************************************
 *
 * QStringList
 *
 *************************************************************/

static bool stringListToCpp( PyObject* obj, QStringList* lst )
{
    if ( !PyList_Check( obj ) )
	return FALSE;

    int max = PyList_Size( obj );
    for( int i = 0; i < max; ++i )
    {
	PyObject* l = PyList_GetItem( obj, i );
	if ( !l || !PyString_Check( l ) )
	    return FALSE;
	lst->append( PyString_AsString( l ) );
    }

    return TRUE;
}

static PyObject* pyqt_qstringlist_toPython( const QStringList& lst )
{
    PyObject* obj = PyList_New( lst.count() );

    QStringList::ConstIterator it = lst.begin();
    int i = 0;
    for( ; it != lst.end(); ++it, ++i )
	PyList_SetItem( obj, i, PyString_FromString( (*it).utf8() ) );

    return obj;
}

/*************************************************************
 *
 * Include other classes
 *
 *************************************************************/

#include "pyqt_size.cpp"
#include "pyqt_point.cpp"
#include "pyqt_rect.cpp"
#include "pyqt_font.cpp"
#include "pyqt_color.cpp"
#include "pyqt_wmatrix.cpp"
#include "pyqt_image.cpp"

/*************************************************************
 *
 *
 *
 *************************************************************/

static PyObject* pyqt_init( PyObject*, PyObject* )
{
    static bool init_done = FALSE;
    if ( init_done )
    {
	Py_INCREF( Py_None );
	return Py_None;
    }

    init_done = TRUE;

    pyqt_qt_module = PyImport_ImportModule( "qt" );
    // ##### Better error handling
    ASSERT( pyqt_qt_module );
    pyqt_qt_dict = PyModule_GetDict( pyqt_qt_module );

    // Init classes
    pyqt_qsize_init();
    pyqt_qpoint_init();
    pyqt_qrect_init();
    pyqt_qfont_init();
    pyqt_qcolor_init();
    pyqt_qwmatrix_init();
    pyqt_qimage_init();
	
    PyObject* cobj = PyCObject_FromVoidPtr( 0, 0 );
    return cobj;
}

// ######## Security: Check wether one may call a certain method
// via the constructor gate at all.
static PyObject* pyqt_constructor_gate( PyObject* /* self */, PyObject* args )
{
    int command;
    PyObject* tuple;
    PyObject* obj;
    PyQt_ClassId classid;

    if ( !PyArg_ParseTuple(args, "OiiO", &obj, &classid, &command, &tuple ) )
    {
	qDebug("4");
	return NULL;
    }

    if ( !obj || !tuple )
    {
	qDebug("3");
	return NULL;
    }
    if ( !PyTuple_Check( tuple ) )
    {
	qDebug("2");
	return NULL;
    }

    PyQt_ClassInfo* table = 0;
    int i = 0;
    while( !table )
    {
	if ( pyqt_classes[i] == NULL )
	    return NULL;
	if ( (*(pyqt_classes[i]))->classid == classid )
	    table = *(pyqt_classes[ i ]);
	else
	    ++i;
    }
    ASSERT( table );

    if ( command >= table->methodCount || command < 0 )
    {
	qDebug("6");
	return NULL;
    }

    return (*table->methods[ command ])( 0, obj, tuple );
}

// ######## Security: Check wether one may call a certain method
// via the static gate at all.
static PyObject* pyqt_static_gate( PyObject* /* self */, PyObject* args )
{
    int command;
    PyObject* tuple;
    PyQt_ClassId classid;

    if ( !PyArg_ParseTuple(args, "iiO", &classid, &command, &tuple ) )
    {
	qDebug("4");
	return NULL;
    }

    if ( !PyTuple_Check( tuple ) )
    {
	qDebug("2");
	 return NULL;
    }

    PyQt_ClassInfo* table = 0;
    int i = 0;
    while( !table )
    {
	if ( pyqt_classes[i] == NULL )
	    return NULL;
	if ( (*(pyqt_classes[i]))->classid == classid )
	    table = *(pyqt_classes[ i ]);
	else
	    ++i;
    }
    ASSERT( table );

    if ( command >= table->methodCount || command < 0 )
    {
	qDebug("6");
	return NULL;
    }

    return (*table->methods[ command ])( 0, 0, tuple );
}

// ######## Security: Check wether one may call a certain method
// via this gate at all.
static PyObject* pyqt_gate( PyObject* /* self */, PyObject* args )
{
    int command;
    PyObject* tuple;
    PyObject* obj;
    PyQt_ClassId classid;

    if ( !PyArg_ParseTuple(args, "OiiO", &obj, &classid, &command, &tuple ) )
    {
	qDebug("4");
	return NULL;
    }
    if ( !PyTuple_Check( tuple ) )
    {
	qDebug("2");
	 return NULL;
    }

    PyQt_ClassInfo* table = 0;
    PyQt_ObjectMapping* mapping = getMapping( obj );
    if ( mapping )
	table = getClassInfo( mapping, classid );
    if ( !table )
    {
	qDebug("5");
	return NULL;
    }
    if ( command >= table->methodCount || command < 0 )
    {
	qDebug("6");
	return NULL;
    }

    return (*table->methods[ command ])( getPointer( obj ), obj, tuple );
}

static PyMethodDef pyqt_methods[] = {
    	{"init",  pyqt_init, METH_VARARGS, NULL},	
        {"gate",  pyqt_gate, METH_VARARGS, NULL},
	{"static_gate",  pyqt_static_gate, METH_VARARGS, NULL},
	{"constructor_gate",  pyqt_constructor_gate, METH_VARARGS, NULL},	
        {NULL,    NULL,  0,  NULL }        /* Sentinel */
};

extern "C"
{

void initpyqt()
{
    (void) Py_InitModule("pyqt", pyqt_methods);
}

}
