#ifndef SHADER_H
#define SHADER_H

#include "math/Vector.h"
#include "utils/Array.h"
#include "utils/Error.h"
#include "utils/List.h"
#include "wrapper/GL.h"

class Shader final {
    static constexpr int MAX_SHADERS = 5;
    Array<GL::Shader, MAX_SHADERS> shaders;
    GL::Program program;

public:
    Shader();
    ~Shader();
    Shader(const Shader& other) = delete;
    Shader(Shader&& other) = delete;
    Shader& operator=(const Shader& other) = delete;
    Shader& operator=(Shader&& other) = delete;

    template<typename... Args>
    Error compile(Args&&... args) {
        const int size = sizeof...(args);
        const char* paths[size] = {args...};
        static_assert(size <= MAX_SHADERS, "too much shaders paths given");

        for(int i = 0; i < size; i++) {
            GL::ShaderType type = getShaderType(paths[i]);
            if(type == GL::NO_SHADER) {
                Error error = {"'"};
                error.message.append(paths[i]).append(
                    "' is not a valid shader type");
                return error;
            }
            Error error = readAndCompile(paths[i], shaders[i], type);
            if(error.has()) {
                return error;
            }
        }

        program = GL::createProgram();
        for(GL::Shader shader : shaders) {
            if(shader != 0) {
                GL::attachShader(program, shader);
            }
        }
        GL::linkProgram(program);
        Error error = GL::getError("cannot link");
        if(error.has()) {
            return error;
        }
        return GL::getLinkerError(program);
    }

    void use() const;
    void setMatrix(const char* name, const float* data);
    void setInt(const char* name, int data);
    void setFloat(const char* name, float data);

    void setVector(const char* name, const Vector2& v);
    void setVector(const char* name, const Vector3& v);
    void setVector(const char* name, const Vector4& v);

private:
    GL::ShaderType getShaderType(const char* path) const;
    Error readAndCompile(const char* path, GL::Shader& s, GL::ShaderType st);
    Error readFile(List<char>& code, const char* path) const;
    Error compileType(GL::Shader& s, const List<char>& code, GL::ShaderType st);
};

#endif