/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2016 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/


// -- Core stuff
#include "ObjectController.h"

#include "qtvariantproperty.h"
#include "qtgroupboxpropertybrowser.h"
#include "qttreepropertybrowser.h"
#include "qtbuttonpropertybrowser.h"
#include "qtpropertybrowser.h"
#include "qtpropertymanager.h"

// -- QT stuff
#include <QMetaObject>
#include <QMetaProperty>
#include <QVBoxLayout>
#include <QScrollArea>
#include <QMessageBox>
#include <QSize>

// -- stl stuff
#include <iostream>

//-- CamiTK stuff
#include "Property.h"


namespace camitk {
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 );

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

QtVariantProperty * ObjectControllerPrivate::buildQtVariantProperty(QString name, QMetaType::Type type, QVariant value, bool isReadable, bool isWritable, bool isEnumType, bool isFlagType, bool isDesignable, QMetaEnum* metaEnum) {
    QtVariantProperty *buildProperty = 0;

    // hide objectName property
    if ( !isReadable ) {
        buildProperty = m_readOnlyManager->addProperty ( QVariant::String, name);
        buildProperty->setValue ( QLatin1String ( "< Non Readable >" ) );
    } else if ( isEnumType ) {
        if ( isFlagType ) {
            buildProperty = m_manager->addProperty ( QtVariantPropertyManager::flagTypeId(), name );
            QMap<int, bool> valueMap;
            QStringList flagNames;

            for ( int i = 0; i < metaEnum->keyCount(); i++ ) {
                int enumValue = metaEnum->value ( i );

                if ( !valueMap.contains ( enumValue ) && isPowerOf2 ( enumValue ) ) {
                    valueMap[enumValue] = true;
                    flagNames.append ( QLatin1String ( metaEnum->key ( i ) ) );
                }

                buildProperty->setAttribute ( QLatin1String ( "flagNames" ), flagNames );
                buildProperty->setValue ( flagToInt ( *metaEnum, value.toInt() ) );
            }
        } else {
            buildProperty = m_manager->addProperty ( QtVariantPropertyManager::enumTypeId(), name);
            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 ) ) );
                }
            }

            buildProperty->setAttribute ( QLatin1String ( "enumNames" ), enumNames );
            buildProperty->setValue ( enumToInt ( *metaEnum, value.toInt() ) );
        }
    } else if ( m_manager->isPropertyTypeSupported ( type ) ) {
        if ( !isWritable ) {
            buildProperty = m_readOnlyManager->addProperty ( type,  name);
        } else if ( !isDesignable )
            buildProperty = m_readOnlyManager->addProperty ( type, name + " (Non Designable)" );
        else
            buildProperty = m_manager->addProperty ( type, name );

        buildProperty->setValue ( value );
    } else {
        // a map is managed as a group
        if ( type == QMetaType::QVariantMap) {
            QString mapName = name;

            buildProperty = m_manager->addProperty(QtVariantPropertyManager::groupTypeId(), mapName);
            // build the subproperties
            QMapIterator<QString, QVariant> it(value.toMap());

            while (it.hasNext()) {
                it.next();
                QtVariantProperty *mapSubProperty = buildQtVariantProperty(it.key(), (QMetaType::Type)it.value().userType(), it.value(), isReadable, isWritable, false, false, isDesignable);

                buildProperty->addSubProperty(mapSubProperty);
            }

        } else {
            buildProperty = m_readOnlyManager->addProperty ( QVariant::String, name );
            buildProperty->setValue ( QLatin1String ( "< Unknown Type >" ) );
            buildProperty->setEnabled ( false );
        }
    }

    return buildProperty;
}

