/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 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 "PropertyObject.h"
#include "Application.h"
#include "PersistenceManager.h"

#include <QVector3D>
#include <QDate>
#include <QKeySequence>
#include <QSizePolicy>
#include <QFont>
#include <QBitArray>
#include <QUuid>

#include <utility> // as_const

namespace camitk {

// -------------------- Constructor --------------------
PropertyObject::PropertyObject(QString name) : QObject() {
    setObjectName(name);
    Property* prop = new Property("uuid", QUuid(), "Unique Identifier", "");
    prop->setReadOnly(true);
    PropertyObject::addProperty(prop);
}

// -------------------- Destructor --------------------
PropertyObject::~PropertyObject() {
    // delete all properties
    for (auto prop : std::as_const(propertiesMap)) {
        delete prop;
    }
    propertiesMap.clear();
}

// -------------------- addProperty --------------------
bool PropertyObject::addProperty(Property* prop) {
    // add to the map
    propertiesMap.insert(prop->getName(), prop);

    // add a dynamic Qt Meta Object property with the same name
    return setProperty(prop->getName().toStdString().c_str(), prop->getInitialValue());
}

// -------------------- getProperty --------------------
const Property* PropertyObject::getProperty(QString name) const {
    return propertiesMap.value(name);
}
camitk::Property* PropertyObject::getProperty(QString name) {
    return propertiesMap.value(name);
}


// -------------------- removeProperty --------------------
void PropertyObject::removeProperty(Property* prop) {
    // remove the given property from the object by setting its value to an invalid variant
    setProperty(prop->getName().toStdString().c_str(), QVariant());
}

// -------------------- getNumberOfProperties --------------------
unsigned int PropertyObject::getNumberOfProperties() const {
    return propertiesMap.size();
}

// -------------------- getPropertyName --------------------
QString PropertyObject::getPropertyName(unsigned int index) const {
    if ((int) index < propertiesMap.size()) {
        return propertiesMap.keys().at(index);
    }
    else {
        return QString(); // null and empty QString
    }
}

// -------------------- getPropertyValue --------------------
QVariant PropertyObject::getPropertyValue(unsigned int index) {
    if ((int) index < propertiesMap.size()) {
        return property(getPropertyName(index).toStdString().c_str());
    }
    else {
        return QVariant(); // invalid QVariant
    }
}

QVariant PropertyObject::getPropertyValue(const QString name) const {
    if (propertiesMap.contains(name)) {
        return property(name.toStdString().c_str());
    }
    else {
        return QVariant(); // invalid QVariant
    }
}

// ---------------------- toCamelCase ------------------
QString PropertyObject::toCamelCase(const QString& s) {
    // split lowercased word separated by space into parts
    QStringList parts = s.toLower().split(QLatin1Char(' '), Qt::SkipEmptyParts);
    // capitalize firt letter of every word (but not the first word)
    for (int i = 1; i < parts.size(); ++i) {
        parts[i].replace(0, 1, parts[i][0].toUpper());
    }
    // join every word again
    return parts.join("");
}

// ---------------------- toVariant ------------------
QVariant PropertyObject::toVariant() const {
    QVariantMap map;
    for (QString name : propertiesMap.keys()) {
        if (!getProperty(name)->getReadOnly()) {
            map.insert(name, getPropertyValue(name));
        }
    }
    return map;
}

// ---------------------- fromVariant ------------------
void PropertyObject::fromVariant(const QVariant& variant) {
    PersistenceManager::loadProperties(this, variant);
}

// ---------------------- getUuid ------------------
QUuid PropertyObject::getUuid() const {
    return this->getPropertyValue("uuid").toUuid();
}

// ---------------------- setUuid ------------------
bool PropertyObject::setUuid(QUuid uuid) {
    if (this->getPropertyValue("uuid").toUuid().isNull()) {
        this->setProperty("uuid", QVariant(uuid));
        return true;
    }
    else {
        return false;
    }

}



// ---------------------- loadFromSettings ------------------
void PropertyObject::loadFromSettings(const QString& settingGroupName) {
    // settings
    QSettings& settings = Application::getSettings();
    settings.beginGroup(settingGroupName);

    for (unsigned int i = 0; i < getNumberOfProperties(); i++) {
        QByteArray propertyName = getPropertyName(i).toLocal8Bit();
        QVariant propertySettingsValue = settings.value(toCamelCase(propertyName), getPropertyValue(QString(propertyName)));
        switch (static_cast<QMetaType::Type>(getProperty(QString(propertyName))->getInitialValue().type())) {
            case QMetaType::Int:
                setProperty(propertyName, propertySettingsValue.toInt());
                break;
            case QMetaType::Bool:
                setProperty(propertyName, propertySettingsValue.toBool());
                break;
            case QMetaType::Double:
                setProperty(propertyName, propertySettingsValue.toDouble());
                break;
            case QMetaType::QString:
                setProperty(propertyName, propertySettingsValue.toString());
                break;
            case QMetaType::QVector3D:
                setProperty(propertyName, propertySettingsValue.value<QVector3D>());
                break;
            case QMetaType::QColor:
                setProperty(propertyName, propertySettingsValue.value<QColor>());
                break;
            case QMetaType::QDate:
                setProperty(propertyName, propertySettingsValue.toDate());
                break;
            case QMetaType::QTime:
                setProperty(propertyName, propertySettingsValue.toTime());
                break;
            case QMetaType::QChar:
                setProperty(propertyName, propertySettingsValue.toChar());
                break;
            case QMetaType::QDateTime:
                setProperty(propertyName, propertySettingsValue.toDateTime());
                break;
            case QMetaType::QPoint:
                setProperty(propertyName, propertySettingsValue.toPoint());
                break;
            case QMetaType::QPointF:
                setProperty(propertyName, propertySettingsValue.toPointF());
                break;
            case QMetaType::QKeySequence:
                setProperty(propertyName, propertySettingsValue.value<QKeySequence>());
                break;
            case QMetaType::QLocale:
                setProperty(propertyName, propertySettingsValue.toLocale());
                break;
            case QMetaType::QSize:
                setProperty(propertyName, propertySettingsValue.toSize());
                break;
            case QMetaType::QSizeF:
                setProperty(propertyName, propertySettingsValue.toSizeF());
                break;
            case QMetaType::QRect:
                setProperty(propertyName, propertySettingsValue.toRect());
                break;
            case QMetaType::QRectF:
                setProperty(propertyName, propertySettingsValue.toRectF());
                break;
            case QMetaType::QSizePolicy:
                setProperty(propertyName, propertySettingsValue.value<QSizePolicy>());
                break;
            case QMetaType::QFont:
                setProperty(propertyName, propertySettingsValue.value<QFont>());
                break;
            case QMetaType::QCursor:
                setProperty(propertyName, propertySettingsValue.value<QCursor>());
                break;
            case QMetaType::Float:
                setProperty(propertyName, propertySettingsValue.toFloat());
                break;
            case QMetaType::QBitArray:
                setProperty(propertyName, propertySettingsValue.toBitArray());
                break;
            case QMetaType::QByteArray:
                setProperty(propertyName, propertySettingsValue.toByteArray());
                break;
            case QMetaType::QLine:
                setProperty(propertyName, propertySettingsValue.toLine());
                break;
            case QMetaType::QLineF:
                setProperty(propertyName, propertySettingsValue.toLineF());
                break;
            default:
                setProperty(propertyName, propertySettingsValue);
                break;
        }
    }

    settings.endGroup();
}

// ---------------------- saveSettings ------------------
void PropertyObject::saveToSettings(const QString& settingGroupName) {
    QSettings& settings = Application::getSettings();
    settings.beginGroup(settingGroupName);

    for (unsigned int i = 0; i < getNumberOfProperties(); i++) {
        QByteArray propertyName = getPropertyName(i).toLocal8Bit();
        settings.setValue(toCamelCase(propertyName), getPropertyValue(QString(propertyName)));
    }

    settings.endGroup();
}

}
