module gui.manager;

import app;

import cimgui.cimgui;

import bubel.ecs.entity;
import bubel.ecs.manager;
import bubel.ecs.std;
import bubel.ecs.system;
import bubel.ecs.vector;

import ecs_utils.math.vector;

import gui.attributes;
import gui.component;
import gui.system;
import gui.template_;

import std.traits;

extern(C):

struct GUIManager
{
    Vector!SystemGUI systems;
    Vector!ComponentGUI components;
    Vector!TemplateGUI templates;
    Vector!ComponentEditGUI edit_components;

    int selected_template = 0;
    int selected_component = 0;

    void selectTemplate(int id)
    {
        if(templates.length == 0)return;
        selected_template = id;
        while(selected_template < 0)selected_template += cast(int)templates.length;
        while(selected_template >= templates.length)selected_template -= cast(int)templates.length;
    }

    void selectComponent(int id)
    {
        if(components.length == 0)return;
        selected_component = id;
        while(selected_component < 0)selected_component += cast(int)components.length;
        while(selected_component >= components.length)selected_component -= cast(int)components.length;
    }
    
    void clear()
    {
        foreach(tmpl; templates)
        {
            launcher.manager.freeTemplate(tmpl.tmpl);
        }
        foreach(comp; components)
        {
            free(comp.data);
        }
        foreach(ref comp; edit_components)
        {
            if(comp.variables)Mallocator.dispose(comp.variables);
            comp.variables = null;
            comp.used = 0;
        }

        systems.clear();
        templates.clear();
        components.clear();
        selected_template = 0;
        selected_component = 0;
    }

    EntityTemplate* getSelectedTemplate()
    {
        if(templates.length > selected_template)return templates[selected_template].tmpl;
        else return null;
    }

    ComponentRef getSelectedComponent()
    {
        if(components.length > selected_component)return ComponentRef(components[selected_component].data, components[selected_component].component_id);
        else return ComponentRef(null, ushort.max);
    }

    void addSystem(ushort id, const (char)* name, bool enabled = true)
    {
        System* system = launcher.manager.getSystem(id);
        //const (char)* name = 
        systems.add(SystemGUI(name,system,enabled));
    }

    void addTemplate(ushort[] components, const (char)* name)
    {
        templates.add(TemplateGUI(name, launcher.manager.allocateTemplate(components)));
    }

    void addTemplate(EntityTemplate* tmpl, const (char)* name)
    {
        templates.add(TemplateGUI(name, tmpl));
    }

    // void addComponent(ComponentRef comp, const (char)* name)
    // {
    //     uint size = EntityManager.instance.components[comp.component_id].size;
    //     void* data = malloc(size);
    //     memcpy(data, comp.ptr, size);
    //     components.add(ComponentGUI(name, data, comp.component_id));
    // }

