// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "uniformmodel.h"
#include "propertyhandler.h"
#include "effectmanager.h"

UniformModel::UniformModel(QObject *parent)
    : QAbstractListModel(parent)
{

}

void UniformModel::setModelData(UniformTable *data)
{
    beginResetModel();
    m_uniformTable = data;
    endResetModel();
    updateCanMoveStatus();
    Q_EMIT uniformsChanged();
}

int UniformModel::rowCount(const QModelIndex &) const
{
    return int((m_uniformTable != nullptr) ? m_uniformTable->size() : 0);
}

QVariant UniformModel::data(const QModelIndex &index, int role) const
{
    if (!m_uniformTable || !index.isValid())
        return QVariant();

    if (index.row() >= m_uniformTable->size())
        return false;

    const auto &uniform = (*m_uniformTable)[index.row()];

    if (role == Type) {
        return QVariant::fromValue(uniform.type);
    } else if (role == NodeId) {
        return QVariant::fromValue(uniform.nodeId);
    } else if (role == Name) {
        return QVariant::fromValue(QString::fromLatin1(uniform.name));
    } else if (role == Value) {
        switch (uniform.type) {
        case Uniform::Type::Sampler:
            return uniform.value.value<QString>();
        case Uniform::Type::Bool:
            return uniform.value.value<bool>();
        case Uniform::Type::Int:
            return uniform.value.value<int>();
        case Uniform::Type::Float:
            return uniform.value.value<double>();
        case Uniform::Type::Vec2:
            return uniform.value.value<QVector2D>();
        case Uniform::Type::Vec3:
            return uniform.value.value<QVector3D>();
        case Uniform::Type::Vec4:
            return uniform.value.value<QVector4D>();
        case Uniform::Type::Color:
            return uniform.value.value<QColor>();
        case Uniform::Type::Define:
            return uniform.value.value<QString>();
        }
    } else if (role == DefaultValue) {
        switch (uniform.type) {
        case Uniform::Type::Sampler:
            return uniform.defaultValue.value<QString>();
        case Uniform::Type::Bool:
            return uniform.defaultValue.value<bool>();
        case Uniform::Type::Int:
            return uniform.defaultValue.value<int>();
        case Uniform::Type::Float:
            return uniform.defaultValue.value<double>();
        case Uniform::Type::Vec2:
            return uniform.defaultValue.value<QVector2D>();
        case Uniform::Type::Vec3:
            return uniform.defaultValue.value<QVector3D>();
        case Uniform::Type::Vec4:
            return uniform.defaultValue.value<QVector4D>();
        case Uniform::Type::Color:
            return uniform.defaultValue.value<QColor>();
        case Uniform::Type::Define:
            return uniform.defaultValue.value<QString>();
        }
    } else if (role == MinValue) {
        switch (uniform.type) {
        case Uniform::Type::Sampler:
            return uniform.minValue.value<QString>();
        case Uniform::Type::Bool:
            return uniform.minValue.value<bool>();
        case Uniform::Type::Int:
            return uniform.minValue.value<int>();
        case Uniform::Type::Float:
            return uniform.minValue.value<double>();
        case Uniform::Type::Vec2:
            return uniform.minValue.value<QVector2D>();
        case Uniform::Type::Vec3:
            return uniform.minValue.value<QVector3D>();
        case Uniform::Type::Vec4:
            return uniform.minValue.value<QVector4D>();
        case Uniform::Type::Color:
            return uniform.minValue.value<QColor>();
        case Uniform::Type::Define:
            return uniform.minValue.value<QString>();
        }
    } else if (role == MaxValue) {
        switch (uniform.type) {
        case Uniform::Type::Sampler:
            return uniform.maxValue.value<QString>();
        case Uniform::Type::Bool:
            return uniform.maxValue.value<bool>();
        case Uniform::Type::Int:
            return uniform.maxValue.value<int>();
        case Uniform::Type::Float:
            return uniform.maxValue.value<double>();
        case Uniform::Type::Vec2:
            return uniform.maxValue.value<QVector2D>();
        case Uniform::Type::Vec3:
            return uniform.maxValue.value<QVector3D>();
        case Uniform::Type::Vec4:
            return uniform.maxValue.value<QVector4D>();
        case Uniform::Type::Color:
            return uniform.maxValue.value<QColor>();
        case Uniform::Type::Define:
            return uniform.maxValue.value<QString>();
        }
    } else if (role == Description) {
        return QVariant::fromValue(uniform.description);
    } else if (role == CustomValue) {
        return QVariant::fromValue(uniform.customValue);
    } else if (role == UseCustomValue) {
        return QVariant::fromValue(uniform.useCustomValue);
    } else if (role == Visible) {
        return QVariant::fromValue(uniform.visible);
    } else if (role == ExportProperty) {
        return QVariant::fromValue(uniform.exportProperty);
    } else if (role == CanMoveUp) {
        return QVariant::fromValue(uniform.canMoveUp);
    } else if (role == CanMoveDown) {
        return QVariant::fromValue(uniform.canMoveDown);
    } else if (role == EnableMipmap) {
        return QVariant::fromValue(uniform.enableMipmap);
    } else if (role == ExportImage) {
        return QVariant::fromValue(uniform.exportImage);
    }

    return QVariant();
}

