#include <QtCore/private/qmetaobjectbuilder_p.h>

#include <QtOpenGL/QtOpenGL>
#include <QtOpenGL/QGLFunctions>

#include <QtQml/QtQml>
#include <QQmlEngine>
#include <QQmlListProperty>
#include <QDebug>

#include "govalue.h"
#include "capi.h"

namespace {

QObject *listPropertyAt(QQmlListProperty<QObject> *list, int i)
{
    GoRef ref = 0;
    auto error = objectGoRef(list->object, &ref);
    if (error)
        panicf("%s", error);

    auto memberInfo = static_cast<GoMemberInfo *>(list->data);

    return reinterpret_cast<QObject *>(hookListPropertyAt(ref, memberInfo->reflectIndex, memberInfo->reflectSetIndex, i));
}

int listPropertyCount(QQmlListProperty<QObject> *list)
{
    GoRef ref = 0;
    auto error = objectGoRef(list->object, &ref);
    if (error)
        panicf("%s", error);

    auto memberInfo = static_cast<GoMemberInfo *>(list->data);

    return hookListPropertyCount(ref, memberInfo->reflectIndex, memberInfo->reflectSetIndex);
}

void listPropertyAppend(QQmlListProperty<QObject> *list, QObject *obj)
{
    GoRef ref = 0;
    auto error = objectGoRef(list->object, &ref);
    if (error)
        panicf("%s", error);

    auto memberInfo = static_cast<GoMemberInfo *>(list->data);

    hookListPropertyAppend(ref, memberInfo->reflectIndex, memberInfo->reflectSetIndex, obj);
}

void listPropertyClear(QQmlListProperty<QObject> *list)
{
    GoRef ref = 0;
    auto error = objectGoRef(list->object, &ref);
    if (error)
        panicf("%s", error);

    auto memberInfo = static_cast<GoMemberInfo *>(list->data);

    hookListPropertyClear(ref, memberInfo->reflectIndex, memberInfo->reflectSetIndex);
}

QQmlListProperty<QObject> makeListProperty(QObject * obj, GoMemberInfo * memberInfo)
{
    return {
        obj,
        memberInfo,
        &listPropertyAppend,
        &listPropertyCount,
        &listPropertyAt,
        &listPropertyClear,
        /* TODO: implement last 2 functions for better performance. */
        /* ReplaceFunction */ nullptr,
        /* RemoveLastFunction */ nullptr,
    };
}

}

class GoValueMetaObject : public QAbstractDynamicMetaObject
{
public:
    GoValueMetaObject(QObject* value, GoRef ref, GoTypeInfo *typeInfo);

    void activatePropIndex(int propIndex);

protected:
    int metaCall(QMetaObject::Call c, int id, void **a);

private:
    QObject *value;
    GoRef ref;
    GoTypeInfo *typeInfo;
};

GoValueMetaObject::GoValueMetaObject(QObject *value, GoRef ref, GoTypeInfo *typeInfo)
    : value(value), ref(ref), typeInfo(typeInfo)
{
    //d->parent = static_cast<QAbstractDynamicMetaObject *>(priv->metaObject);
    *static_cast<QMetaObject *>(this) = *metaObjectFor(typeInfo);

    QObjectPrivate *objPriv = QObjectPrivate::get(value);
    objPriv->metaObject = this;
}

int GoValueMetaObject::metaCall(QMetaObject::Call c, int idx, void **a)
{
    //qWarning() << "GoValueMetaObject::metaCall" << c << idx;
    switch (c) {
    case QMetaObject::ReadProperty:
    case QMetaObject::WriteProperty:
        {
            // TODO Cache propertyOffset, methodOffset (and maybe qmlEngine)
            int propOffset = propertyOffset();
            if (idx < propOffset) {
                return value->qt_metacall(c, idx, a);
            }
            GoMemberInfo *memberInfo = typeInfo->fields;
            for (int i = 0; i < typeInfo->fieldsLen; i++) {
                if (memberInfo->metaIndex == idx) {
                    if (c == QMetaObject::ReadProperty) {
                        DataValue result;
                        hookGoValueReadField(qmlEngine(value), ref, memberInfo->reflectIndex, memberInfo->reflectGetIndex, memberInfo->reflectSetIndex, &result);
                        if (memberInfo->memberType == DTListProperty) {
                            if (result.dataType != DTListProperty) {
                                panicf("reading DTListProperty field returned non-DTListProperty result");
                            }
                            QQmlListProperty<QObject> *out = reinterpret_cast<QQmlListProperty<QObject> *>(a[0]);
                            *out = makeListProperty(value, memberInfo);
                            // TODO Could provide a single variable in the stack to ReadField instead.
                        } else {
                            QVariant *out = reinterpret_cast<QVariant *>(a[0]);
                            unpackDataValue(&result, out);
                        }
                    } else {
                        DataValue assign;
                        QVariant *in = reinterpret_cast<QVariant *>(a[0]);
                        packDataValue(in, &assign);
                        hookGoValueWriteField(qmlEngine(value), ref, memberInfo->reflectIndex, memberInfo->reflectSetIndex, &assign);
                        activate(value, methodOffset() + (idx - propOffset), 0);
                    }
                    return -1;
                }
                memberInfo++;
            }
            QMetaProperty prop = property(idx);
            qWarning() << "Property" << prop.name() << "not found!?";
            break;
        }
    case QMetaObject::InvokeMetaMethod:
        {
            if (idx < methodOffset()) {
                return value->qt_metacall(c, idx, a);
            }
            GoMemberInfo *memberInfo = typeInfo->methods;
            for (int i = 0; i < typeInfo->methodsLen; i++) {
                if (memberInfo->metaIndex == idx) {
                    // args[0] is the result if any.
                    DataValue args[1 + MaxParams];
                    for (int i = 1; i < memberInfo->numIn+1; i++) {
                        packDataValue(reinterpret_cast<QVariant *>(a[i]), &args[i]);
                    }
                    hookGoValueCallMethod(qmlEngine(value), ref, memberInfo->reflectIndex, args);
                    if (memberInfo->numOut > 0) {
                        unpackDataValue(&args[0], reinterpret_cast<QVariant *>(a[0]));
                    }
                    return -1;
                }
                memberInfo++;
            }
            QMetaMethod m = method(idx);
            qWarning() << "Method" << m.name() << "not found!?";
            break;
        }
    default:
        break; // Unhandled.
    }
    return -1;
}

