/*
 * Copyright 2023-2025 Hartmut Seichter and Contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#ifndef PARADISO_SHADER_HPP
#define PARADISO_SHADER_HPP

#include <paradiso/globals.hpp>
#include <paradiso/matrix.hpp>
#include <paradiso/vector.hpp>

#include <memory>
#include <unordered_map>
#include <variant>
#include <vector>

namespace paradiso {

struct Shader final {
    Shader();
    ~Shader();
    Shader(const Shader&) = delete;
    Shader(Shader&&) = default;

    enum class Type { Vertex, Fragment, Geometry, Compute };

    enum class Preset { Sprite };

    void set_source(Type t, const std::string& c) { source_[t] = c; }
    std::string source(Type t) const { return source_.at(t); }

    bool load_preset(Preset preset);

    bool build();

    bool ready() const;

    void use() const;

    const Shader&
    set_uniform_at_location(int location,
                            float v) const; //!< sets a float in a shader

    const Shader& set_uniform_at_location(
        int location,
        uint32_t v) const; //!< sets a 32bit unsigned in a shader

    const Shader& set_uniform_at_location(
        int location,
        int32_t v) const; //!< sets a 32bit signed in a shader

    const Shader& set_uniform_at_location(
        int location,
        const Vector2<float>& v) const; //!< sets a 2D float vector
    /**
     * @brief retrieves the position of a uniform
     * @param name of the uniform
     * @return position of the uniform or negative if it doesn't exist
     */
    int uniform_location(std::string const& name) const;

    /**
     * @brief check if a uniform with the given name exists
     * @param name of the uniform
     * @return true if found
     */
    bool has_uniform(std::string const& name) const {
        return uniform_location(name) >= 0;
    }

    /**
     * sets data of the uniform inside a shader program
     */
    template <typename T>
    const Shader& set_uniform(std::string const& name, T&& value) const {
        return set_uniform_at_location(uniform_location(name),
                                       std::forward<T>(value));
    }

    using uniform_t =
        std::variant<bool, int, float, double,
                     Vector2<float> /*vector3f,vector4f,matrix4x4f*/>;
    using uniform_entry_t = std::tuple<std::string, uniform_t, int>;

    using uniform_cache_t = std::vector<uniform_entry_t>;

    void set_uniforms(uniform_cache_t c);

    uint32_t native_handle() const;

  private:
    std::unordered_map<Type, std::string> source_;

    struct impl;
    std::unique_ptr<impl> impl_;
};

} // namespace paradiso

#endif