QHash<int, QByteArray> UniformModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[Type] = "type";
    roles[Name] = "name";
    roles[Value] = "value";
    roles[DefaultValue] = "defaultValue";
    roles[MinValue] = "minValue";
    roles[MaxValue] = "maxValue";
    roles[NodeId] = "nodeId";
    roles[Description] = "description";
    roles[CustomValue] = "customValue";
    roles[UseCustomValue] = "useCustomValue";
    roles[Visible] = "visible";
    roles[ExportProperty] = "exportProperty";
    roles[CanMoveUp] = "canMoveUp";
    roles[CanMoveDown] = "canMoveDown";
    roles[EnableMipmap] = "enableMipmap";
    roles[ExportImage] = "exportImage";
    return roles;
}

bool UniformModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || !m_uniformTable)
        return false;

    if (index.row() >= m_uniformTable->size())
        return false;

    auto &uniform = (*m_uniformTable)[index.row()];

    bool ok = true;

    if (role == Type) {
        const auto v = value.toInt(&ok);
        if (ok)
            uniform.type = static_cast<Uniform::Type>(v);
    } else if (role == Name) {
        uniform.name = value.toString().toUtf8();
    } else if (role == Value) {
        switch (uniform.type) {
        case Uniform::Type::Bool:
        case Uniform::Type::Int:
        case Uniform::Type::Float:
        case Uniform::Type::Vec2:
        case Uniform::Type::Vec3:
        case Uniform::Type::Vec4:
        case Uniform::Type::Color:
        case Uniform::Type::Sampler:
        case Uniform::Type::Define:
            uniform.value = value;
            break;
        }
    } else if (role == DefaultValue) {
        switch (uniform.type) {
        case Uniform::Type::Bool:
        case Uniform::Type::Int:
        case Uniform::Type::Float:
        case Uniform::Type::Vec2:
        case Uniform::Type::Vec3:
        case Uniform::Type::Vec4:
        case Uniform::Type::Color:
        case Uniform::Type::Sampler:
        case Uniform::Type::Define:
            uniform.defaultValue = value;
            break;
        }
    } else if (role == MinValue) {
        switch (uniform.type) {
        case Uniform::Type::Bool:
        case Uniform::Type::Int:
        case Uniform::Type::Float:
        case Uniform::Type::Vec2:
        case Uniform::Type::Vec3:
        case Uniform::Type::Vec4:
        case Uniform::Type::Color:
        case Uniform::Type::Sampler:
        case Uniform::Type::Define:
            uniform.minValue = value;
            break;
        }
    } else if (role == MaxValue) {
        switch (uniform.type) {
        case Uniform::Type::Bool:
        case Uniform::Type::Int:
        case Uniform::Type::Float:
        case Uniform::Type::Vec2:
        case Uniform::Type::Vec3:
        case Uniform::Type::Vec4:
        case Uniform::Type::Color:
        case Uniform::Type::Sampler:
        case Uniform::Type::Define:
            uniform.maxValue = value;
            break;
        }
    } else if (role == Description) {
        uniform.description = value.toString();
    } else if (role == CustomValue) {
        uniform.customValue = value.toString();
    } else if (role == UseCustomValue) {
        uniform.useCustomValue = value.toBool();
    } else if (role == ExportProperty) {
        uniform.exportProperty = value.toBool();
    } else if (role == CanMoveUp) {
        uniform.canMoveUp = value.toBool();
    } else if (role == CanMoveDown) {
        uniform.canMoveDown = value.toBool();
    } else if (role == EnableMipmap) {
        uniform.enableMipmap = value.toBool();
    } else if (role == ExportImage) {
        uniform.exportImage = value.toBool();
    }

    if (role == Value) {
        // Don't update uniforms when value is changed, as those
        // go through g_propertyData
        g_propertyData.insert(uniform.name, value);
        if (m_effectManager)
            m_effectManager->setUnsavedChanges(true);
    }
    if (role != Value || !uniform.exportProperty) {
        // But non-exported (const value) properties need update
        Q_EMIT uniformsChanged();
    }

    if (ok)
        emit dataChanged(index, index, {role});

    return ok;
}