void GoValueMetaObject::activatePropIndex(int propIndex)
{
    // Properties are added first, so the first fieldLen methods are in
    // fact the signals of the respective properties.
    int relativeIndex = propIndex - propertyOffset();
    activate(value, methodOffset() + relativeIndex, 0);
}

GoValue::GoValue(GoRef ref, GoTypeInfo *typeInfo, QObject *parent)
    : ref(ref), typeInfo(typeInfo)
{
    valueMeta = new GoValueMetaObject(this, ref, typeInfo);
    setParent(parent);
}

GoValue::~GoValue()
{
    hookGoValueDestroyed(qmlEngine(this), ref);
}

void GoValue::activate(int propIndex)
{
    valueMeta->activatePropIndex(propIndex);
}

GoPaintedValue::GoPaintedValue(GoRef ref, GoTypeInfo *typeInfo, QObject *parent)
    : ref(ref), typeInfo(typeInfo)
{
    valueMeta = new GoValueMetaObject(this, ref, typeInfo);
    setParent(parent);

    QQuickItem::setFlag(QQuickItem::ItemHasContents, true);
    QQuickPaintedItem::setRenderTarget(QQuickPaintedItem::FramebufferObject);
}

GoPaintedValue::~GoPaintedValue()
{
    hookGoValueDestroyed(qmlEngine(this), ref);
}

void GoPaintedValue::activate(int propIndex)
{
    valueMeta->activatePropIndex(propIndex);
}

void GoPaintedValue::paint(QPainter *painter)
{
    painter->beginNativePainting();
    hookGoValuePaint(qmlEngine(this), ref, typeInfo->paint->reflectIndex);
    painter->endNativePainting();
}

QMetaObject *metaObjectFor(GoTypeInfo *typeInfo)
{
    if (typeInfo->metaObject) {
            return reinterpret_cast<QMetaObject *>(typeInfo->metaObject);
    }

    QMetaObjectBuilder mob;
    if (typeInfo->paint) {
        mob.setSuperClass(&QQuickPaintedItem::staticMetaObject);
    } else {
        mob.setSuperClass(&QObject::staticMetaObject);
    }
    mob.setClassName(typeInfo->typeName);
    mob.setFlags(QMetaObjectBuilder::DynamicMetaObject);

    GoMemberInfo *memberInfo;
    
    memberInfo = typeInfo->fields;
    int relativePropIndex = mob.propertyCount();
    for (int i = 0; i < typeInfo->fieldsLen; i++) {
        mob.addSignal("__" + QByteArray::number(relativePropIndex) + "()");
        const char *typeName = "QVariant";
        if (memberInfo->memberType == DTListProperty) {
            typeName = "QQmlListProperty<QObject>";
        }
        QMetaPropertyBuilder propb = mob.addProperty(memberInfo->memberName, typeName, relativePropIndex);
        propb.setWritable(true);
        memberInfo->metaIndex = relativePropIndex;
        memberInfo++;
        relativePropIndex++;
    }

    memberInfo = typeInfo->methods;
    int relativeMethodIndex = mob.methodCount();
    for (int i = 0; i < typeInfo->methodsLen; i++) {
        if (*memberInfo->resultSignature) {
            mob.addMethod(memberInfo->methodSignature, memberInfo->resultSignature);
        } else {
            mob.addMethod(memberInfo->methodSignature);
        }
        memberInfo->metaIndex = relativeMethodIndex;
        memberInfo++;
        relativeMethodIndex++;
    }

    // TODO Support default properties.
    //mob.addClassInfo("DefaultProperty", "objects");

    QMetaObject *mo = mob.toMetaObject();

    // Turn the relative indexes into absolute indexes.
    memberInfo = typeInfo->fields;
    int propOffset = mo->propertyOffset();
    for (int i = 0; i < typeInfo->fieldsLen; i++) {
        memberInfo->metaIndex += propOffset;
        memberInfo++;
    }
    memberInfo = typeInfo->methods;
    int methodOffset = mo->methodOffset();
    for (int i = 0; i < typeInfo->methodsLen; i++) {
        memberInfo->metaIndex += methodOffset;
        memberInfo++;
    }

    typeInfo->metaObject = mo;
    return mo;
}

// vim:ts=4:sw=4:et:ft=cpp