void ObjectControllerPrivate::addClassProperties ( const QMetaObject *metaObject ) {
    if ( !metaObject )
        return;

    // CAMITK REMOVED
    //addClassProperties(metaObject->superClass());

    /* CAMITK REMOVED
    QtProperty *classProperty = m_classToProperty.value ( metaObject );
    if ( !classProperty ) {
    QString className = QLatin1String(metaObject->className());
    classProperty = m_manager->addProperty(QtVariantPropertyManager::groupTypeId(), className);
    m_classToProperty[metaObject] = classProperty;
    m_propertyToClass[classProperty] = metaObject;
    */
    for ( int idx = 0; idx < metaObject->propertyCount(); idx++ ) {
        QMetaProperty metaProperty = metaObject->property ( idx );

        // hide objectName property
        if ( QString(metaProperty.name()) != "objectName") {
            // create the sub property
            QtVariantProperty *subProperty = 0;
            QMetaEnum metaEnum = metaProperty.enumerator();
            subProperty = buildQtVariantProperty(metaProperty.name(), (QMetaType::Type) metaProperty.userType(), metaProperty.read ( m_object ), metaProperty.isReadable(), metaProperty.isWritable(), metaProperty.isEnumType(), metaProperty.isFlagType() , metaProperty.isDesignable(), &metaEnum);

            /* CAMITK ADDED */
            m_topLevelProperties.append ( subProperty );
            m_browser->addProperty ( subProperty );

            /* CAMITK REMOVED */
            //classProperty->addSubProperty(subProperty);
            m_classPropertyToIndex[subProperty] = idx;
            m_classToIndexToProperty[metaObject][idx] = subProperty;
        }
    }

    /*      CAMITK REMOVED
        } else {
            updateClassProperties ( metaObject, false );
        }
         m_topLevelProperties.append(classProperty);
         m_browser->addProperty(classProperty);
         */

}

// CAMITK ADDED
void ObjectControllerPrivate::updateDynamicProperties ( const QObject *edited ) {
    if ( !edited )
        return;

    QList<QByteArray> dynProp = edited->dynamicPropertyNames();

    for ( int idx = 0; idx < dynProp.size(); ++idx ) {
        QString pName = QString ( dynProp.at ( idx ) );
        int type = edited->property ( dynProp.at ( idx ) ).userType();
        QtVariantProperty *subProperty = m_indexToDynamicProperty[idx];

        if ( m_manager->isPropertyTypeSupported ( type ) ) {
            subProperty->setValue ( edited->property ( dynProp.at ( idx ) ) );
        } else {
            subProperty->setValue ( QLatin1String ( "< Unknown Type >" ) );
        }
    }
}