// Initialize the value variant with correct type
QVariant UniformModel::getInitializedVariant(Uniform::Type type, bool maxValue)
{
    switch (type) {
    case Uniform::Type::Sampler:
        return QString();
    case Uniform::Type::Bool:
        return maxValue ? true : false;
    case Uniform::Type::Int:
        return maxValue ? 100 : 0;
    case Uniform::Type::Float:
        return maxValue ? 1.0 : 0.0;
    case Uniform::Type::Vec2:
        return maxValue ? QVector2D(1.0, 1.0) : QVector2D(0.0, 0.0);
    case Uniform::Type::Vec3:
        return maxValue ? QVector3D(1.0, 1.0, 1.0) : QVector3D(0.0, 0.0, 0.0);
    case Uniform::Type::Vec4:
        return maxValue ? QVector4D(1.0, 1.0, 1.0, 1.0) : QVector4D(0.0, 0.0, 0.0, 0.0);
    case Uniform::Type::Color:
        return maxValue ? QColor::fromRgbF(1.0f, 1.0f, 1.0f, 1.0f) : QColor::fromRgbF(0.0f, 0.0f, 0.0f, 0.0f);
    case Uniform::Type::Define:
        return QString();
    }
    return QVariant();
}

// Return name for the image property Image element
QString UniformModel::getImageElementName(const Uniform &uniform)
{
    if (uniform.value.toString().isEmpty())
        return QStringLiteral("null");
    QString simplifiedName = uniform.name.simplified();
    simplifiedName = simplifiedName.remove(' ');
    return QStringLiteral("imageItem") + simplifiedName;
}

