/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
**     of its contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

// updated object create and views

#include "objectcontroller.h"
#include "qdebug.h"

class ObjectControllerPrivate {
    ObjectController *q_ptr;
    Q_DECLARE_PUBLIC(ObjectController)
  public:

    void addClassProperties(const QMetaObject *metaObject, bool subGroup);
    void updateClassProperties(const QMetaObject *metaObject, bool recursive);
    void saveExpandedState();
    void restoreExpandedState();
    void slotValueChanged(QtProperty *property, const QVariant &value);
    int enumToInt(const QMetaEnum &metaEnum, int enumValue) const;
    int intToEnum(const QMetaEnum &metaEnum, int intValue) const;
    int flagToInt(const QMetaEnum &metaEnum, int flagValue) const;
    int intToFlag(const QMetaEnum &metaEnum, int intValue) const;
    bool isSubValue(int value, int subValue) const;
    bool isPowerOf2(int value) const;

    QObject                  *m_object;

    QMap<const QMetaObject *, QtProperty *> m_classToProperty;
    QMap<QtProperty *, const QMetaObject *> m_propertyToClass;
    QMap<QtProperty *, int>     m_propertyToIndex;
    QMap<const QMetaObject *, QMap<int, QtVariantProperty *> > m_classToIndexToProperty;

    QMap<QtProperty *, bool>    m_propertyToExpanded;

    QList<QtProperty *>         m_topLevelProperties;

    QtAbstractPropertyBrowser    *m_browser;
    QtVariantPropertyManager *m_manager;
    QtVariantPropertyManager *m_readOnlyManager;
    bool                      m_subGroup;
};

int ObjectControllerPrivate::enumToInt(const QMetaEnum &metaEnum, int enumValue) const {
    QMap<int, int> valueMap; // dont show multiple enum values which have the same values
    int pos = 0;
    for (int i = 0; i < metaEnum.keyCount(); i++) {
        int value = metaEnum.value(i);
        if (!valueMap.contains(value)) {
            if (value == enumValue)
                return pos;
            valueMap[value] = pos++;
        }
    }
    return -1;
}

int ObjectControllerPrivate::intToEnum(const QMetaEnum &metaEnum, int intValue) const {
    QMap<int, bool> valueMap; // dont show multiple enum values which have the same values
    QList<int> values;
    for (int i = 0; i < metaEnum.keyCount(); i++) {
        int value = metaEnum.value(i);
        if (!valueMap.contains(value)) {
            valueMap[value] = true;
            values.append(value);
        }
    }
    if (intValue >= values.count())
        return -1;
    return values.at(intValue);
}

bool ObjectControllerPrivate::isSubValue(int value, int subValue) const {
    if (value == subValue)
        return true;
    int i = 0;
    while (subValue) {
        if (!(value & (1 << i))) {
            if (subValue & 1)
                return false;
        }
        i++;
        subValue = subValue >> 1;
    }
    return true;
}

bool ObjectControllerPrivate::isPowerOf2(int value) const {
    while (value) {
        if (value & 1) {
            return value == 1;
        }
        value = value >> 1;
    }
    return false;
}

int ObjectControllerPrivate::flagToInt(const QMetaEnum &metaEnum, int flagValue) const {
    if (!flagValue)
        return 0;
    int intValue = 0;
    QMap<int, int> valueMap; // dont show multiple enum values which have the same values
    int pos = 0;
    for (int i = 0; i < metaEnum.keyCount(); i++) {
        int value = metaEnum.value(i);
        if (!valueMap.contains(value) && isPowerOf2(value)) {
            if (isSubValue(flagValue, value))
                intValue |= (1 << pos);
            valueMap[value] = pos++;
        }
    }
    return intValue;
}

int ObjectControllerPrivate::intToFlag(const QMetaEnum &metaEnum, int intValue) const {
    QMap<int, bool> valueMap; // dont show multiple enum values which have the same values
    QList<int> values;
    for (int i = 0; i < metaEnum.keyCount(); i++) {
        int value = metaEnum.value(i);
        if (!valueMap.contains(value) && isPowerOf2(value)) {
            valueMap[value] = true;
            values.append(value);
        }
    }
    int flagValue = 0;
    int temp = intValue;
    int i = 0;
    while (temp) {
        if (i >= values.count())
            return -1;
        if (temp & 1)
            flagValue |= values.at(i);
        i++;
        temp = temp >> 1;
    }
    return flagValue;
}