    void addComponent(T)(T comp, const (char)* name)
    {
        static assert(hasStaticMember!(T,"component_id"));
        uint size = EntityManager.instance.components[comp.component_id].size;
        void* data = malloc(size);
        memcpy(data, &comp, size);
        components.add(ComponentGUI(name, data, comp.component_id));

        if(edit_components.length <= comp.component_id)
        {
            edit_components.length = comp.component_id+1;//.extend(comp.component_id + 1);
        }
        //edit_components[comp.component_id] = ComponentEditGUI(name);
        ComponentEditGUI comp_edit;
        comp_edit.name = T.stringof;
        //enum fields = __traits(allMembers, T);
        alias fields = FieldNameTuple!T;
        //pragma(msg,fields);
        comp_edit.variables = Mallocator.makeArray!VariableGUI(fields.length);
        foreach(member_str; fields)
        {
            alias member = __traits(getMember, T, member_str);
            alias member_type = typeof(member);
            //pragma(msg,member);
            //pragma(msg,member_type);
            //pragma(msg,__traits(getMember, T, member).offsetof);
            ushort offset = member.offsetof;//cast(ushort)__traits(getMember, T, member).offsetof;
            static if(__traits(isIntegral,member_type))
            {
                static if(__traits(isUnsigned, member_type))
                {
                    static if(hasUDA!(member,GUIColor))
                    {
                        comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.color,member_str,offset);
                    }
                    else switch(member_type.sizeof)
                    {
                        case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ubyte_,member_str,offset);break;
                        case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ushort_,member_str,offset);break;
                        case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.uint_,member_str,offset);break;
                        default:break;
                    }
                    static if(hasUDA!(member,GUIRange))
                    {
                        comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min;
                        comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[0].max;
                    }
                    else
                    {
                        comp_edit.variables[comp_edit.used-1].int_.min = 0;
                        comp_edit.variables[comp_edit.used-1].int_.max = int.max;
                    }
                }
                else
                {
                    switch(member_type.sizeof)
                    {
                        case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.byte_,member_str,offset);break;
                        case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.short_,member_str,offset);break;
                        case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.int_,member_str,offset);break;
                        default:break;
                    }
                    static if(hasUDA!(member,GUIRange))
                    {
                        comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min;
                        comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[1].max;
                    }
                    {
                        comp_edit.variables[comp_edit.used-1].int_.min = int.min;
                        comp_edit.variables[comp_edit.used-1].int_.max = int.max;
                    }
                }
            }
            else static if(__traits(isScalar,member_type))
            {
                switch(member_type.sizeof)
                {
                    case 4:comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.float_,member_str,offset);break;
                    case 8:
                    default:break;
                }
                static if(hasUDA!(member,GUIRange))
                {
                    comp_edit.variables[comp_edit.used-1].float_.min = getUDAs!(member,GUIRange)[0].minf;
                    comp_edit.variables[comp_edit.used-1].float_.max = getUDAs!(member,GUIRange)[1].maxf;
                }
                {
                    comp_edit.variables[comp_edit.used-1].float_.min = -float.max;
                    comp_edit.variables[comp_edit.used-1].float_.max = float.max;
                }
            }
        }
        edit_components[comp.component_id] = comp_edit;
    }

    void gui()
    {
        if(igCollapsingHeader("Systems", ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen))
        {
            bool true_ = true;
            igIndent(8);
            foreach(ref SystemGUI system;systems)
            {
                if(igCheckbox(system.name,&system.enabled))
                {
                    if(system.enabled)system.system.enable();
                    else system.system.disable();
                }
            }
            igUnindent(8);
        }
    }

    static vec4 colorUintToVec4(uint color)
    {
       // color = *cast(uint*)(comp_ptr+var.offset);
        return vec4(cast(float)(color & 0xFF) / 255,
            cast(float)(color >> 8 & 0xFF) / 255,
            cast(float)(color >> 16 & 0xFF) / 255,
            cast(float)(color >> 24 & 0xFF) / 255);
    }

    static uint colorVec4ToUint(vec4 color)
    {
        return cast(uint)(color.x * 255) |
            cast(uint)(color.y * 255) << 8 |
            cast(uint)(color.z * 255) << 16 |
            cast(uint)(color.w * 255) << 24;
    }

    static bool igDragScalarClamp(const(char)* label, ImGuiDataType data_type, void* v, float v_speed, const(void)* v_min, const(void)* v_max, const(char)* format, float power)
    {
        ubyte[8] v_backup;// = *v;
        final switch(data_type)
        {
            case ImGuiDataType_S8:memcpy(v_backup.ptr, v, 1);break;
            case ImGuiDataType_S16:memcpy(v_backup.ptr, v, 2);break;
            case ImGuiDataType_S32:memcpy(v_backup.ptr, v, 4);break;
            case ImGuiDataType_U8:memcpy(v_backup.ptr, v, 1);break;
            case ImGuiDataType_U16:memcpy(v_backup.ptr, v, 2);break;
            case ImGuiDataType_U32:memcpy(v_backup.ptr, v, 4);break;
            case ImGuiDataType_Float:memcpy(v_backup.ptr, v, 4);break;
        }
        if (!igDragScalar(label, data_type, v, v_speed, v_min, v_max, format, power))
            return false;

        final switch(data_type)
        {
            case ImGuiDataType_S8:
                if(*cast(byte*)v < *cast(byte*)v_min)*cast(byte*)v = *cast(byte*)v_min;
                else if(*cast(byte*)v > *cast(byte*)v_max)*cast(byte*)v = *cast(byte*)v_max;
                return *cast(byte*)v != *cast(byte*)v_backup.ptr;
            case ImGuiDataType_S16:
                if(*cast(short*)v < *cast(short*)v_min)*cast(short*)v = *cast(short*)v_min;
                else if(*cast(short*)v > *cast(short*)v_max)*cast(short*)v = *cast(short*)v_max;
                return *cast(short*)v != *cast(short*)v_backup.ptr;
            case ImGuiDataType_S32:
                if(*cast(int*)v < *cast(int*)v_min)*cast(int*)v = *cast(int*)v_min;
                else if(*cast(int*)v > *cast(int*)v_max)*cast(int*)v = *cast(int*)v_max;
                return *cast(int*)v != *cast(int*)v_backup.ptr;
            case ImGuiDataType_U8:
                if(*cast(ubyte*)v < *cast(ubyte*)v_min)*cast(ubyte*)v = *cast(ubyte*)v_min;
                else if(*cast(ubyte*)v > *cast(ubyte*)v_max)*cast(ubyte*)v = *cast(ubyte*)v_max;
                return *cast(ubyte*)v != *cast(ubyte*)v_backup.ptr;
            case ImGuiDataType_U16:
                if(*cast(ushort*)v < *cast(ushort*)v_min)*cast(ushort*)v = *cast(ushort*)v_min;
                else if(*cast(ushort*)v > *cast(ushort*)v_max)*cast(ushort*)v = *cast(ushort*)v_max;
                return *cast(ushort*)v != *cast(ushort*)v_backup.ptr;
            case ImGuiDataType_U32:
                if(*cast(uint*)v < *cast(uint*)v_min)*cast(uint*)v = *cast(uint*)v_min;
                else if(*cast(uint*)v > *cast(uint*)v_max)*cast(uint*)v = *cast(uint*)v_max;
                return *cast(uint*)v != *cast(uint*)v_backup.ptr;
            case ImGuiDataType_Float:
                if(*cast(float*)v < *cast(float*)v_min)*cast(float*)v = *cast(float*)v_min;
                else if(*cast(float*)v > *cast(float*)v_max)*cast(float*)v = *cast(float*)v_max;
                return *cast(float*)v != *cast(float*)v_backup.ptr;
        }
    }

    void componentGUI(ushort comp_id, void* data_ptr)
    {
        vec4 color;
        if(comp_id >= edit_components.length)return;
        if(edit_components[comp_id].used)
        {
            if(igCollapsingHeader(edit_components[comp_id].name, ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DefaultOpen))
            {
                igIndent(8);
                foreach(ref VariableGUI var;edit_components[comp_id].variables[0 .. edit_components[comp_id].used])
                {
                    igPushIDPtr(&var);
                    switch(var.type)
                    {
                        case VariableGUI.Type.byte_:
                            igDragScalarClamp(var.name, ImGuiDataType_S8, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
                            break;
                        case VariableGUI.Type.ubyte_:
                            igDragScalarClamp(var.name, ImGuiDataType_U8, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
                            break;
                        case VariableGUI.Type.short_:
                            igDragScalarClamp(var.name, ImGuiDataType_S16, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
                            break;
                        case VariableGUI.Type.ushort_:
                            igDragScalarClamp(var.name, ImGuiDataType_U16, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
                            break;
                        case VariableGUI.Type.int_:
                            igDragScalarClamp(var.name, ImGuiDataType_S32, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
                            break;
                        case VariableGUI.Type.uint_:
                            igDragScalarClamp(var.name, ImGuiDataType_U32, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
                            break;
                        case VariableGUI.Type.float_:
                            igDragScalarClamp(var.name, ImGuiDataType_Float, data_ptr+var.offset, 0.1, cast(void*)&var.float_.min, cast(void*)&var.float_.max, "%2.2f", 1);
                            break;
                        case VariableGUI.Type.color:
                            color = colorUintToVec4(*cast(uint*)(data_ptr+var.offset));
                            if(igColorEdit4(var.name, color.data, ImGuiColorEditFlags_None))
                                *cast(uint*)(data_ptr+var.offset) = colorVec4ToUint(color);
                            break;
                        default:break;
                    }
                    igPopID();
                }
                //igPopID();
                igUnindent(8);
            }
        }
    }

    void entityComponentsGUI()
    {
        EntityTemplate* tmpl = templates[selected_template].tmpl;
        EntityManager.EntityInfo* info = tmpl.info;
        foreach(comp_id; info.components)
        {
            void* data_ptr = tmpl.entity_data.ptr;
            void* comp_ptr = data_ptr + info.tmpl_deltas[comp_id];
            componentGUI(comp_id, comp_ptr);
        }
    }

    void toolGui()
    {
        ImGuiStyle * style = igGetStyle();
        ImVec4 col = style.Colors[ImGuiCol_Header];
        style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg];
        //style.
        //ImDrawList* draw_list = igGetWindowDrawList();
        final switch(launcher.used_tool)
        {
            case Tool.entity_spawner:
                if(templates.length)
                {
                    {
                        if(igListBoxHeaderInt("Template",cast(int)templates.length,cast(int)templates.length))
                        {
                            foreach(i, tmpl; templates)
                            {
                                if(igSelectable(tmpl.name,selected_template == i,ImGuiSelectableFlags_AllowDoubleClick,ImVec2(0,0)))
                                {
                                    selected_template = cast(uint)i;
                                }
                            }
                            igListBoxFooter();
                        }
                    }
                    if(igIsItemHovered(0))igSetTooltip("Select entity to spawn (SHIFT + Scroll)");
                }
                style.Colors[ImGuiCol_Header] = col;
                entityComponentsGUI();
                break;
            case Tool.component_manipulator:
                if(components.length)
                {
                    if(igListBoxHeaderInt("Components",cast(int)components.length,cast(int)components.length))
                    {
                        {
                            foreach(i, comp; components)
                            {
                                if(igSelectable(comp.name,selected_component == i,0,ImVec2(0,0)))
                                {
                                    selected_component = cast(uint)i;
                                }
                            }
                            igListBoxFooter();
                        }
                    }
                    if(igIsItemHovered(0))igSetTooltip("Select component to add/remove (SHIFT + Scroll)");
                }
                style.Colors[ImGuiCol_Header] = col;
                componentGUI(components[selected_component].component_id, components[selected_component].data);
                break;
            case Tool.selector:
                break;
        }
        
        style.Colors[ImGuiCol_Header] = col;
    }

    void filterGUI()
    {
        ImGuiStyle * style = igGetStyle();
        ImVec4 col = style.Colors[ImGuiCol_Header];
        style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg];

        int length = 0;
        foreach(comp; edit_components)
        {
            if(comp.name !is null)length++;
        }

        if(length && igListBoxHeaderInt("Components",cast(int)length,cast(int)length))
        {
            foreach(i, comp; edit_components)
            {
                if(comp.name is null)return;
                if(igSelectable(comp.name,false,0,ImVec2(0,0)))
                {
                    
                }
            }
            igListBoxFooter();
        }
        if(igIsItemHovered(0))igSetTooltip("Select components to filter while tool work.\nComponents are only changed for filtered entities.");
        
        style.Colors[ImGuiCol_Header] = col;
    }
}