// This will update the row content at rowIndex
// When rowIndex = -1, new row will be appended
bool UniformModel::updateRow(int nodeId, int rowIndex, int type, const QString &id,
                             const QVariant &defaultValue, const QString &description, const QString &customValue,
                             bool useCustomValue, const QVariant &minValue, const QVariant &maxValue,
                             bool enableMipmap, bool exportImage)
{
    if (m_uniformTable == nullptr)
        return false;

    bool addNewRow = false;
    if (rowIndex == -1) {
        addNewRow = true;
        // Add new row after the last of its node so uniforms are
        // in continuous groups per node.
        rowIndex = m_uniformTable->size();
        for (int i = m_uniformTable->size() - 1; i >= 0; i--) {
            auto &uniform = (*m_uniformTable)[i];
            if (uniform.nodeId == nodeId) {
                rowIndex = i + 1;
                break;
            }
        }
    }

    if (addNewRow && !validateUniformName(id))
        return false;

    if (addNewRow)
        beginInsertRows(QModelIndex(), rowIndex, rowIndex);

    Uniform newUniform = { };
    auto &uniform = addNewRow ? newUniform : (*m_uniformTable)[rowIndex];

    uniform.type = Uniform::Type(type);
    uniform.name = id.toLocal8Bit();
    uniform.description = description;
    uniform.customValue = customValue;
    uniform.useCustomValue = useCustomValue;
    uniform.enableMipmap = enableMipmap;
    uniform.exportImage = exportImage;
    switch (uniform.type) {
    case Uniform::Type::Bool:
        uniform.defaultValue = defaultValue;
        break;
    case Uniform::Type::Int:
    case Uniform::Type::Float:
    case Uniform::Type::Vec2:
    case Uniform::Type::Vec3:
    case Uniform::Type::Vec4:
    case Uniform::Type::Color:
    case Uniform::Type::Define:
        uniform.defaultValue = defaultValue;
        uniform.minValue = minValue;
        uniform.maxValue = maxValue;
        break;
    case Uniform::Type::Sampler:
        if (defaultValue.metaType().id() == QMetaType::QUrl)
            uniform.defaultValue = defaultValue.toString();
        else
            uniform.defaultValue = "";
        // Update the mipmap property
        QString mipmapProperty = m_effectManager->mipmapPropertyName(uniform.name);
        g_propertyData[mipmapProperty] = enableMipmap;
        break;
    }

    if (addNewRow) {
        // When adding a property, value is default value
        uniform.value = uniform.defaultValue;
        g_propertyData.insert(uniform.name, uniform.value);
        // Note: NodeId is only updated when adding new property
        // Properties can't be moved to other nodes
        uniform.nodeId = nodeId;
        m_uniformTable->insert(rowIndex, uniform);
        endInsertRows();
        updateCanMoveStatus();
        if (uniform.type == Uniform::Type::Sampler) {
            QString fs = "\n";
            fs += "// *** Autogenerated - Remove if not needed ***\n";
            fs += "// vec4 " + uniform.name + "Tex = texture(" + uniform.name + ", texCoord);\n";
            Q_EMIT addFSCode(fs);
        }
    } else {
        if (!uniform.useCustomValue)
            g_propertyData.insert(uniform.name, uniform.value);
        Q_EMIT dataChanged(QAbstractItemModel::createIndex(rowIndex, 0),
                           QAbstractItemModel::createIndex(rowIndex, 0));
    }
    // Update QML component as useCustomValue, customValue etc. might have changed
    Q_EMIT qmlComponentChanged();
    Q_EMIT uniformsChanged();
    return true;
}

bool UniformModel::resetValue(int rowIndex)
{
    auto &uniform = (*m_uniformTable)[rowIndex];
    auto index = QAbstractItemModel::createIndex(rowIndex, 0);
    setData(index, uniform.defaultValue, Value);

    g_propertyData.insert(uniform.name, uniform.defaultValue);

    emit dataChanged(index, index, {Value});

    // Image element may change, so update component
    if (uniform.type == Uniform::Type::Sampler)
        Q_EMIT qmlComponentChanged();
    Q_EMIT uniformsChanged();

    return true;
}

// Updates the image data at rowIndex
bool UniformModel::setImage(int rowIndex, const QVariant &value)
{
    auto &uniform = (*m_uniformTable)[rowIndex];
    uniform.value = value.toString();

    g_propertyData.insert(uniform.name, value);

    emit dataChanged(QAbstractItemModel::createIndex(0, 0),
                     QAbstractItemModel::createIndex(rowIndex, 0));

    // Image element may change, so update component
    if (uniform.type == Uniform::Type::Sampler)
        Q_EMIT qmlComponentChanged();
    Q_EMIT uniformsChanged();
    return true;
}