void ObjectControllerPrivate::updateClassProperties(const QMetaObject *metaObject, bool recursive) {
    if (!metaObject)
        return;

    if (recursive)
        updateClassProperties(metaObject->superClass(), recursive);

    QtProperty *classProperty = m_classToProperty.value(metaObject);
    if (!classProperty)
        return;

    for (int idx = metaObject->propertyOffset(); idx < metaObject->propertyCount(); idx++) {
        QMetaProperty metaProperty = metaObject->property(idx);
        if (metaProperty.isReadable()) {
            if (m_classToIndexToProperty.contains(metaObject) && m_classToIndexToProperty[metaObject].contains(idx)) {
                QtVariantProperty *subProperty = m_classToIndexToProperty[metaObject][idx];
                if (metaProperty.isEnumType()) {
                    if (metaProperty.isFlagType())
                        subProperty->setValue(flagToInt(metaProperty.enumerator(), metaProperty.read(m_object).toInt()));
                    else
                        subProperty->setValue(enumToInt(metaProperty.enumerator(), metaProperty.read(m_object).toInt()));
                } else {
                    subProperty->setValue(metaProperty.read(m_object));
                }
            }
        }
    }
}

void ObjectControllerPrivate::addClassProperties(const QMetaObject *inmetaObject, bool subGroup) {
    if (!inmetaObject)
        return;

    // Collect a list of all sub classes in the object
    QList< const QMetaObject *> metaObjectsList;
    metaObjectsList.clear();

    metaObjectsList << inmetaObject;

    const QMetaObject *tmpObj = inmetaObject->superClass();
    metaObjectsList << tmpObj;
    while (tmpObj) {
        tmpObj = tmpObj->superClass();
        if (tmpObj)
            metaObjectsList << tmpObj;
    }

    const QMetaObject *metaObject;

    for (int i = 0; i < metaObjectsList.count(); i++) {
        metaObject = metaObjectsList[i];

        QtProperty *classProperty = m_classToProperty.value(metaObject);
        if (!classProperty) {
            QString className = QLatin1String(metaObject->className());

            // Note: Skip class QObject from the property views
            if (className == QLatin1String("QObject")) return;

            // If a class name starts with lowercase "x" then skip it from view!
            if (className.at(0) == "x") continue;

            // Process Class name into a user friendly view
            // Strip prefix C_ and process all _ to spaces

            QString prefix("C_"); // String to replace.
            QString replaceprefix(""); // Replacement string.
            className.replace(className.indexOf(prefix), prefix.size(), replaceprefix);
            className.replace(QString("_"), QString(" "));
            classProperty = m_manager->addProperty(QtVariantPropertyManager::groupTypeId(), className);
            m_classToProperty[metaObject] = classProperty;
            m_propertyToClass[classProperty] = metaObject;

            for (int idx = metaObject->propertyOffset(); idx < metaObject->propertyCount(); idx++) {

                QMetaProperty metaProperty = metaObject->property(idx);
                int type = metaProperty.userType();
                QtVariantProperty *subProperty = 0;

                // Note:  Get the var member name and check if we want it to be writable (Enabled)
                QString memberVarName = QLatin1String(metaProperty.name());
                bool b_SetEnabled = true;

                // Special case for enabling or disabling editing
                QString ememberVarName = "e" + QLatin1String(metaProperty.name());
                QByteArray array = ememberVarName.toLocal8Bit();
                char* buffer = array.data();
                QVariant set = m_object->property(buffer);
                if (set.type() == QVariant::Bool) {
                    b_SetEnabled = (bool &)set;
                }

                // qDebug() << "Member Name :" << memberVarName;

                // Note: process the first char if it contains _ then the var is read only and remove the _ char
                if (memberVarName.at(0) == "_") {
                    b_SetEnabled = false;
                    memberVarName.remove(0, 1);
                } else if (memberVarName.at(0) == "x") { // Special case if first char is x then remove it as its a place holde for tabbing spaces
                    memberVarName.remove(0, 1);
                }

                // after that replace all occurance of _ with space char for display
                memberVarName.replace(QString("_"), QString(" "));

                if (!metaProperty.isReadable()) {
                    subProperty = m_readOnlyManager->addProperty(QVariant::String, memberVarName);
                    subProperty->setValue(QLatin1String("< Non Readable >"));
                } else if (metaProperty.isEnumType()) {
                    if (metaProperty.isFlagType()) {
                        subProperty = m_manager->addProperty(QtVariantPropertyManager::flagTypeId(), memberVarName);
                        QMetaEnum metaEnum = metaProperty.enumerator();
                        QMap<int, bool> valueMap;
                        QStringList flagNames;
                        for (int i = 0; i < metaEnum.keyCount(); i++) {
                            int value = metaEnum.value(i);
                            if (!valueMap.contains(value) && isPowerOf2(value)) {
                                valueMap[value] = true;
                                flagNames.append(QLatin1String(metaEnum.key(i)));
                            }
                            subProperty->setAttribute(QLatin1String("flagNames"), flagNames);
                            subProperty->setValue(flagToInt(metaEnum, metaProperty.read(m_object).toInt()));
                        }
                    } else {
                        subProperty = m_manager->addProperty(QtVariantPropertyManager::enumTypeId(), memberVarName);
                        QMetaEnum metaEnum = metaProperty.enumerator();
                        QMap<int, bool> valueMap; // dont show multiple enum values which have the same values
                        QStringList enumNames;
                        for (int i = 0; i < metaEnum.keyCount(); i++) {
                            int value = metaEnum.value(i);
                            if (!valueMap.contains(value)) {
                                valueMap[value] = true;
                                enumNames.append(QLatin1String(metaEnum.key(i)));
                            }
                        }
                        subProperty->setAttribute(QLatin1String("enumNames"), enumNames);
                        subProperty->setValue(enumToInt(metaEnum, metaProperty.read(m_object).toInt()));
                    }
                } else if (m_manager->isPropertyTypeSupported(type)) {
                    if (!metaProperty.isWritable())
                        subProperty = m_readOnlyManager->addProperty(type, memberVarName + QLatin1String(" (Non Writable)"));
                    if (!metaProperty.isDesignable())
                        subProperty = m_readOnlyManager->addProperty(type, memberVarName + QLatin1String(" (Non Designable)"));
                    else
                        subProperty = m_manager->addProperty(type, memberVarName);
                    subProperty->setValue(metaProperty.read(m_object));
                } else {
                    subProperty = m_readOnlyManager->addProperty(QVariant::String, memberVarName);
                    subProperty->setValue(QLatin1String("< Unknown Type >"));
                    b_SetEnabled = false;
                }


                // Notes: QtVariantProperty *priority = variantManager->addProperty(QVariant::Int, "Priority");

                if (subProperty)
                    subProperty->setEnabled(b_SetEnabled);

                m_propertyToIndex[subProperty] = idx;
                m_classToIndexToProperty[metaObject][idx] = subProperty;
                if (subGroup)
                    classProperty->addSubProperty(subProperty);
                else
                    m_browser->addProperty(subProperty);
            }
        } else {
            updateClassProperties(metaObject, false);
        }

        m_topLevelProperties.append(classProperty);
        if (subGroup)
            m_browser->addProperty(classProperty);
    } // Loop i
}

