/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2024 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * 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$
 ****************************************************************************/

#include "Property.h"

#include <QMetaEnum>

namespace camitk {

// -------------------- constructor --------------------
Property::Property(QString name, const QVariant& variant, QString description, QString unit) : initialValue{variant} {
    this->name = name;
    if (!unit.isEmpty()) {
        this->description = "<b>(in " + unit + ") </b>";
    }
    else {
        this->description = "";
    }
    this->description += description;
    readOnly = false;
    // intialize null strings, see http://qt-project.org/doc/qt-4.8/qstring.html#distinction-between-null-and-empty-strings
    enumTypeName = QString((const char*)nullptr);
    groupName = QString((const char*)nullptr);
}

// -------------------- getName --------------------
const QString& Property::getName() const {
    return name;
}

// -------------------- getInitialValue --------------------
const QVariant& Property::getInitialValue() const {
    return initialValue;
}

// -------------------- setReadOnly --------------------
void Property::setReadOnly(bool isReadOnly) {
    readOnly = isReadOnly;
}

// --------------------getReadOnly  --------------------
bool Property::getReadOnly() const {
    return readOnly;
}

// -------------------- setDescription --------------------
void Property::setDescription(QString description) {
    this->description = description;
}

// -------------------- getDescription --------------------
const QString& Property::getDescription() const {
    return description;
}


// -------------------- getEnumTypeName --------------------
QString Property::getEnumTypeName() const {
    return enumTypeName;
}

// -------------------- setEnumTypeName --------------------
void Property::setEnumTypeName(QString nameOfTheEnum) {
    enumTypeName = nameOfTheEnum;
}

void Property::setEnumTypeName(QString nameOfTheEnum, QObject* objectDeclaringTheEnum) {
    setEnumTypeName(nameOfTheEnum);

    //-- build enumNames property from enum
    QStringList enumAutoGuiLiterals;
    const QMetaObject* metaObj = objectDeclaringTheEnum->metaObject();
    QMetaEnum enumType = metaObj->enumerator(metaObj->indexOfEnumerator(enumTypeName.toStdString().c_str()));

    // loop over the enum type, get the key value as string and beautify it
    for (int i = 0; i < enumType.keyCount(); i++) {
        // capitalize every word and replace "_" by space
        QStringList tokens = QString(enumType.key(i)).split(QLatin1Char('_'), Qt::SkipEmptyParts);
        for (int j = 0; j < tokens.size(); ++j) {
            tokens[j] = tokens[j].toLower();
            tokens[j].replace(0, 1, tokens[j][0].toUpper());
        }
        enumAutoGuiLiterals << tokens.join(" ");
    }
    setAttribute("enumNames", enumAutoGuiLiterals);
}

// -------------------- getEnumValueAsString --------------------
QString Property::getEnumValueAsString(const QObject* objectDeclaringTheEnum) const {
    if (!enumTypeName.isNull()) {
        int indexOfEnum = objectDeclaringTheEnum->metaObject()->indexOfEnumerator(enumTypeName.toStdString().c_str());
        QMetaEnum enumType = objectDeclaringTheEnum->metaObject()->enumerator(indexOfEnum);
        // property current value
        int val = objectDeclaringTheEnum->property(name.toStdString().c_str()).toInt();
        return enumType.valueToKey(val);
    }
    else {
        return enumTypeName;    // null string
    }
}

// -------------------- getEnumIcons --------------------
QMap< int, QIcon > Property::getEnumIcons() const {
    return enumIcons;
}

// -------------------- setEnumIcons --------------------
void Property::setEnumIcons(const QMap< int, QIcon >& enumIcons) {
    this->enumIcons = enumIcons;
}

// -------------------- getGroupName --------------------
QString Property::getGroupName() const {
    return groupName;
}

// -------------------- setGroupName --------------------
void Property::setGroupName(QString groupName) {
    this->groupName = groupName;
}

// -------------------- getAttribute --------------------
QVariant Property::getAttribute(QString attName) {
    if (attributeValues.contains(attName)) {
        return attributeValues.value(attName);
    }
    else {
        return QVariant();    // invalid QVariant
    }
}

// -------------------- getAttributeList --------------------
QStringList Property::getAttributeList() {
    return attributeValues.keys();
}

// -------------------- setAttribute --------------------
void Property::setAttribute(const QString& attribute, const QVariant& value) {
    // If there is already an item with the key attribute, that item's value is replaced with the given value
    attributeValues.insert(attribute, value);
}

// -------------------- getProperty --------------------
Property* Property::getProperty(QObject* object, QString name) {
    // check if the edited object use a list of camitk::Property
    Property* camitkProp = nullptr;
    bool getPropertyExist = QMetaObject::invokeMethod(object, "getProperty", Qt::DirectConnection,
                            Q_RETURN_ARG(Property*, camitkProp),
                            Q_ARG(QString, name));

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

    if (!getPropertyExist) {
        return nullptr;
    }
    else {
        return camitkProp;
    }

}

}
