/*****************************************************************************
 * $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 "VariantDataModel.h"

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

#include <iostream> // for std::cerr
#include <ranges> // for all_of and iota

// static invalid value
VariantDataModel VariantDataModel::invalidVariantDataModel;

// -------------------- invalid --------------------
VariantDataModel& VariantDataModel::invalid() {
    return invalidVariantDataModel;
}

// -------------------- constructors --------------------
VariantDataModel::VariantDataModel() {
    simpleValue = QVariant();
    isModifiedFlag = false;
    type = Atomic;
    name = "Invalid";
}

VariantDataModel::VariantDataModel(const VariantDataModel& other) {
    simpleValue = other.simpleValue;
    list = other.list;
    map = other.map;
    isModifiedFlag = other.isModifiedFlag;
    type = other.type;
    name = other.name;
}

VariantDataModel::VariantDataModel(const QVariant& variant) {
    if (variant.userType() == QMetaType::QStringList) {
        for (QString s : variant.toStringList()) {
            list.append(VariantDataModel(s));
        }
        isModifiedFlag = false;
        type = List;
        name = typeString();
    }
    else {
        simpleValue = variant;
        isModifiedFlag = false;
        type = Atomic;
        name = typeString();
    }
}

VariantDataModel::VariantDataModel(const QVariantList& variant) {
    copyList(variant);
    isModifiedFlag = false;
    type = List;
    name = typeString();
}

VariantDataModel::VariantDataModel(const QVariantMap& variant) {
    copyMap(variant);
    isModifiedFlag = false;
    type = Map;
    name = typeString();
}

// -------------------- copyList --------------------
void VariantDataModel::copyList(const QVariantList& variant) {
    for (const auto& variant : variant) {
        switch (variant.userType()) {
            case QMetaType::QVariantList:
            case QMetaType::QStringList:
                list.append(VariantDataModel(variant.toList()));
                break;
            case QMetaType::QVariantMap:
                list.append(VariantDataModel(variant.toMap()));
                break;
            default:
                list.append(VariantDataModel(variant));
                break;
        }
    }
}

// -------------------- copyMap --------------------
void VariantDataModel::copyMap(const QVariantMap& variant) {
    for (const auto& key : variant.keys()) {
        switch (variant.value(key).userType()) {
            case QMetaType::QVariantList:
            case QMetaType::QStringList: {
                map.insert(key, VariantDataModel(variant.value(key).toList()));
            }
            break;
            case QMetaType::QVariantMap:
                map.insert(key, VariantDataModel(variant.value(key).toMap()));
                break;
            default:
                map.insert(key, VariantDataModel(variant.value(key)));
                break;
        }
    }
}

// -------------------- fromJsonString --------------------
const VariantDataModel VariantDataModel::fromJsonString(const QString& jsonString) {
    VariantDataModel vdm;
    QJsonParseError jsonParseError;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8(), &jsonParseError);
    if (jsonDoc.isNull()) {
        // extract the string from the error offset to the next return carriage
        QString jsonErrorData(jsonString.mid(jsonParseError.offset, jsonString.indexOf("\n", jsonParseError.offset) - jsonParseError.offset));
        int lineNr = jsonString.left(jsonParseError.offset).count('\n');
        if (lineNr == 0) {
            std::cerr << "Error creating VariantDataModel from string \"" << jsonString.toStdString() << "\":\n" << jsonParseError.errorString().toStdString() << ":\n" << jsonErrorData.toStdString() << "\n" << "Please check that the input string is a valid JSON string\n";
        }
        else {
            QString errorLine(jsonString.split('\n')[lineNr - 1]);
            std::cerr << "Error creating JSON from string \"" << jsonString.toStdString() << "\":\n" << jsonParseError.errorString().toStdString() << ":\n" << jsonErrorData.toStdString() << "\nOn line " << lineNr << ":\n" << errorLine.toStdString() << "\n";
        }
        return vdm;
    }
    else if (!jsonDoc.isObject()) {
        std::cerr << "Error creating JSON from string \"" << jsonString.toStdString() << "\":\n: JSON document is not an object\n";
        return vdm;
    }
    else {
        vdm.copyMap(jsonDoc.object().toVariantMap());
        vdm.isModifiedFlag = false;
        vdm.type = Map;
        vdm.name = vdm.typeString();
        return vdm;
    }
}

// -------------------- fromJsonObject --------------------
const VariantDataModel VariantDataModel::fromJsonObject(const QJsonObject& jsonObject)  {
    VariantDataModel vdm;
    vdm.copyMap(jsonObject.toVariantMap());
    vdm.isModifiedFlag = false;
    vdm.type = Map;
    vdm.name = vdm.typeString();
    return vdm;
}

// -------------------- getName --------------------
QString VariantDataModel::getName() {
    return name;
}

// -------------------- operator= --------------------
VariantDataModel& VariantDataModel::operator=(QVariant newValue) {
    simpleValue = QVariant();
    list.clear();
    map.clear();
    switch (newValue.userType()) {
        case QMetaType::QVariantList:
        case QMetaType::QStringList: {
            VariantDataModel node(newValue.toList());
            list = node.list;
            type = List;
        }
        break;
        case QMetaType::QVariantMap: {
            VariantDataModel node(newValue.toMap());
            map = node.map;
            type = Map;
        }
        break;
        default:
            simpleValue = newValue;
            type = Atomic;
            break;
    }
    return *this;
}

// -------------------- operator== --------------------
bool VariantDataModel::operator==(const VariantDataModel& other) const {
    if (type == Atomic) {
        return simpleValue == (other.simpleValue);
    }
    else if (type == List) {
        return list == (other.list);
    }
    else {   // Map
        return map == (other.map);
    }
}

bool VariantDataModel::operator==(const QVariant& v) const {
    if (type == Atomic) {
        return simpleValue == v;
    }
    else if (type == List) {
        if (v.userType() == QMetaType::QVariantList) {
            QVariantList vl = v.toList();
            if (vl.size() != list.size()) {
                return false;
            }
            else {
                return std::ranges::all_of(std::views::iota(0, list.size()),
                [&](int index) {
                    return list[index] == vl[index];
                });
            }
        }
        else {
            if (v.userType() == QMetaType::QStringList) {
                QStringList vsl = v.toStringList();
                if (vsl.size() != list.size()) {
                    return false;
                }
                else {
                    return std::ranges::all_of(std::views::iota(0, list.size()),
                    [&](int index) {
                        return list[index] == QVariant(vsl[index]);
                    });
                }
            }
            else {
                // v is not a list
                return false;
            }
        }
    }
    else {   // Map
        QVariantMap vm = v.toMap();
        if (map.keys() != vm.keys()) {
            return false;
        }
        else {
            return std::ranges::all_of(map.keys(),
            [&](QString key) {
                return map[key] == vm[key];
            });
        }
    }
}

bool VariantDataModel::operator==(const QVariantList& v) const {
    return (*this) == QVariant(v);
}

bool VariantDataModel::operator==(const QVariantMap& v) const {
    return (*this) == QVariant(v);
}

// -------------------- operator!= --------------------
bool VariantDataModel::operator!=(const VariantDataModel& other) const {
    return !((*this) == other);
}

bool VariantDataModel::operator!=(const QVariant& v) const {
    return !((*this) == v);
}

bool VariantDataModel::operator!=(const QVariantList& v) const {
    return !((*this) == v);
}

bool VariantDataModel::operator!=(const QVariantMap& v) const {
    return !((*this) == v);
}

// -------------------- getValue --------------------
QVariant VariantDataModel::getValue() const {
    if (type == Atomic) {
        return simpleValue;
    }
    else if (type == List) {
        QVariantList variantList;
        for (const auto& node : list) {
            QVariant itemValue = node.getValue();
            if (itemValue.isValid()) {
                variantList.append(node.getValue());
            }
        }
        return variantList;
    }
    else {
        // Map
        QVariantMap variantMap;
        for (const auto& key : map.keys()) {
            QVariant itemValue = map.value(key).getValue();
            if (itemValue.isValid()) {
                variantMap.insert(key, map.value(key).getValue());
            }
        }
        return variantMap;
    }
}

// -------------------- toString --------------------
QString VariantDataModel::toString() const {
    return getValue().toString();
}

// -------------------- operator QString() --------------------
VariantDataModel::operator QString() const {
    return toString();
}

// -------------------- toJsonString --------------------
QString VariantDataModel::toJsonString() const {
    if (type == Atomic) {
        QJsonValue val = simpleValue.toJsonValue();
        if (val.type() == QJsonValue::String) {
            return "\"" + val.toString() + "\"";
        }
        else if (val.type() == QJsonValue::Undefined || val.type() == QJsonValue::Array || val.type() == QJsonValue::Object) {
            // when toJsonValue failed or if it is transformed to Object or Array (which should never happen),
            // do the conversion manually
            return "\"" + simpleValue.toString() + "\"";
        }
        else {
            return simpleValue.toString();
        }
    }
    else if (type == List) {
        QStringList stringValues;
        for (const auto& node : list) {
            // do not add empty node (i.e. invalid data model, that were, for instance deleted)
            QString nodeJsonString = node.toJsonString();
            if (!nodeJsonString.isNull()) {
                stringValues.append(node.toJsonString());
            }
        }
        return "[" + stringValues.join(",") + "]";
    }
    else {
        // Map
        QStringList stringValues;
        for (const auto& key : map.keys()) {
            // replace invalid data model by null
            QString valueJsonString = map.value(key).toJsonString();
            if (valueJsonString.isNull()) {
                valueJsonString = "null";
            }
            stringValues.append("\"" + key + "\":" + map.value(key).toJsonString());
        }
        return "{" + stringValues.join(",") + "}";
    }
    return QString();
}

// -------------------- remove --------------------
void VariantDataModel::remove() {
    simpleValue = QVariant();
    list.clear();
    map.clear();
    type = Atomic;
    isModifiedFlag = true;
}

// -------------------- value --------------------
VariantDataModel& VariantDataModel::value(const QString& key) {
    if (type == Map) {
        return map[key];
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot get value of key \"" << key.toStdString() << "\"\n";
        return invalid();
    }
}

// -------------------- operator[](QString) --------------------
VariantDataModel& VariantDataModel::operator[](const QString& key) {
    if (type == Map) {
        return map[key];
    }
    else if (type == List) {
        bool isInteger;
        int listIndex = key.toInt(&isInteger);
        if (isInteger) {
            if (listIndex >= 0 && listIndex < list.size()) {
                return list[listIndex];
            }
            else {
                std::cerr << typeString().toStdString() << ": cannot get value of index " << key.toStdString() << " (operator []): out of bounds\n";
                return invalid();
            }
        }
        else {
            std::cerr << typeString().toStdString() << ": cannot get value of index \"" << key.toStdString() << "\" (operator []): not a number\n";
            return invalid();
        }
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot get value of key \"" << key.toStdString() << "\" (operator []): not a map nor a list\n";
        return invalid();
    }
}

const VariantDataModel VariantDataModel::operator[](const QString& key) const {
    if (type == Map) {
        return map[key];
    }
    else if (type == List) {
        bool isInteger;
        int listIndex = key.toInt(&isInteger);
        if (isInteger) {
            if (listIndex >= 0 && listIndex < list.size()) {
                return list[listIndex];
            }
            else {
                std::cerr << typeString().toStdString() << ": cannot get value of index " << key.toStdString() << " (operator []): out of bounds\n";
                return invalid();
            }
        }
        else {
            std::cerr << typeString().toStdString() << ": cannot get value of index \"" << key.toStdString() << "\" (operator []): not a number\n";
            return invalid();
        }
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot get value of key \"" << key.toStdString() << "\" (operator []): not a map nor a list\n";
        return invalid();
    }
}

// -------------------- operator[](int) --------------------
VariantDataModel& VariantDataModel::operator[](int i) {
    return at(i);
}

const VariantDataModel VariantDataModel::operator[](int i) const {
    if (type == List) {
        if (i >= 0 && i < list.size()) {
            return list[i];
        }
        else {
            std::cerr << typeString().toStdString() << ": cannot get value at " << i << ": out of bounds\n";
            return invalid();
        }
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot get value at index " << i << ": not a list\n";
        return invalid();
    }
}

// -------------------- at --------------------
VariantDataModel& VariantDataModel::at(int i) {
    if (type == List) {
        if (i >= 0 && i < list.size()) {
            return list[i];
        }
        else {
            std::cerr << typeString().toStdString() << ": cannot get value at " << i << ": out of bounds\n";
            return invalid();
        }
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot get value at index " << i << ": not a list\n";
        return invalid();
    }
}
// -------------------- last --------------------
VariantDataModel& VariantDataModel::last() {
    if (type == Atomic) {
        return (*this);
    }
    else if (type == List) {
        return list.last();
    }
    else {
        // Map
        return map.last();
    }
}

// -------------------- size --------------------
int VariantDataModel::size() const {
    if (type == Atomic) {
        return (isEmpty()) ? 1 : 0;
    }
    else if (type == List) {
        return list.size();
    }
    else {   // Map
        return map.size();
    }
}

// -------------------- isEmpty --------------------
bool VariantDataModel::isEmpty() const {
    if (type == Atomic) {
        return !simpleValue.isValid();
    }
    else if (type == List) {
        return list.isEmpty();
    }
    else {   // Map
        return map.isEmpty();
    }
}

// -------------------- isValid --------------------
bool VariantDataModel::isValid() const {
    return (*this) != invalid();
}

// -------------------- contains --------------------
bool VariantDataModel::contains(const QString& key) const {
    if (type == Map) {
        return map.contains(key);
    }
    else {   // Map
        std::cerr << typeString().toStdString() << ": cannot call contains(" << key.toStdString() << "): not a map\n";
        return false;
    }
}

// -------------------- insert --------------------
void VariantDataModel::insert(const QString& key, const VariantDataModel& value) {
    if (type == Map) {
        map.insert(key, value);
        isModifiedFlag = true;
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot insert(" << key.toStdString() << ", VariantDataModel*): not a map\n";
    }
}

void VariantDataModel::insert(const QString& key, const QVariant& value) {
    if (type == Map) {
        switch (value.userType()) {
            case QMetaType::QVariantList:
            case QMetaType::QStringList:
                insert(key, VariantDataModel(value.toList()));
                break;
            case QMetaType::QVariantMap:
                insert(key, VariantDataModel(value.toMap()));
                break;
            default:
                insert(key, VariantDataModel(value));
                break;
        }
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot insert(" << key.toStdString() << "," << value.toString().toStdString() << "): not a map\n";
    }
}

// -------------------- remove --------------------
int VariantDataModel::remove(const QString& key) {
    if (type == Map) {
        int result = map.remove(key);
        if (result > 0) {
            isModifiedFlag = true;
        }
        return result;
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot remove(" << key.toStdString() << "): not a map\n";
        return 0;
    }
}

// -------------------- removeOne --------------------
bool VariantDataModel::removeOne(const VariantDataModel& item) {
    if (type == List) {
        bool result = list.removeOne(item);
        if (result) {
            isModifiedFlag = true;
        }
        return result;
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot removeOne(" << item.getValue().toString().toStdString() << "): not a list\n";
        return false;
    }
}

// -------------------- removeAt --------------------
bool VariantDataModel::removeAt(int i) {
    if (type == List) {
        bool result = false;
        if (i >= 0 && i < list.size()) {
            list.removeAt(i);
            result = true;
        }
        if (result) {
            isModifiedFlag = true;
        }
        return result;
    }
    else {
        std::cerr << typeString().toStdString() << ": cannot removeAt(" << i << "): not a list\n";
        return false;
    }
}

// -------------------- append --------------------
void VariantDataModel::append(const VariantDataModel& item) {
    if (type == List) {
        list.append(item);
        isModifiedFlag = true;
    }
    else {   // Map
        std::cerr << typeString().toStdString() << ": cannot append(" << item.getValue().toString().toStdString() << "): not a list\n";
    }
}

// -------------------- isModified --------------------
bool VariantDataModel::isModified() const {
    if (type == Atomic) {
        return isModifiedFlag;
    }
    else if (type == List) {
        bool allModifiedFlag = isModifiedFlag;
        auto it = list.constBegin();
        while (it != list.constEnd() && !allModifiedFlag) {
            allModifiedFlag = allModifiedFlag || it->isModified();
            ++it;
        }
        return allModifiedFlag;
    }
    else {   // Map
        bool allModifiedFlag = isModifiedFlag;
        auto it = map.constBegin();
        while (it != map.constEnd() && !allModifiedFlag) {
            allModifiedFlag = allModifiedFlag || it.value().isModified();
            ++it;
        }
        return allModifiedFlag;
    }
}

// -------------------- reset --------------------
void VariantDataModel::reset() {
    isModifiedFlag = false;
    for (auto& node : list) {
        node.reset();
    }
    for (auto& node : map) {
        node.reset();
    }
}

// -------------------- typeString --------------------
QString VariantDataModel::typeString() const {
    if (type == Atomic) {
        return "Atomic";
    }
    else if (type == List) {
        return "List";
    }
    else {
        return "Map";
    }
}

// -------------------- setName --------------------
void VariantDataModel::setName(const QString& name) {
    this->name = name;

}

// -------------------- begin --------------------
QList<VariantDataModel>::iterator VariantDataModel::begin() {
    return list.begin();
}

QList<VariantDataModel>::const_iterator VariantDataModel::begin() const {
    return list.begin();
}

// -------------------- end --------------------
QList<VariantDataModel>::iterator VariantDataModel::end() {
    return list.end();
}

QList<VariantDataModel>::const_iterator VariantDataModel::end() const {
    return list.end();
}

// -------------------- beginMap --------------------
QMap<QString, VariantDataModel>::iterator VariantDataModel::beginMap() {
    return map.begin();
}

QMap<QString, VariantDataModel>::const_iterator VariantDataModel::beginMap() const {
    return map.begin();
}

// -------------------- endMap --------------------
QMap<QString, VariantDataModel>::iterator VariantDataModel::endMap() {
    return map.end();
}

QMap<QString, VariantDataModel>::const_iterator VariantDataModel::endMap() const {
    return map.end();
}