void ObjectControllerPrivate::saveExpandedState() {

}

void ObjectControllerPrivate::restoreExpandedState() {

}

void ObjectControllerPrivate::slotValueChanged(QtProperty *property, const QVariant &value) {
    if (!m_propertyToIndex.contains(property))
        return;

    int idx = m_propertyToIndex.value(property);

    const QMetaObject *metaObject = m_object->metaObject();
    QMetaProperty metaProperty = metaObject->property(idx);
    if (metaProperty.isEnumType()) {
        if (metaProperty.isFlagType())
            metaProperty.write(m_object, intToFlag(metaProperty.enumerator(), value.toInt()));
        else
            metaProperty.write(m_object, intToEnum(metaProperty.enumerator(), value.toInt()));
    } else {
        metaProperty.write(m_object, value);
    }

    updateClassProperties(metaObject, true);
}

///////////////////

QtProperty *ObjectController::getProperty(QString propertyName) {
    if (m_use_treeView) {
        QList<QtBrowserItem *> topLevelItems = d_ptr->m_browser->topLevelItems();
        QList<QtBrowserItem *>::iterator i;
        for (i = topLevelItems.begin(); i != topLevelItems.end(); ++i) {
            if ((*i)) {
                QtProperty *rootprop = (*i)->property();
                QString rootName = rootprop->propertyName();
                //qDebug() << "Root PropertyName: " << rootName;
                if (propertyName.compare(rootName) == 0) {
                    return rootprop;
                }
                QList<QtBrowserItem *> children = (*i)->children();
                QList<QtBrowserItem *>::iterator j;
                for (j = children.begin(); j != children.end(); ++j) {
                    if ((*j)) {
                        QtProperty *prop = (*j)->property();
                        QString propName = prop->propertyName();
                        //qDebug() << "Child PropertyName: " << propName;
                        if (propertyName.compare(propName) == 0) {
                            return prop;
                        }
                    }
                }
            }
        }
    }
    return nullptr;
}