// CAMITK ADDED
// loop on dynamically added property
void ObjectControllerPrivate::addDynamicProperties ( QObject *edited ) {

    QList<QByteArray> dynProp = edited->dynamicPropertyNames();

    // loop on all dynamic properties
    for ( int idx = 0; idx < dynProp.size(); ++idx ) {
        QString pName = QString ( dynProp.at ( idx ) );
        QVariant pValue = edited->property ( dynProp.at ( idx ) );
        QtVariantProperty  *subProperty = 0;
        QtProperty *groupProperty = NULL;

        // check if the edited object use a list of camitk::Property to
        // add more information about the dynamic property
        Property* camitkProp = NULL;
        bool getPropertyExist = QMetaObject::invokeMethod(edited, "getProperty", Qt::DirectConnection,
                                Q_RETURN_ARG(Property*, camitkProp),
                                Q_ARG(QString, pName));

        // try harder (in case the edited object was not declared in the camitk namespace)
        if (!getPropertyExist) {
            getPropertyExist = QMetaObject::invokeMethod(edited, "getProperty", Qt::DirectConnection,
                               Q_RETURN_ARG(camitk::Property*, camitkProp),
                               Q_ARG(QString, pName));
        }

        if (!getPropertyExist || camitkProp==NULL) {
            // the old way, build a simple dynamic variant property
            subProperty = buildQtVariantProperty(pName, (QMetaType::Type) pValue.userType(), pValue, true, /* isWritable*/ true, false, false, true);
        } else {
            //-- use all the power of the camitk::Property
            // get then meta enum if available
            if (!camitkProp->getEnumTypeName().isNull()) {
                const QMetaObject* metaObj = edited->metaObject();
                QMetaEnum enumType = metaObj->enumerator(metaObj->indexOfEnumerator(camitkProp->getEnumTypeName().toStdString().c_str()));

                // Create the dynamic variant property + use the read only status
                subProperty = buildQtVariantProperty(pName, (QMetaType::Type) pValue.userType(), pValue, true, /* isWritable*/ !camitkProp->getReadOnly(), true, false, true, &enumType);
            } else {
                // Create the dynamic variant property + use the read only status
                subProperty = buildQtVariantProperty(pName, (QMetaType::Type) pValue.userType(), pValue, true, /* isWritable*/ !camitkProp->getReadOnly(), false, false, true);
            }

            // loop on all property attributes and transfer the values to subProperty
            foreach(QString attributeName, camitkProp->getAttributeList()) {
                subProperty->setAttribute( attributeName, camitkProp->getAttribute( attributeName ) );
            }


            /*
                        // check if we can add icons
                        if (!camitkProp->getReadOnly() && !camitkProp->getEnumTypeName().isNull() && camitkProp->getEnumIcons().size()>0) {
                            std::cout << "mgr=" << subProperty->propertyManager() << std::endl;
                            QtEnumPropertyManager *testmgr = dynamic_cast<QtEnumPropertyManager*>(subProperty->propertyManager());
                            std::cout << "testmgr=" << testmgr << std::endl;
                            //m_browser->setFactoryForManager<>();
                            std::cout << "mgr class=" << subProperty->propertyManager()->metaObject()->className() << std::endl;
                            std::cout << "camitkProp->getEnumIcons().size()" << camitkProp->getEnumIcons().size() << std::endl;
                            QVariantMap vmap = camitkProp->getEnumIcons();
                            m_manager->setAttribute(subProperty,"enumIcons", vmap);
                            //m_manager->setUserData();
                            //std::cout << "yep..." << std::endl;

                        }

            */
            // use description for tool tip and what's this
            QString description = camitkProp->getDescription();
            subProperty->setWhatsThis(description);
            subProperty->setToolTip(description);

            // remove rich-text for status tip string (using a reg exp)
            subProperty->setStatusTip(description.remove(QRegExp("<[^>]*>")));

            //-- check for subgroup
            if (!camitkProp->getGroupName().isNull()) {
                // check if the group property already exists
                groupProperty = groupProperties.value(camitkProp->getGroupName());
                if (!groupProperty) {
                    // create the group property
                    groupProperty = m_manager->addProperty(QtVariantPropertyManager::groupTypeId(), camitkProp->getGroupName());
                    //m_classToProperty[metaObject] = classProperty;
                    //m_propertyToClass[classProperty] = metaObject;
                    // register it
                    groupProperties.insert(camitkProp->getGroupName(), groupProperty);
                    // add to the top level browser
                    m_topLevelProperties.append(groupProperty);
                    m_browser->addProperty(groupProperty);
                }
                groupProperty->addSubProperty(subProperty);
            }
        }

        if (!groupProperty) {
            // add the variant property to the property and build the matching GUI
            m_topLevelProperties.append ( subProperty );
            m_browser->addProperty ( subProperty );
        }

        m_dynamicPropertyToIndex[subProperty] = idx;
        m_indexToDynamicProperty[idx] = subProperty;

    }


}

void ObjectControllerPrivate::saveExpandedState() {

}

void ObjectControllerPrivate::restoreExpandedState() {

}

void ObjectControllerPrivate::valueChanged ( QtProperty *property, const QVariant value ) {

    // meta properties
    if ( m_classPropertyToIndex.contains ( property ) ) {

        int idx = m_classPropertyToIndex.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 );
    }

    // dynamic properties
    if (m_dynamicPropertyToIndex.contains(property)) {
        int idx = m_dynamicPropertyToIndex.value(property);
        QList<QByteArray> dynProp = m_object->dynamicPropertyNames();
        m_object->setProperty(dynProp.at ( idx), value);

        updateDynamicProperties(m_object);
    }
}

void ObjectControllerPrivate::saveChange ( QtProperty *property, const QVariant &value ) {
    saveChangeValue.insert ( property,value );
}

void ObjectControllerPrivate::applyChange () {
    QMapIterator<QtProperty *, QVariant> it ( saveChangeValue );

    while ( it.hasNext() ) {
        it.next();
        valueChanged ( it.key() , it.value() );
    }

    saveChangeValue.clear();
}

