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

#ifndef EFFECTMANAGER_H
#define EFFECTMANAGER_H

#include <QObject>
#include <QTemporaryFile>
#include <QTimer>
#include <QQmlPropertyMap>
#include <QFileSystemWatcher>
#include "uniformmodel.h"
#include "nodeview.h"
#include "shaderfeatures.h"
#include <QtShaderTools/private/qshaderbaker_p.h>
#include <QtQuick/private/qquicktextedit_p_p.h>
#include "addnodemodel.h"
#include "applicationsettings.h"
#include "codehelper.h"

// The delay in ms to wait until updating the effect
const int EFFECT_UPDATE_DELAY = 200;

// This will be used for commandline arguments.
extern QQmlPropertyMap g_argData;

struct EffectError {
    Q_GADGET
    Q_PROPERTY(QString message MEMBER m_message)
    Q_PROPERTY(int line MEMBER m_line)
    Q_PROPERTY(int type MEMBER m_type)
public:
    QString m_message;
    int m_line = -1;
    int m_type = -1;
};

class EffectManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(UniformModel *uniformModel READ uniformModel WRITE setUniformModel NOTIFY uniformModelChanged)
    Q_PROPERTY(NodeView *nodeView READ nodeView WRITE setNodeView NOTIFY nodeViewChanged)
    Q_PROPERTY(CodeHelper *codeHelper READ codeHelper CONSTANT)
    Q_PROPERTY(QString fragmentShader READ fragmentShader WRITE setFragmentShader NOTIFY fragmentShaderChanged)
    Q_PROPERTY(QString vertexShader READ vertexShader WRITE setVertexShader NOTIFY vertexShaderChanged)
    Q_PROPERTY(QString fragmentShaderFilename READ fragmentShaderFilename WRITE setFragmentShaderFilename NOTIFY fragmentShaderFilenameChanged)
    Q_PROPERTY(QString vertexShaderFilename READ vertexShaderFilename WRITE setVertexShaderFilename NOTIFY vertexShaderFilenameChanged)
    Q_PROPERTY(QString qmlComponentString READ qmlComponentString WRITE setQmlComponentString NOTIFY qmlComponentStringChanged)

    Q_PROPERTY(EffectError effectError READ effectError NOTIFY effectErrorChanged)
    Q_PROPERTY(bool unsavedChanges READ unsavedChanges WRITE setUnsavedChanges NOTIFY unsavedChangesChanged)
    Q_PROPERTY(bool hasProjectFilename READ hasProjectFilename NOTIFY hasProjectFilenameChanged)
    Q_PROPERTY(QString projectName READ projectName NOTIFY projectNameChanged)
    Q_PROPERTY(QString projectFilename READ projectFilename NOTIFY projectFilenameChanged)
    Q_PROPERTY(QString projectDirectory READ projectDirectory NOTIFY projectDirectoryChanged)
    Q_PROPERTY(QString exportFilename READ exportFilename NOTIFY exportFilenameChanged)
    Q_PROPERTY(QString exportDirectory READ exportDirectory NOTIFY exportDirectoryChanged)
    Q_PROPERTY(int exportFlags READ exportFlags NOTIFY exportFlagsChanged)
    Q_PROPERTY(bool shadersUpToDate READ shadersUpToDate WRITE setShadersUpToDate NOTIFY shadersUpToDateChanged)
    Q_PROPERTY(bool autoPlayEffect READ autoPlayEffect WRITE setAutoPlayEffect NOTIFY autoPlayEffectChanged)
    Q_PROPERTY(AddNodeModel *addNodeModel READ addNodeModel NOTIFY addNodeModelChanged)
    Q_PROPERTY(QRect effectPadding READ effectPadding WRITE setEffectPadding NOTIFY effectPaddingChanged)
    Q_PROPERTY(QString effectHeadings READ effectHeadings WRITE setEffectHeadings NOTIFY effectHeadingsChanged)
    Q_PROPERTY(ApplicationSettings * settings READ settings NOTIFY settingsChanged)
    QML_ELEMENT