QtTreePropertyBrowser *ObjectController::getTreeBrowser() {
    return (QtTreePropertyBrowser *)d_ptr->m_browser;
}

QtGroupBoxPropertyBrowser* ObjectController::getBoxBrowser() {
    return (QtGroupBoxPropertyBrowser *)d_ptr->m_browser;
}

ObjectController::ObjectController(QWidget *parent, bool use_treeView, bool ReadOnly)
    : QWidget(parent) {

    m_use_treeView = use_treeView;
    m_ReadOnly = ReadOnly;

    d_ptr = new ObjectControllerPrivate;
    d_ptr->q_ptr = this;
    d_ptr->m_object = 0;

    QScrollArea *scroll = new QScrollArea(this);
    scroll->setWidgetResizable(true);
    QVBoxLayout *layout = new QVBoxLayout(this);

    if (use_treeView) {
        QtTreePropertyBrowser *browser = new QtTreePropertyBrowser(this);
        browser->setRootIsDecorated(false);
        d_ptr->m_browser = browser;
    } else {
        d_ptr->m_browser = new QtGroupBoxPropertyBrowser(this);
    }

    layout->setMargin(0);
    layout->addWidget(scroll);
    scroll->setWidget(d_ptr->m_browser);

    d_ptr->m_readOnlyManager = new QtVariantPropertyManager(this);
    d_ptr->m_manager = new QtVariantPropertyManager(this);

    QtVariantEditorFactory *factory = new QtVariantEditorFactory(this);

    if (ReadOnly)
        d_ptr->m_browser->setFactoryForManager(d_ptr->m_readOnlyManager, factory);
    else
        d_ptr->m_browser->setFactoryForManager(d_ptr->m_manager, factory);

    connect(d_ptr->m_manager, SIGNAL(valueChanged(QtProperty *, const QVariant &)),
            this, SLOT(slotValueChanged(QtProperty *, const QVariant &)));
}

ObjectController::~ObjectController() {
    if (d_ptr) {
        delete d_ptr;
        d_ptr = NULL;
    }
}

void ObjectController::setObject(QObject *object, bool subGroup, bool forceRefresh) {
    if ((d_ptr->m_object == object) && (!forceRefresh))
        return;

    if (d_ptr->m_object) {
        d_ptr->saveExpandedState();
        QListIterator<QtProperty *> it(d_ptr->m_topLevelProperties);
        while (it.hasNext()) {
            d_ptr->m_browser->removeProperty(it.next());
        }
        d_ptr->m_topLevelProperties.clear();
    }

    d_ptr->m_object = object;

    if (!d_ptr->m_object)
        return;

    d_ptr->addClassProperties(d_ptr->m_object->metaObject(), subGroup);

    d_ptr->restoreExpandedState();
}

QObject *ObjectController::object() const {
    return d_ptr->m_object;
}

#include "moc_objectcontroller.cpp"