ObjectController::ObjectController ( QWidget *parent, ViewMode viewMode )
    : QWidget ( parent ) {
    d_ptr = new ObjectControllerPrivate;
    d_ptr->q_ptr = this;

    // object unroll in the property browser
    d_ptr->m_object = 0;

    // Scroll for view mode GROUPBOX and BUTTON
    scroll = new QScrollArea ( this );
    scroll->setWidgetResizable ( true );
    scroll->setMinimumSize ( 300,100 ); // to have no horizontal scrollbar

    // declaration of the layout
    layout = new QVBoxLayout ( this );
    layout->setMargin ( 0 );

    // CAMITK ADDED
    currentViewMode = viewMode;
    initViewMode();

    d_ptr->m_readOnlyManager = new QtVariantPropertyManager ( this );
    d_ptr->m_manager = new QtVariantPropertyManager ( this );
    QtVariantEditorFactory *factory = new QtVariantEditorFactory ( this );  //to give a form at every data's type
    d_ptr->m_browser->setFactoryForManager ( d_ptr->m_manager, factory );

    setAutoUpdateProperty(false);
}

ObjectController::~ObjectController() {
    delete d_ptr;
}

void ObjectController::setObject ( QObject *object ) {
    if ( d_ptr->m_object == object )
        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();

        // all the QtProperty instances are delete, just clear the groupProperties map (no need to delete)
        d_ptr->groupProperties.clear();

    }

    d_ptr->m_object = object;

    if ( !d_ptr->m_object )
        return;

    d_ptr->addClassProperties ( d_ptr->m_object->metaObject() );
    d_ptr->addDynamicProperties( d_ptr->m_object );

    d_ptr->restoreExpandedState();

    d_ptr->saveChangeValue.clear();
}

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

// CAMITK ADDED
void ObjectController::setAutoUpdateProperty(bool autoUpdate) {
    if (autoUpdate) {
        connect ( d_ptr->m_manager, SIGNAL ( valueChanged ( QtProperty *, const QVariant & ) ),
                  this, SLOT ( valueChanged ( QtProperty *, const QVariant & ) ) );
    } else {
        connect ( d_ptr->m_manager, SIGNAL ( valueChanged ( QtProperty *, const QVariant & ) ),
                  this, SLOT ( saveChange ( QtProperty *, const QVariant & ) ) );
    }
}

void ObjectController::setViewMode ( ViewMode viewMode ) {
    if ( currentViewMode != viewMode ) {

        // save the current object show in the property browser to get back later
        QObject *currentObject = object();
        setObject ( NULL );

        if ( currentViewMode == TREE ) {    // in view mode TREE, the layout contains the property browser
            layout->removeWidget ( d_ptr->m_browser );
        } else if ( currentViewMode == GROUPBOX || currentViewMode == BUTTON ) { // whereas in view mode BUTTON and GROUPBOX, the layout contains the scroll wich contains the property browser
            layout->removeWidget ( scroll );
        }

        // just delete to re use the same address for the new property browser
        delete d_ptr->m_browser;

        currentViewMode = viewMode;
        initViewMode();

        // reinsert the object
        setObject ( currentObject );
    }
}

void ObjectController::initViewMode() {
    switch ( currentViewMode) {
    case GROUPBOX :
        d_ptr->m_browser = new QtGroupBoxPropertyBrowser ( this );
        layout->addWidget ( scroll );
        scroll->setWidget ( d_ptr->m_browser );
        break;
    case BUTTON :
        d_ptr->m_browser = new QtButtonPropertyBrowser ( this );
        layout->addWidget ( scroll );
        scroll->setWidget ( d_ptr->m_browser );
        break;
    case TREE :
    default :
        d_ptr->m_browser = new QtTreePropertyBrowser ( this );
        dynamic_cast<QtTreePropertyBrowser*> ( d_ptr->m_browser )->setRootIsDecorated ( false );
        dynamic_cast<QtTreePropertyBrowser*> ( d_ptr->m_browser )->setResizeMode(QtTreePropertyBrowser::Interactive);
        layout->addWidget ( d_ptr->m_browser );
        break;
    }
}

ObjectController::ViewMode ObjectController::getViewMode() const {
    return currentViewMode;
}

bool ObjectController::isModified() {
    return ( !d_ptr->saveChangeValue.empty() );
}

void ObjectController::apply() {
    d_ptr->applyChange();
}

void ObjectController::revert() {
    d_ptr->saveChangeValue.clear();
    QObject *currentObject = object();
    setObject ( NULL );
    setObject ( currentObject );
}

}

