module demos.particles;

import app;

import bindbc.sdl;

import cimgui.cimgui;

import bubel.ecs.attributes;
import bubel.ecs.core;
import bubel.ecs.entity;
import bubel.ecs.manager;
import bubel.ecs.std;

import ecs_utils.gfx.texture;
import ecs_utils.math.vector;
import ecs_utils.utils;

import game_core.basic;
import game_core.rendering;

import gui.attributes;

extern(C):

private enum float px = 1.0/512.0;

/*#######################################################################################################################
------------------------------------------------ Components ------------------------------------------------------------------
#######################################################################################################################*/

/*struct CLocation
{
    mixin ECS.Component;

    alias location this;

    vec2 location;
}

struct CColor
{
    mixin ECS.Component;

    alias value this;

    @GUIColor uint value = uint.max;
}

struct CTexCoords
{
    mixin ECS.Component;

    vec4 value;
}*/

// struct CVelocity
// {
//     mixin ECS.Component;

//     alias value this;

//     vec2 value = vec2(0);
// }

struct CForceRange
{
    mixin ECS.Component;

    vec2 range = vec2(20,200);
}

struct CAttractor
{
    mixin ECS.Component;

    //alias value this;
    float strength = 0.2;
}

struct CVortex
{
    mixin ECS.Component;

    float strength = 0.6;
}

// struct CDamping
// {
//     mixin ECS.Component;

//     alias power this;

//     @GUIRange(0,9) ubyte power = 0;
// }

struct CGravity
{
    mixin ECS.Component;
}

struct CParticleLife
{
    mixin ECS.Component;

    this(float life_in_secs)
    {
        life = cast(int)(life_in_secs * 1000_000);
    }

    alias life this;

    int life = 1000000;
}

/*#######################################################################################################################
------------------------------------------------ Systems ------------------------------------------------------------------
#######################################################################################################################*/
/*
struct DrawSystem
{
    mixin ECS.System!32;

    struct EntitiesData
    {
        uint length;
        //uint thread_id;
        uint job_id;
        //@readonly CTexCoords[] coords;
        @readonly CLocation[] locations;

        @optional @readonly CColor[] color;
    }

    void onUpdate(EntitiesData data)
    {
        if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached
        import ecs_utils.gfx.renderer;
        Renderer.DrawData draw_data;
        draw_data.size = vec2(2,2);
        draw_data.coords = vec4(246,64,2,2)*px;
        draw_data.color = 0x80808080;
        draw_data.material_id = 2;
        draw_data.thread_id = data.job_id;
        draw_data.texture = particles_demo.texture;
        
        if(!data.color)
        {
            foreach(i; 0..data.length)
            {
                draw_data.position = data.locations[i];
                launcher.renderer.draw(draw_data);//particles_demo.texture, data.locations[i], vec2(2,2), vec4(246,64,2,2)*px, 0, 0x80808080, 0, 2, 0, data.job_id);
            }
        }
        else 
        {
            foreach(i; 0..data.length)
            {
                draw_data.position = data.locations[i];
                draw_data.color = data.color[i].value;
                launcher.renderer.draw(draw_data);//particles_demo.texture, data.locations[i], vec2(2,2), vec4(246,64,2,2)*px, 0, data.color[i].value, 0, 2, 0, data.job_id);
            }
        }
        
    }
}*/

// struct MoveSystem
// {
//     mixin ECS.System!64;

//     struct EntitiesData
//     {
//         uint length;
//         CLocation[] locations;
//         @readonly CVelocity[] velocity;
//     }

//     void onUpdate(EntitiesData data)
//     {
//         foreach(i; 0..data.length)
//         {
//             data.locations[i] += data.velocity[i] * launcher.delta_time;
//         }
//     }
// }

struct MouseAttractSystem
{
    mixin ECS.System!64;

    struct EntitiesData
    {
        uint length;
        @readonly CLocation[] locations;
        CVelocity[] velocity;
    }

    vec2 mouse_pos;

    bool onBegin()
    {
        if(!launcher.getKeyState(SDL_SCANCODE_SPACE))return false;
        mouse_pos = launcher.mouse.position;
        mouse_pos = vec2(mouse_pos.x, mouse_pos.y) * launcher.scalling - launcher.render_position;
        return true;
    }

