module ecs_utils.gfx.material;

import bindbc.sdl;

import ecs.std;

import ecs_utils.gfx.shader;

import glad.gl.gl;

//import mutils.serializer.json;

struct Material
{

    void create() nothrow
    {
        data = Mallocator.make!Data;
    }

    bool load(const char[] path) nothrow
    {
        struct LoadData
        {
            @("malloc") string blend_mode;
            @("malloc") string vertex;
            @("malloc") string fragment;

            void dispose() nothrow
            {
                //if(blend_mode)Mallocator.instance.dispose(cast(char[])blend_mode);
                //if(vertex)Mallocator.instance.dispose(cast(char[])vertex);
                //if(fragment)Mallocator.instance.dispose(cast(char[])fragment);
            }
        }

        char[] cpath = (cast(char*)alloca(path.length+1))[0..path.length+1];
        cpath[0..$-1] = path[0..$];
        cpath[$-1] = 0;

        SDL_RWops* file = SDL_RWFromFile(cpath.ptr,"r");
        if(file)
        {
            size_t size = cast(size_t)SDL_RWsize(file);
            char[] buffer = Mallocator.makeArray!char(size);
            SDL_RWread(file,buffer.ptr,size,1);

            LoadData load_data;
            scope(exit)load_data.dispose();

            /*JSONSerializer serializer = Mallocator.make!JSONSerializer;
            scope(exit)Mallocator.dispose(serializer);
            serializer.serialize!(Load.yes, true)(load_data,buffer);*/

            //if(__ecs_used_backend == Backend.opengl)
            {
                Shader vsh;
                vsh.load(load_data.vertex);
                vsh.compile();

                Shader fsh;
                fsh.load(load_data.fragment);
                fsh.compile();

                Material.ShaderModule[1] modules = [Material.ShaderModule(vsh,fsh)];

                attachModules(modules);
            }

            SDL_RWclose(file);
            load_data.dispose();
            return true;
        }
        else return false;
    }

    void bind() nothrow
    {
        glUseProgram(data.modules[0].gl_handle);
    }
    
    enum BlendMode
    {
        opaque,
        additive,
        mixed
    }

    enum TransformMode
    {
        position,
        matrix
    }
    
    struct ShaderModule
    {
        Shader fragment_shader;
        Shader vertex_shader;
        uint gl_handle;
    }

    void attachModules(scope ShaderModule[] modules) nothrow
    {
        data.modules = Mallocator.makeArray(modules);
    }

    bool compile() nothrow
    {
        foreach(ref module_;data.modules)
        {
            module_.gl_handle = glCreateProgram();
            glAttachShader(module_.gl_handle, module_.vertex_shader.data.gl_handle);
            glAttachShader(module_.gl_handle, module_.fragment_shader.data.gl_handle);
        }

        return true;
    }

    void bindAttribLocation(const char* name, uint location) nothrow
    {
        foreach(ref module_;data.modules)
        {
            glBindAttribLocation(module_.gl_handle, location, name);
        }
    }

    bool link() nothrow
    {
        foreach(ref module_;data.modules)
        {
            glLinkProgram(module_.gl_handle);

            GLint ok = 0;
            glGetProgramiv(module_.gl_handle, GL_LINK_STATUS, &ok);
            if(!ok)
            {
                SDL_Log("Program link error!");
                return false;
            }
        }

        return true;
    }

    int getLocation(const char* name)
    {
        foreach(ref module_;data.modules)
        {
            int location = glGetUniformLocation(module_.gl_handle,name);
            if(location != -1)return location;
        }
        return -1;
    }

    void pushBindings()
    {
        foreach(i;0..data.bindings.length)
        {
            glUniform1i(data.bindings[i],cast(int)i);
        }
    }

    void pushUniforms(void* ptr)
    {
        foreach(ref Uniform uniform; data.uniforms)
        {
            void* local_ptr = ptr + uniform.offset;
            glUniform4fv(uniform.location,1,cast(float*)local_ptr);
        }
    }

    enum Type 
    {
        float_,
        float4
    }

    struct Uniform 
    {
        Type type;
        int location;
        uint offset;
    }

    struct Data
    {
        BlendMode blend_mode = BlendMode.opaque;
    
        ShaderModule[] modules;

        TransformMode mode;

        Uniform[] uniforms;
        int[] bindings;
    }

    Data* data;
}