#include "qgpropertywidget.h"

#include <QTreeView>
#include <QTreeWidgetItem>
#include <QStandardItemModel>
#include <QStandardItem>

#include <QWidget>
#include <QVBoxLayout>
#include <QLabel>

#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaEnum>

#include <QStyledItemDelegate>
#include <QItemDelegate>

#include <QLineEdit>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>

#include "mydebug.h"

class QgPropertyWidget::Implementation
{
public:
    QWidget* w;
    QLabel* lbl;
    QVBoxLayout* ly;
    QTreeView* t;
    QStandardItemModel* m;

    QObject* obj;

    QDialogButtonBox* bb;
};


enum DelegateType
{
    DT_Enum=QVariant::UserType+1,
    DT_Checkbox,
    DT_Combo
};

const int MyTypeRole = Qt::UserRole + 1;

class PropertyItem: public QStandardItem
{
public:
    QObject& obj;
    const QMetaObject& mt;
    QMetaProperty mp;
    int index;
    QStandardItem& value;
    QStandardItem& label;

    virtual ~PropertyItem()
    {
        //MyDebug()<<"PropertyItem::~PropertyItem()";
    }

    PropertyItem(QObject& o, int i):
        obj(o),
        mt(*o.metaObject()),
        mp(mt.property(i)),
        index(i),
        value(*this),
        label(*new QStandardItem)
    {
        label.setData(mp.name(), Qt::DisplayRole);
        label.setEditable(false);

        QString description; //=mt.classInfo(mt.indexOfClassInfo("description")).value();
        description+=QString(" (")+QString(mp.typeName())+QString(")");
        label.setToolTip(description);
        label.setData(MyTypeRole, reinterpret_cast<const unsigned long long>(&obj));

        value.setData(reinterpret_cast<const unsigned long long>(this), MyTypeRole);
        value.setEnabled(mp.isValid() && mp.isReadable());

        value.setData("(invalid)", Qt::DisplayRole);
        value.setEditable(false);

        propertyInit();
    }