    void onUpdate(EntitiesData data)
    {
        float speed = launcher.delta_time * 0.01;
        foreach(i;0..data.length)
        {
            vec2 rel_pos = mouse_pos - data.locations[i];
            float len2 = rel_pos.x * rel_pos.x + rel_pos.y * rel_pos.y;
            if(len2 < 0.1)len2 = 0.1;
            data.velocity[i] = data.velocity[i] + rel_pos / len2 * speed;
        }
    }
}

struct AttractSystem
{
    mixin ECS.System!64;

    struct EntitiesData
    {
        uint length;
        @readonly CLocation[] locations;
        CVelocity[] velocity;
    }

    struct Updater
    {
        AttractSystem.EntitiesData data;

        void onUpdate(AttractorIterator.EntitiesData adata)
        {
            float speed = launcher.delta_time * 0.00004;
            if(adata.vortex)
            {
                foreach(i;0..data.length)
                {
                    foreach(j;0..adata.length)
                    {
                        vec2 rel_pos = data.locations[i] - adata.locations[j];
                        float len2 = rel_pos.length2();
                        float inv_len = rsqrt(len2);

                        if(1 < adata.force_range[j].range.y*inv_len)
                        {
                            float dist = (adata.force_range[j].range.y - 0.4)*inv_len - 1;
                            
                            vec2 vec = rel_pos * inv_len;
                            vec2 cvec = vec2(-vec.y,vec.x);

                            float sign = -1;
                            if(1 < adata.force_range[j].range.x*inv_len)sign = 1;
                            
                            float str = adata.attractor[j].strength * sign;
                            float vortex_str = adata.vortex[j].strength;
                            data.velocity[i] = data.velocity[i] + (rel_pos * str + cvec * vortex_str) * speed * dist;
                        }
                    }
                }
            }
            else
            {
                foreach(i;0..data.length)
                {
                    foreach(j;0..adata.length)
                    {
                        vec2 rel_pos = data.locations[i] - adata.locations[j];
                        float len2 = rel_pos.length2();
                        float inv_len = rsqrt(len2);

                        if(1 < adata.force_range[j].range.y*inv_len)
                        {
                            float dist = (adata.force_range[j].range.y - 0.4)*inv_len - 1;
                            
                            vec2 vec = rel_pos;

                            float sign = -1;
                            if(1 < adata.force_range[j].range.x*inv_len)sign = 1;
                            
                            float str = adata.attractor[j].strength * speed * dist * sign;
                            data.velocity[i] = data.velocity[i] + vec * str;
                        }
                    }
                }
            }
        }
    }

    void onUpdate(EntitiesData data)
    {
        Updater updater;
        updater.data = data;
        launcher.manager.callEntitiesFunction!AttractorIterator(&updater.onUpdate);
    }
}

struct AttractorIterator
{
    mixin ECS.System!1;

    struct EntitiesData
    {
        uint length;
        @readonly CLocation[] locations;
        @readonly CAttractor[] attractor;
        @readonly CForceRange[] force_range;
        @optional @readonly CVortex[] vortex;
    }

    bool onBegin()
    {
        return false;
    }

    void onUpdate(EntitiesData data)
    {
        
    }
}

struct PlayAreaSystem
{
    mixin ECS.System!32;

    struct EntitiesData
    {
        uint length;
        Entity[] entity;
        @readonly CLocation[] locations;
    }

    void onUpdate(EntitiesData data)
    {
        foreach(i; 0..data.length)
        {
            if(data.locations[i].x > 440)launcher.manager.removeEntity(data.entity[i].id);
            else if(data.locations[i].x < -40)launcher.manager.removeEntity(data.entity[i].id);
            if(data.locations[i].y > 340)launcher.manager.removeEntity(data.entity[i].id);
            else if(data.locations[i].y < -40)launcher.manager.removeEntity(data.entity[i].id);
        }
    }
}

// struct DampingSystem
// {
//     mixin ECS.System!32;

//     struct EntitiesData
//     {
//         uint length;
//         const (Entity)[] entity;
//         @readonly CDamping[] damping;
//         CVelocity[] velocity;
//     }

//     float[10] damp = 0;

//     bool onBegin()
//     {
//         foreach(i;0..10)
//         {
//             damp[i] = powf((0.99 - cast(float)i * 0.01),launcher.delta_time*0.1);
//         }

//         return true;
//     }