public:
    explicit EffectManager(QObject *parent = nullptr);

    UniformModel *uniformModel() const;
    void setUniformModel(UniformModel *newUniformModel);

    CodeHelper *codeHelper() const;

    QString fragmentShader() const;
    void setFragmentShader(const QString &newFragmentShader);

    QString vertexShader() const;
    void setVertexShader(const QString &newVertexShader);

    bool unsavedChanges() const;
    void setUnsavedChanges(bool newUnsavedChanges);

    bool hasProjectFilename() const;
    QString projectFilename() const;
    QString projectName() const;
    void setProjectName(const QString &name);
    QString projectDirectory() const;
    QString exportFilename() const;
    QString exportDirectory() const;
    int exportFlags() const;

    NodeView *nodeView() const;
    void setNodeView(NodeView *newNodeView);

    EffectError effectError() const;

    const QString &fragmentShaderFilename() const;
    void setFragmentShaderFilename(const QString &newFragmentShaderFilename);

    const QString &vertexShaderFilename() const;
    void setVertexShaderFilename(const QString &newVertexShaderFilename);

    const QString &qmlComponentString() const;
    void setQmlComponentString(const QString &string);

    bool shadersUpToDate() const;
    void setShadersUpToDate(bool upToDate);

    bool autoPlayEffect() const;
    void setAutoPlayEffect(bool autoPlay);

    AddNodeModel *addNodeModel() const;

    const QRect &effectPadding() const;
    QString effectHeadings() const;

    ApplicationSettings *settings() const {
        return m_settings;
    }

    Q_INVOKABLE QString mipmapPropertyName(const QString &name) const;
    Q_INVOKABLE QString getSupportedImageFormatsFilter() const;

    Q_INVOKABLE int effectUpdateDelay() const {
        return EFFECT_UPDATE_DELAY;
    }

public Q_SLOTS:
    QString generateVertexShader(bool includeUniforms = true);
    QString generateFragmentShader(bool includeUniforms = true);
    void updateQmlComponent();
    void initialize();
    NodesModel::Node loadEffectNode(const QString &filename);
    bool addEffectNode(const QString &filename, int startNodeId = -1, int endNodeId = -1);
    bool deleteEffectNodes(QList<int> nodeIds);
    bool loadProject(const QUrl &filename);
    bool saveProject(const QUrl &filename = QString());
    bool newProject(const QString &filepath, const QString &filename, bool clearNodeView, bool createProjectDir = false);
    void closeProject();
    bool exportEffect(const QString &dirPath, const QString &filename, int exportFlags, int qsbVersionIndex);
    void cleanupProject();
    void cleanupNodeView(bool initialize = true);
    bool saveSelectedNode(const QUrl &filename);
    void updateBakedShaderVersions();
    void bakeShaders(bool forced = false);

    QString getHelpTextString();
    void updateAddNodeData();
    void showHideAddNodeGroup(const QString &groupName, bool show);
    void refreshAddNodesList();
    void setEffectPadding(const QRect &newEffectPadding);
    void setEffectHeadings(const QString &newEffectHeadings);
    QString stripFileFromURL(const QString &urlString) const;
    QString addFileToURL(const QString &urlString) const;
    QString getDirectory(const QString &path, bool useFileScheme = true) const;
    QString getDefaultImagesDirectory(bool useFileScheme = true) const;
    void autoIndentCurrentCode(int codeTab, const QString &code);
    bool processKey(int codeTab, int keyCode, int modifiers, QQuickTextEdit *textEdit);
    void setEffectError(const QString &errorMessage, int type = -1, int lineNumber = -1);
    void resetEffectError(int type = -1);

signals:
    void uniformModelChanged();
    void fragmentShaderChanged();
    void vertexShaderChanged();
    void unsavedChangesChanged();
    void hasProjectFilenameChanged();
    void projectFilenameChanged();
    void projectNameChanged();
    void projectDirectoryChanged();
    void exportFilenameChanged();
    void exportDirectoryChanged();
    void exportFlagsChanged();
    void shadersBaked();

    void nodeViewChanged();
    void effectErrorChanged();

    void fragmentShaderFilenameChanged();
    void vertexShaderFilenameChanged();
    void qmlComponentStringChanged();
    void shadersUpToDateChanged();
    void autoPlayEffectChanged();
    void addNodeModelChanged();

    void effectPaddingChanged();
    void effectHeadingsChanged();
    void settingsChanged();