// Move uniform up/down in the list
void UniformModel::moveIndex(int rowIndex, int direction)
{
    if (m_uniformTable == nullptr)
        return;

    // Note: beginMoveRows index behaves differently than QList move index.
    int destinationRowIndex = rowIndex + 2;
    int destinationListIndex = rowIndex + 1;
    if (direction == -1) {
        destinationRowIndex = rowIndex - 1;
        destinationListIndex = rowIndex - 1;
    }

    if (destinationListIndex >= 0 && m_uniformTable->size() > destinationListIndex) {
        beginMoveRows(QModelIndex(), rowIndex, rowIndex, QModelIndex(), destinationRowIndex);
        m_uniformTable->move(rowIndex, destinationListIndex);
        endMoveRows();
        updateCanMoveStatus();
    }
}

void UniformModel::removeRow(int rowIndex, int rows)
{
    if (m_uniformTable == nullptr)
        return;

    if (m_uniformTable->size() > rowIndex) {
        rows = qBound(1, rows, m_uniformTable->size());
        beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1);
        m_uniformTable->remove(rowIndex, rows);
        endRemoveRows();
        updateCanMoveStatus();

        emit dataChanged(QAbstractItemModel::createIndex(0, 0),
                         QAbstractItemModel::createIndex(rowIndex, 0));
        Q_EMIT uniformsChanged();
    }
}

bool UniformModel::validateUniformName(const QString &uniformName)
{
    if (!m_uniformTable)
        return false;

    // must be unique
    for (const auto &uniform : *m_uniformTable) {
        if (uniform.name == uniformName)
            return false;
    }

    return true;
}

QString UniformModel::typeToProperty(Uniform::Type type)
{
    QString property;
    switch (type) {
    case Uniform::Type::Bool:
        property = "bool";
        break;
    case Uniform::Type::Int:
        property = "int";
        break;
    case Uniform::Type::Float:
        property = "real";
        break;
    case Uniform::Type::Vec2:
        property = "point";
        break;
    case Uniform::Type::Vec3:
        property = "vector3d";
        break;
    case Uniform::Type::Vec4:
        property = "vector4d";
        break;
    case Uniform::Type::Color:
        property = "color";
        break;
    case Uniform::Type::Sampler:
        property = "var";
        break;
    case Uniform::Type::Define:
        property = "var";
        break;
    }
    return property;
}

QString UniformModel::typeToUniform(Uniform::Type type)
{
    QString property;
    switch (type) {
    case Uniform::Type::Bool:
        property = "bool";
        break;
    case Uniform::Type::Int:
        property = "int";
        break;
    case Uniform::Type::Float:
        property = "float";
        break;
    case Uniform::Type::Vec2:
        property = "vec2";
        break;
    case Uniform::Type::Vec3:
        property = "vec3";
        break;
    case Uniform::Type::Vec4:
    case Uniform::Type::Color:
        property = "vec4";
        break;
    case Uniform::Type::Sampler:
        property = "sampler2D";
        break;
    case Uniform::Type::Define:
        property = "define";
        break;
    }
    return property;
}

// Get value in QML format that used for exports
QString UniformModel::valueAsString(const Uniform &uniform)
{
    QString s;
    switch (uniform.type) {
    case Uniform::Type::Bool:
        s = uniform.value.toBool() ? "true" : "false";
        break;
    case Uniform::Type::Int:
        s = QString::number(uniform.value.toInt());
        break;
    case Uniform::Type::Float:
        s = QString::number(uniform.value.toDouble());
        break;
    case Uniform::Type::Vec2:
    {
        QVector2D v2 = uniform.value.value<QVector2D>();
        s = QString("Qt.point(%1, %2)").arg(v2.x()).arg(v2.y());
        break;
    }
    case Uniform::Type::Vec3:
    {
        QVector3D v3 = uniform.value.value<QVector3D>();
        s = QString("Qt.vector3d(%1, %2, %3)").arg(v3.x()).arg(v3.y()).arg(v3.z());
        break;
    }
    case Uniform::Type::Vec4:
    {
        QVector4D v4 = uniform.value.value<QVector4D>();
        s = QString("Qt.vector4d(%1, %2, %3, %4)").arg(v4.x()).arg(v4.y()).arg(v4.z()).arg(v4.w());
        break;
    }
    case Uniform::Type::Color:
    {
        QColor c = uniform.value.value<QColor>();
        s = QString("Qt.rgba(%1, %2, %3, %4)").arg(c.redF()).arg(c.greenF()).arg(c.blueF()).arg(c.alphaF());
        break;
    }
    case Uniform::Type::Sampler:
    {
        s = getImageElementName(uniform);
        break;
    }
    case Uniform::Type::Define:
    {
        s = uniform.value.toString();
        break;
    }
    }
    return s;
}