//     void onUpdate(EntitiesData data)
//     {
//         foreach(i; 0..data.length)
//         {
//             data.velocity[i] = data.velocity[i] * damp[data.damping[i]];
//         }
//     }
// }

struct ParticleLifeSystem
{
    mixin ECS.System!32;

    struct EntitiesData
    {
        uint length;
        const (Entity)[] entity;
        CParticleLife[] life;
    }

    int delta_time;

    bool onBegin()
    {
        delta_time = cast(int)(launcher.delta_time * 1000);
        return true;
    }

    void onUpdate(EntitiesData data)
    {
        foreach(i; 0..data.length)
        {
            data.life[i] -= delta_time;
            if(data.life[i] < 0)launcher.manager.removeEntity(data.entity[i].id);
        }
    }
}

struct GravitySystem
{
    mixin ECS.System!32;

    struct EntitiesData
    {
        uint length;
        const (Entity)[] entity;
        @readonly CGravity[] gravity;
        CVelocity[] velocity;
    }

    void onUpdate(EntitiesData data)
    {
        float delta_time = launcher.delta_time * 0.00_092;
        foreach(i; 0..data.length)
        {
            data.velocity[i].y -= delta_time;
        }
    }
}

/*#######################################################################################################################
------------------------------------------------ Functions ------------------------------------------------------------------
#######################################################################################################################*/

struct ParticlesDemo
{
    __gshared const (char)* tips = "Use \"space\" to spwan entities.\n\nSystems can be enabled/disabled from \"Simple\" window.";

    Texture texture;
}

__gshared ParticlesDemo* particles_demo;

void particlesRegister()
{
    particles_demo = Mallocator.make!ParticlesDemo;

    particles_demo.texture.create();
    particles_demo.texture.load("assets/textures/atlas.png");

    launcher.manager.beginRegister();

    registerRenderingModule(launcher.manager);

    launcher.manager.registerComponent!CLocation;
    //launcher.manager.registerComponent!CTexCoords;
    launcher.manager.registerComponent!CColor;
    launcher.manager.registerComponent!CVelocity;
    launcher.manager.registerComponent!CScale;
    launcher.manager.registerComponent!CTexCoords;
    launcher.manager.registerComponent!CTexCoordsIndex;
    launcher.manager.registerComponent!CRotation;
    launcher.manager.registerComponent!CDepth;
    launcher.manager.registerComponent!CAttractor;
    launcher.manager.registerComponent!CDamping;
    launcher.manager.registerComponent!CGravity;
    launcher.manager.registerComponent!CVortex;
    launcher.manager.registerComponent!CParticleLife;
    launcher.manager.registerComponent!CForceRange;
    launcher.manager.registerComponent!CMaterialIndex;

    launcher.manager.registerSystem!MoveSystem(0);
    launcher.manager.registerSystem!DrawSystem(100);
    launcher.manager.registerSystem!PlayAreaSystem(102);
    launcher.manager.registerSystem!AttractSystem(-1);
    launcher.manager.registerSystem!MouseAttractSystem(1);
    launcher.manager.registerSystem!DampingSystem(101);
    launcher.manager.registerSystem!ParticleLifeSystem(-10);
    launcher.manager.registerSystem!GravitySystem(-2);

    launcher.manager.registerSystem!AttractorIterator(-1);

    launcher.manager.endRegister();
}