private:
    friend class AddNodeModel;
    friend class ApplicationSettings;

    enum ExportFlags {
        QMLComponent = 1,
        QSBShaders = 2,
        TextShaders = 4,
        Images = 8,
        QRCFile = 16
    };

    enum ErrorTypes {
        ErrorCommon = -1,
        ErrorQMLParsing,
        ErrorVert,
        ErrorFrag,
        ErrorQMLRuntime,
        ErrorPreprocessor
    };

    const QString getBufUniform();
    const QString getFSUniforms();
    const QString getVSUniforms();
    const QString getDefineProperties();
    const QString getConstVariables();
    void updateCustomUniforms();
    QString getQmlImagesString(bool localFiles);
    QString getQmlComponentString(bool localFiles);
    QString getQmlEffectString();
    QStringList getDefaultRootVertexShader();
    QStringList getDefaultRootFragmentShader();
    QString processVertexRootLine(const QString &line);
    QString processFragmentRootLine(const QString &line);
    QString getCustomShaderVaryings(bool outState);
    QStringList removeTagsFromCode(const QStringList &codeLines);
    QString removeTagsFromCode(const QString &code);
    QString replaceOldTagsWithNew(const QString &code);
    QString detectErrorMessage(const QString &errorMessage);

    void doBakeShaders();
    QFile resolveFileFromUrl(const QUrl &fileUrl);
    bool createNodeFromJson(const QJsonObject &rootJson, NodesModel::Node &node, bool fullNode, const QString &nodePath);
    bool addNodeIntoView(NodesModel::Node &node, int startNodeId = -1, int endNodeId = -1);
    bool addNodeConnection(int startNodeId, int endNodeId);
    int getTagIndex(const QStringList &code, const QString &tag);
    QJsonObject nodeToJson(const NodesModel::Node &node, bool simplified, const QString &nodePath);
    QString codeFromJsonArray(const QJsonArray &codeArray);
    QString absoluteToRelativePath(const QString &path, const QString &toPath = QString());
    QString relativeToAbsolutePath(const QString &path, const QString &toPath = QString());
    void updateImageWatchers();
    void clearImageWatchers();

    UniformModel *m_uniformModel = nullptr;
    UniformModel::UniformTable m_uniformTable;
    CodeHelper *m_codeHelper = nullptr;
    QString m_fragmentShader;
    QString m_vertexShader;
    QString m_qmlCode;
    // Used in exported QML, at root of the file
    QString m_exportedRootPropertiesString;
    // Used in exported QML, at ShaderEffect component of the file
    QString m_exportedEffectPropertiesString;
    // Used in preview QML, at ShaderEffect component of the file
    QString m_previewEffectPropertiesString;
    bool m_unsavedChanges = false;
    bool m_firstBake = true;
    // Full path of the project file
    QUrl m_projectFilename;
    // Path to where project file is located
    QString m_projectDirectory;
    // Exported effect name, without the prefixes
    QString m_exportFilename;
    // Path to where effect is exported
    QString m_exportDirectory;
    // Name of the project
    // E.g. "MyEffect"
    QString m_projectName;
    int m_exportFlags = QMLComponent | QSBShaders | Images;

    QTemporaryFile m_fragmentShaderFile;
    QTemporaryFile m_vertexShaderFile;

    ApplicationSettings *m_settings = nullptr;
    QShaderBaker m_baker;
    NodeView *m_nodeView = nullptr;
    QMap<int, EffectError> m_effectErrors;
    QString m_fragmentShaderFilename;
    QString m_vertexShaderFilename;
    QString m_qmlComponentString;
    QStringList m_defaultRootVertexShader;
    QStringList m_defaultRootFragmentShader;
    QTimer m_shaderBakerTimer;
    // True when shaders haven't changed since last baking
    bool m_shadersUpToDate = true;
    // When true, shaders are baked automatically after a
    // short delay when they change.
    bool m_autoPlayEffect = true;
    bool m_loadComponentImages = true;

    ShaderFeatures m_shaderFeatures;
    AddNodeModel *m_addNodeModel = nullptr;
    QStringList m_shaderVaryingVariables;
    QRect m_effectPadding;
    QString m_effectHeadings;
    QFileSystemWatcher m_fileWatcher;
};

#endif // EFFECTMANAGER_H