// Get value in GLSL format that is used for non-exported const properties
QString UniformModel::valueAsVariable(const Uniform &uniform)
{
    QString s;
    switch (uniform.type) {
    case Uniform::Type::Bool:
        s = uniform.value.toBool() ? "true" : "false";
        break;
    case Uniform::Type::Int:
        s = QString::number(uniform.value.toInt());
        break;
    case Uniform::Type::Float:
        s = QString::number(uniform.value.toDouble());
        break;
    case Uniform::Type::Vec2:
    {
        QVector2D v2 = uniform.value.value<QVector2D>();
        s = QString("vec2(%1, %2)").arg(v2.x()).arg(v2.y());
        break;
    }
    case Uniform::Type::Vec3:
    {
        QVector3D v3 = uniform.value.value<QVector3D>();
        s = QString("vec3(%1, %2, %3)").arg(v3.x()).arg(v3.y()).arg(v3.z());
        break;
    }
    case Uniform::Type::Vec4:
    {
        QVector4D v4 = uniform.value.value<QVector4D>();
        s = QString("vec4(%1, %2, %3, %4)").arg(v4.x()).arg(v4.y()).arg(v4.z()).arg(v4.w());
        break;
    }
    case Uniform::Type::Color:
    {
        QColor c = uniform.value.value<QColor>();
        s = QString("vec4(%1, %2, %3, %4)").arg(c.redF()).arg(c.greenF()).arg(c.blueF()).arg(c.alphaF());
        break;
    }
    default:
        qWarning() << QString("Unhandled const variable type: %1").arg(int(uniform.type)).toLatin1();
        break;
    }
    return s;
}

// Get value in QML binding that used for previews
QString UniformModel::valueAsBinding(const Uniform &uniform)
{
    QString s;
    switch (uniform.type) {
    case Uniform::Type::Bool:
    case Uniform::Type::Int:
    case Uniform::Type::Float:
    case Uniform::Type::Define:
        s = "g_propertyData." + uniform.name;
        break;
    case Uniform::Type::Vec2:
    {
        QString sx = QString("g_propertyData.%1.x").arg(uniform.name);
        QString sy = QString("g_propertyData.%1.y").arg(uniform.name);
        s = QString("Qt.point(%1, %2)").arg(sx, sy);
        break;
    }
    case Uniform::Type::Vec3:
    {
        QString sx = QString("g_propertyData.%1.x").arg(uniform.name);
        QString sy = QString("g_propertyData.%1.y").arg(uniform.name);
        QString sz = QString("g_propertyData.%1.z").arg(uniform.name);
        s = QString("Qt.vector3d(%1, %2, %3)").arg(sx, sy, sz);
        break;
    }
    case Uniform::Type::Vec4:
    {
        QString sx = QString("g_propertyData.%1.x").arg(uniform.name);
        QString sy = QString("g_propertyData.%1.y").arg(uniform.name);
        QString sz = QString("g_propertyData.%1.z").arg(uniform.name);
        QString sw = QString("g_propertyData.%1.w").arg(uniform.name);
        s = QString("Qt.vector4d(%1, %2, %3, %4)").arg(sx, sy, sz, sw);
        break;
    }
    case Uniform::Type::Color:
    {
        QString sr = QString("g_propertyData.%1.r").arg(uniform.name);
        QString sg = QString("g_propertyData.%1.g").arg(uniform.name);
        QString sb = QString("g_propertyData.%1.b").arg(uniform.name);
        QString sa = QString("g_propertyData.%1.a").arg(uniform.name);
        s = QString("Qt.rgba(%1, %2, %3, %4)").arg(sr, sg, sb, sa);
        break;
    }
    case Uniform::Type::Sampler:
    {
        s = getImageElementName(uniform);
        break;
    }
    }
    return s;
}