    virtual void propertyInit()
    {
        value.setEditable(mp.isWritable());
        value.setData(mp.read(&obj), Qt::DisplayRole);
    }
    virtual QString displayText() const
    {
        return mp.read(&obj).toString();
    }
    virtual QWidget* createEditor(QWidget */*parent*/, const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const
    {
        return 0;
    }
    virtual bool updateEditorGeometry(QWidget */*editor*/, const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const
    {
        return false;
    }

    virtual bool onSetModelData(QWidget* /*editor*/, QAbstractItemModel */*model*/, const QModelIndex& /*index*/) const
    {
        obj.setProperty(mp.name(), value.data(Qt::DisplayRole));
        return false;
    }

    virtual bool setModelData(QWidget* editor, QAbstractItemModel *model, const QModelIndex& index)
    {
        label.setCheckable(true);
        label.setCheckState(Qt::Checked);
        return onSetModelData(editor, model, index);
    }
    virtual bool setEditorData(QWidget */*editor*/, const QModelIndex &/*index*/) const
    {
        return false;
    }
};

class EnumPropertyItem: public PropertyItem
{
public:
    EnumPropertyItem(QObject& o, int i):
        PropertyItem(o, i)
    {
    }

    virtual void propertyInit() override
    {
        QMetaEnum e=mp.enumerator();
        auto v=mp.read(&obj).toInt();
        auto key=e.valueToKey(v);

        //value.setData(v);

        if (!key)
            value.setData(QString("%1 (invalid)").arg(v), Qt::DisplayRole);
        else
        {
            value.setData(key, Qt::DisplayRole);
            value.setToolTip(QString("%1 (%2)").arg(key).arg(v));
        }
    }

    virtual QString displayText() const
    {
        return mp.read(&obj).toString();
    }

    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const override
    {
        QMetaEnum e=mp.enumerator();
        auto v=mp.read(&obj).toInt();

        auto* cb=new QComboBox(parent);
        int selected=0;
        for(int i=0; i<e.keyCount(); ++i)
        {
            //MyDebug()<<"ENUM:"<<i<<e.key(i)<<e.value(i);

            cb->addItem(QString("%1 (%2)").arg(e.key(i)).arg(e.value(i)), e.value(i));
            if (e.value(i)==v)
                selected=i;
        }
        cb->setCurrentIndex(selected);
        return cb;
    }
    virtual bool onSetModelData(QWidget* editor, QAbstractItemModel */*model*/, const QModelIndex& /*index*/) const override
    {
        QComboBox* cb=qobject_cast<QComboBox*>(editor);
        int v=cb->currentData().toInt();
        mp.write(&obj, v);
        value.setData(cb->currentText(), Qt::DisplayRole);
        MyDebug<<"property="<<v;
        return true;
    }
    virtual bool setEditorData(QWidget */*editor*/, const QModelIndex &/*index*/) const override
    {
        return false;
    }
};

class MyDelegate: public QStyledItemDelegate
{
    QWidget* createEditor(QWidget *parent,
                                      const QStyleOptionViewItem &option,
                                      const QModelIndex &index) const override
    {
        auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
        if (mt_opaque)
        {
            auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
            auto editor=p->createEditor(parent, option, index);
            if (editor)
                return editor;
        }
        return QStyledItemDelegate::createEditor(parent, option, index);
    }
    virtual QString displayText(const QVariant &value, const QLocale &locale) const override
    {
        return QStyledItemDelegate::displayText(value, locale);
    }
    virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
        if (mt_opaque)
        {
            auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
            if (p->updateEditorGeometry(editor, option, index))
                return;
        }
        QStyledItemDelegate::updateEditorGeometry(editor, option, index);
    }
    virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
    {
        QStyledItemDelegate::setModelData(editor, model, index);

        auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
        if (mt_opaque)
        {
            auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
            if (p->setModelData(editor, model, index))
                return;
        }
        //QStyledItemDelegate::setModelData(editor, model, index);
    }
    virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override
    {
        auto mt_opaque = index.data(MyTypeRole).toULongLong(); //MyTypeRole).toInt();
        if (mt_opaque)
        {
            auto p=reinterpret_cast<PropertyItem*>(mt_opaque);
            if (p->setEditorData(editor, index))
                return;
        }
        QStyledItemDelegate::setEditorData(editor, index);
    }
};

static void populateTreeR(QObject* obj, const QMetaObject* mt, int start, int stop, QStandardItem* root)
{
    if (mt==0)
      mt=obj->metaObject();
    if (start<0)
        start=mt->propertyOffset();
    if (stop<0)
        stop=mt->propertyCount();

    for(int i=start; i<stop; ++i)
    {
        PropertyItem* pi=0;
        auto mt=obj->metaObject();
        auto mp=mt->property(i);
        if (mp.isEnumType() && !mp.isFlagType())
            pi=new EnumPropertyItem(*obj, i);
        else if (mp.type()==(QVariant::Type)QMetaType::QObjectStar)
        {
            auto item=new QStandardItem(mp.name());
            item->setEditable(false);
            //auto i2=new QStandardItem("FAKE");
            //item->appendRow(i2);
            //i2->setEditable(false);
            //QObject* o=((QObject*)mp.read(obj).toULongLong());
            QObject* o=reinterpret_cast<QObject*>(mp.read(obj).toLongLong());
            if (o)
            {
              populateTreeR(o, 0, -1, -1, item);
              root->appendRow(item);
            }
            //pi=new PropertyItem(*obj, i);
        }
        else
            pi=new PropertyItem(*obj, i);

        pi->propertyInit();
        root->appendRow(QList<QStandardItem*>()<<&pi->label<<pi);
    }
    auto sc=mt->superClass();
    if (sc)
    {
        auto item=new QStandardItem(sc->className());
        item->setEditable(false);
        //auto i2=new QStandardItem("FAKE");
        //item->appendRow(i2);
        //i2->setEditable(false);
        populateTreeR(obj, sc, sc->propertyOffset(), -1, item);
        root->appendRow(item);        
    }
}

static void populateTree(QObject* obj, QStandardItem* root)
{
    populateTreeR(obj, 0, -1, -1, root);
}

QgPropertyWidget::QgPropertyWidget(QObject *obj, QWidget *parent):
    QWidget(parent), //QScrollArea(parent),
    p_(*new QgPropertyWidget::Implementation)
{
    p_.obj=obj;
    p_.w=this;
    p_.ly=new QVBoxLayout;
    p_.lbl=new QLabel(obj->objectName());
    p_.ly->addWidget(p_.lbl);

    p_.t=new QTreeView(parent);
    p_.t->setUniformRowHeights(true);

    p_.t->setItemDelegate(new MyDelegate);

    p_.m=new QStandardItemModel;
    auto root=p_.m->invisibleRootItem();
    p_.m->setHeaderData(0, Qt::Horizontal, "Property", Qt::DisplayRole);
    p_.m->setHeaderData(1, Qt::Horizontal, "Value", Qt::DisplayRole);

    populateTree(obj, root);

    connect(p_.t, &QTreeView::expanded,
            [=](const QModelIndex&)
            {
              MyDebug<<"expanded!";
            });

    /*
    connect(p_.m, &QStandardItemModel::itemChanged,
            [=](QStandardItem* item)
            {
              item->setCheckState(Qt::Checked);
            }
    );
    */
    p_.t->setModel(p_.m);
    p_.t->resizeColumnToContents(1);
    p_.t->setAlternatingRowColors(true);

    p_.ly->addWidget(p_.t);

    //p_.bb=new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
    //p_.ly->addWidget(p_.bb);

    //p_.w->setLayout(p_.ly);
    //this->setWidget(p_.w);
    setLayout(p_.ly);

    //connect(p_.bb, &QDialogButtonBox::accepted, this, QDialog::accept);
    //connect(p_.bb, &QDialogButtonBox::rejected, this, QDialog::reject);
}

QgPropertyWidget::~QgPropertyWidget()
{
    p_.obj=0;
    p_.t->setModel(0);
    delete p_.m;
    delete &p_;
}



class QgPropertyDialog::Implementation
{
public:
    QgPropertyWidget* w;
    QVBoxLayout* ly;
    QDialogButtonBox* bb;
};

QgPropertyDialog::QgPropertyDialog(QObject* obj, QWidget* parent):
    QDialog(parent),
    p_(*new QgPropertyDialog::Implementation)
{
    p_.w=new QgPropertyWidget(obj, this);

    p_.ly=new QVBoxLayout;

    p_.ly->addWidget(p_.w);

    p_.bb=new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);

    p_.ly->addWidget(p_.bb);

    setLayout(p_.ly);

    connect(p_.bb, &QDialogButtonBox::accepted, this, QDialog::accept);
    connect(p_.bb, &QDialogButtonBox::rejected, this, QDialog::reject);
}

QgPropertyDialog::~QgPropertyDialog()
{
    //setLayout(0);
    delete p_.ly;
    delete &p_;
}