void particlesStart()
{
    DrawSystem* draw_system = launcher.manager.getSystem!DrawSystem;
    draw_system.default_data.size = vec2(2,2);
    draw_system.default_data.coords = vec4(246,64,2,2)*px;
    draw_system.default_data.material_id = 2;
    draw_system.default_data.texture = particles_demo.texture;

    launcher.gui_manager.addSystem(MoveSystem.system_id,"Move System");
    launcher.gui_manager.addSystem(DrawSystem.system_id,"Draw System");
    launcher.gui_manager.addSystem(PlayAreaSystem.system_id,"Play Area System");
    launcher.gui_manager.addSystem(AttractSystem.system_id,"Attract System");
    launcher.gui_manager.addSystem(MouseAttractSystem.system_id,"Mouse Attract System");
    launcher.gui_manager.addSystem(DampingSystem.system_id,"Damping System");
    launcher.gui_manager.addSystem(ParticleLifeSystem.system_id,"Particle Life System");

    // launcher.gui_manager.addComponent(CColor(),"Color (white)");
    // launcher.gui_manager.addComponent(CColor(0xFF101540),"Color (red)");
    // launcher.gui_manager.addComponent(CColor(0xFF251010),"Color (blue)");
    // launcher.gui_manager.addComponent(CColor(0xFF102010),"Color (green)");
    launcher.gui_manager.addComponent(CColor(0xFF101540),"Color");
    launcher.gui_manager.addComponent(CAttractor(0.1),"Attractor");
    launcher.gui_manager.addComponent(CForceRange(vec2(5,40)),"ForceRange");
    launcher.gui_manager.addComponent(CVelocity(),"Velocity");
    launcher.gui_manager.addComponent(CDamping(),"Damping");
    launcher.gui_manager.addComponent(CVortex(),"Vortex");
    launcher.gui_manager.addComponent(CParticleLife(),"Particle Life");
    launcher.gui_manager.addComponent(CGravity(),"Gravity");

    EntityTemplate* tmpl;
    EntityTemplate* base_tmpl = launcher.manager.allocateTemplate([CTexCoords.component_id, CLocation.component_id, CColor.component_id, CVelocity.component_id, CDamping.component_id, CScale.component_id, CMaterialIndex.component_id].staticArray);
    base_tmpl.getComponent!CColor().value = 0xFF251010;
    base_tmpl.getComponent!CScale().value = vec2(2);
    base_tmpl.getComponent!CTexCoords().value = vec4(246,64,2,2)*px;
    base_tmpl.getComponent!CMaterialIndex().value = 2;
    launcher.gui_manager.addTemplate(base_tmpl,"Particle");
    // tmpl = launcher.manager.allocateTemplate(base_tmpl);
    // tmpl.getComponent!CColor().value = 0xFF251010;
    // launcher.gui_manager.addTemplate(tmpl,"Particle (blue)");
    // tmpl = launcher.manager.allocateTemplate(base_tmpl);
    // tmpl.getComponent!CColor().value = 0xFF102010;
    // launcher.gui_manager.addTemplate(tmpl,"Particle (green)");
    // tmpl = launcher.manager.allocateTemplate(base_tmpl);
    // tmpl.getComponent!CColor().value = 0xFF101540;
    // launcher.gui_manager.addTemplate(tmpl,"Particle (red)");
    // tmpl = launcher.manager.allocateTemplate(tmpl, [CDamping.component_id].staticArray);
    // launcher.gui_manager.addTemplate(tmpl,"Particle (damping)");
    // tmpl = launcher.manager.allocateTemplate(tmpl);
    // tmpl.getComponent!CDamping().power = 4;
    // launcher.gui_manager.addTemplate(tmpl,"Particle (damping!)");
    tmpl = launcher.manager.allocateTemplate([CAttractor.component_id, CLocation.component_id, CForceRange.component_id, CScale.component_id].staticArray);
    tmpl.getComponent!CScale().value = vec2(4);
    launcher.gui_manager.addTemplate(tmpl,"Attractor");
    tmpl = launcher.manager.allocateTemplate(tmpl, [CVortex.component_id].staticArray);
    launcher.gui_manager.addTemplate(tmpl,"Vortex");
    // tmpl = launcher.manager.allocateTemplate(tmpl);
    // tmpl.getComponent!CVortex().strength = -0.6;
    // launcher.gui_manager.addTemplate(tmpl,"Vortex (reversed)");

}

void particlesEnd()
{
    particles_demo.texture.destroy();

    //launcher.manager.freeTemplate(simple.tmpl);
    Mallocator.dispose(particles_demo);
}

void particlesEvent(SDL_Event* event)
{
}

bool particlesLoop()
{
    launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5;
    
    launcher.manager.begin();
    if(launcher.multithreading)
    {
        launcher.job_updater.begin();
        launcher.manager.updateMT();
        launcher.job_updater.call();
    }
    else
    {
        launcher.manager.update();
    }
    launcher.manager.end();

    return true;
}

DemoCallbacks getParticlesDemo()
{
    DemoCallbacks demo;
    demo.register = &particlesRegister;
    demo.initialize = &particlesStart;
    demo.deinitialize = &particlesEnd;
    demo.loop = &particlesLoop;
    demo.tips = ParticlesDemo.tips;
    return demo;
}