QString UniformModel::variantAsDataString(const Uniform::Type type, const QVariant &variant)
{
    QString s;
    switch (type) {
    case Uniform::Type::Bool:
        s = variant.toBool() ? "true" : "false";
        break;
    case Uniform::Type::Int:
        s = QString::number(variant.toInt());
        break;
    case Uniform::Type::Float:
        s = QString::number(variant.toDouble());
        break;
    case Uniform::Type::Vec2:
    {
        QStringList list;
        QVector2D v2 = variant.value<QVector2D>();
        list << QString::number(v2.x());
        list << QString::number(v2.y());
        s = list.join(", ");
        break;
    }
    case Uniform::Type::Vec3:
    {
        QStringList list;
        QVector3D v3 = variant.value<QVector3D>();
        list << QString::number(v3.x());
        list << QString::number(v3.y());
        list << QString::number(v3.z());
        s = list.join(", ");
        break;
    }
    case Uniform::Type::Vec4:
    {
        QStringList list;
        QVector4D v4 = variant.value<QVector4D>();
        list << QString::number(v4.x());
        list << QString::number(v4.y());
        list << QString::number(v4.z());
        list << QString::number(v4.w());
        s = list.join(", ");
        break;
    }
    case Uniform::Type::Color:
    {
        QStringList list;
        QColor c = variant.value<QColor>();
        list << QString::number(c.redF(), 'g', 3);
        list << QString::number(c.greenF(), 'g', 3);
        list << QString::number(c.blueF(), 'g', 3);
        list << QString::number(c.alphaF(), 'g', 3);
        s = list.join(", ");
        break;
    }
    case Uniform::Type::Sampler:
    case Uniform::Type::Define:
    {
        s = variant.toString();
        break;
    }
    }
    return s;
}

void UniformModel::appendUniform(Uniform uniform)
{
    if (!validateUniformName(uniform.name)) {
        qWarning() << "Invalid uniform name, can't add it";
        return;
    }

    int rowIndex = m_uniformTable->size();
    beginInsertRows(QModelIndex(), rowIndex, rowIndex);
    m_uniformTable->insert(rowIndex, uniform);
    endInsertRows();
    updateCanMoveStatus();

    emit dataChanged(QAbstractItemModel::createIndex(0, 0),
                     QAbstractItemModel::createIndex(rowIndex, 0));

    Q_EMIT uniformsChanged();

}

void UniformModel::setUniformValueData(Uniform *uniform, const QString &value, const QString &defaultValue, const QString &minValue, const QString &maxValue)
{
    if (!uniform)
        return;

    uniform->value = value.isEmpty() ? getInitializedVariant(uniform->type, false) : valueStringToVariant(uniform->type, value);
    uniform->defaultValue = defaultValue.isEmpty() ? getInitializedVariant(uniform->type, false) : valueStringToVariant(uniform->type, defaultValue);
    uniform->minValue = minValue.isEmpty() ? getInitializedVariant(uniform->type, false) : valueStringToVariant(uniform->type, minValue);
    uniform->maxValue = maxValue.isEmpty() ? getInitializedVariant(uniform->type, true) : valueStringToVariant(uniform->type, maxValue);

    // Update the value data in bindings
    g_propertyData.insert(uniform->name, uniform->value);
}

UniformModel::Uniform::Type UniformModel::typeFromString(const QString &typeString) const
{
    if (typeString == "bool")
        return UniformModel::Uniform::Type::Bool;
    else if (typeString == "int")
        return UniformModel::Uniform::Type::Int;
    else if (typeString == "float")
        return UniformModel::Uniform::Type::Float;
    else if (typeString == "vec2")
        return UniformModel::Uniform::Type::Vec2;
    else if (typeString == "vec3")
        return UniformModel::Uniform::Type::Vec3;
    else if (typeString == "vec4")
        return UniformModel::Uniform::Type::Vec4;
    else if (typeString == "color")
        return UniformModel::Uniform::Type::Color;
    else if (typeString == "image")
        return UniformModel::Uniform::Type::Sampler;
    else if (typeString == "define")
        return UniformModel::Uniform::Type::Define;

    qWarning() << QString("Unknown type: %1").arg(typeString).toLatin1();
    return UniformModel::Uniform::Type::Float;
}

