#ifndef OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H
#define OPENMW_COMPONENTS_SETTINGS_SHADERMANAGER_H

#include <filesystem>
#include <fstream>
#include <optional>
#include <unordered_map>
#include <vector>

#include <yaml-cpp/yaml.h>

#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>

#include <components/debug/debuglog.hpp>
#include <components/files/conversion.hpp>
#include <components/serialization/osgyaml.hpp>

namespace Settings
{
    /*
     * Manages the shader.yaml file which is auto-generated and lives next to settings.cfg.
     * This YAML file is simply a mapping of technique name to a list of uniforms and their values.
     * Currently only vec2f, vec3f, vec4f, int, and float uniforms are supported.
     *
     * config:
     *   TECHNIQUE:
     *    MY_FLOAT: 10.34
     *    MY_VEC2: [0.23, 0.34]
     *   TECHNIQUE2:
     *    MY_VEC3: [0.22, 0.33, 0.20]
     */
    class ShaderManager
    {
    public:
        enum class Mode
        {
            Normal,
            Debug
        };

        ShaderManager() = default;
        ShaderManager(ShaderManager const&) = delete;
        void operator=(ShaderManager const&) = delete;

        static ShaderManager& get()
        {
            static ShaderManager instance;
            return instance;
        }

        Mode getMode() { return mMode; }

        void setMode(Mode mode) { mMode = mode; }

        const YAML::Node& getRoot() { return mData; }

        template <class T>
        bool setValue(const std::string& tname, const std::string& uname, const T& value)
        {
            if (mData.IsNull())
            {
                Log(Debug::Warning) << "Failed setting " << tname << ", " << uname
                                    << " : shader settings failed to load";
                return false;
            }

            mData["config"][tname][uname] = value;
            return true;
        }

        template <class T>
        std::optional<T> getValue(const std::string& tname, const std::string& uname)
        {
            if (mData.IsNull())
            {
                Log(Debug::Warning) << "Failed getting " << tname << ", " << uname
                                    << " : shader settings failed to load";
                return std::nullopt;
            }

            try
            {
                auto value = mData["config"][tname][uname];

                if (!value)
                    return std::nullopt;

                return value.as<T>();
            }
            catch (const YAML::BadConversion&)
            {
                Log(Debug::Warning) << "Failed retrieving " << tname << ", " << uname
                                    << " : mismatched types in config file.";
            }

            return std::nullopt;
        }

        bool load(const std::filesystem::path& path)
        {
            mData = YAML::Null;
            mPath = path;

            Log(Debug::Info) << "Loading shader settings file: " << mPath;

            if (!std::filesystem::exists(mPath))
            {
                std::ofstream fout(mPath);
                if (!fout)
                {
                    Log(Debug::Error) << "Failed creating shader settings file: " << mPath;
                    return false;
                }
            }

            try
            {
                std::ifstream file{ mPath };
                mData = YAML::Load(file);
                mData.SetStyle(YAML::EmitterStyle::Block);

                if (!mData["config"])
                    mData["config"] = YAML::Node();

                return true;
            }
            catch (const YAML::Exception& e)
            {
                Log(Debug::Error) << "Shader settings failed to load, " << e.msg;
            }

            return false;
        }

        bool save()
        {
            if (mData.IsNull())
            {
                Log(Debug::Error) << "Shader settings failed to load, settings will not be saved: " << mPath;
                return false;
            }

            Log(Debug::Info) << "Saving shader settings file: " << mPath;

            YAML::Emitter out;
            out.SetMapFormat(YAML::Block);
            out << mData;

            std::ofstream fout(mPath);
            fout << out.c_str();

            if (!fout)
            {
                Log(Debug::Error) << "Failed saving shader settings file: " << mPath;
                return false;
            }

            return true;
        }

    private:
        std::filesystem::path mPath;
        YAML::Node mData;
        Mode mMode = Mode::Normal;
    };
}

#endif