QString UniformModel::stringFromType(Uniform::Type type)
{
    QString property;
    switch (type) {
    case Uniform::Type::Bool:
        property = "bool";
        break;
    case Uniform::Type::Int:
        property = "int";
        break;
    case Uniform::Type::Float:
        property = "float";
        break;
    case Uniform::Type::Vec2:
        property = "vec2";
        break;
    case Uniform::Type::Vec3:
        property = "vec3";
        break;
    case Uniform::Type::Vec4:
        property = "vec4";
        break;
    case Uniform::Type::Color:
        property = "color";
        break;
    case Uniform::Type::Sampler:
        property = "image";
        break;
    case Uniform::Type::Define:
        property = "define";
        break;
    }
    return property;
}

QVariant UniformModel::valueStringToVariant(const Uniform::Type type, const QString &value)
{
    QVariant variant;
    switch (type) {
    case UniformModel::Uniform::Type::Bool:
        variant = (value == "true") ? true : false;
        break;
    case UniformModel::Uniform::Type::Int:
    case UniformModel::Uniform::Type::Float:
        variant = value;
        break;
    case UniformModel::Uniform::Type::Vec2:
    {
        QStringList list = value.split(QLatin1Char(','));
        if (list.size() >= 2)
            variant = QVector2D(list.at(0).toDouble(), list.at(1).toDouble());
    }
        break;
    case UniformModel::Uniform::Type::Vec3:
    {
        QStringList list = value.split(QLatin1Char(','));
        if (list.size() >= 3)
            variant = QVector3D(list.at(0).toDouble(), list.at(1).toDouble(), list.at(2).toDouble());
    }
        break;
    case UniformModel::Uniform::Type::Vec4:
    {
        QStringList list = value.split(QLatin1Char(','));
        if (list.size() >= 4)
            variant = QVector4D(list.at(0).toDouble(), list.at(1).toDouble(), list.at(2).toDouble(), list.at(3).toDouble());
    }
        break;
    case UniformModel::Uniform::Type::Color:
    {
        QStringList list = value.split(QLatin1Char(','));
        if (list.size() >= 4)
            variant = QColor::fromRgbF(list.at(0).toDouble(), list.at(1).toDouble(), list.at(2).toDouble(), list.at(3).toDouble());
    }
        break;
    case UniformModel::Uniform::Type::Sampler:
        variant = value;
        break;
    case UniformModel::Uniform::Type::Define:
        variant = value;
        break;
    }

    return variant;
}

// Update canMoveUp and canMoveDown.
// When uniform is first/last of its node it can't move up/down.
void UniformModel::updateCanMoveStatus() {
    if (m_uniformTable->isEmpty())
            return;
    QList<int> usedIds;
    for (int i = 0; i < m_uniformTable->size() ; i++) {
        auto &uniform = (*m_uniformTable)[i];
        if (usedIds.contains(uniform.nodeId)) {
            uniform.canMoveUp = true;
        } else {
            uniform.canMoveUp = false;
            usedIds << uniform.nodeId;
        }
    }
    usedIds.clear();
    for (int i = m_uniformTable->size() - 1; i >= 0; i--) {
        auto &uniform = (*m_uniformTable)[i];
        if (usedIds.contains(uniform.nodeId)) {
            uniform.canMoveDown = true;
        } else {
            uniform.canMoveDown = false;
            usedIds << uniform.nodeId;
        }
    }
    int lastRowIndex = m_uniformTable->size();
    emit dataChanged(QAbstractItemModel::createIndex(0, 0),
                     QAbstractItemModel::createIndex(lastRowIndex, 0));
}

