module demos.brick_breaker;

import app;

import bindbc.sdl;

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

import cimgui.cimgui;

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

import game_core.basic;
import game_core.rendering;
import game_core.collision;

extern(C):

private enum float px = 1.0/512.0;

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

/*struct CLocation
{
    mixin ECS.Component;

    alias location this;

    vec2 location;
}*/

struct CBrick
{
    mixin ECS.Component;
}

struct CPaddle
{
    mixin ECS.Component;
}

struct CBall
{
    mixin ECS.Component;

    ubyte radius;
}

struct CHitPoints
{
    mixin ECS.Component;

    alias value this;

    short value;
}

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

//     alias value this;

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

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

//     alias value this;

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

struct EDamage
{
    mixin ECS.Event;

    ubyte damage = 1;
}

/*#######################################################################################################################
------------------------------------------------ Systems ------------------------------------------------------------------
#######################################################################################################################*/

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

//     struct EntitiesData
//     {
//         uint length;
//         CLocation[] location;
//         @readonly CVelocity[] velocity;
//         @optional @readonly CVelocityFactor[] vel_factor;
//     }

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

struct EdgeCollisionSystem
{
    mixin ECS.System!64;

    struct EntitiesData
    {
        uint length;
        CLocation[] location;
        CVelocity[] velocity;
        //CBall[] ball_flag;
    }

    void onUpdate(EntitiesData data)
    {
        foreach(i; 0..data.length)
        {
            if(data.location[i].x < 0)
            {
                if(data.velocity[i].x < 0)data.velocity[i].x = -data.velocity[i].x;
                data.location[i].x = 0;
            }
            else if(data.location[i].x > 400)
            {
                if(data.velocity[i].x > 0)data.velocity[i].x = -data.velocity[i].x;
                data.location[i].x = 400;
            }

            if(data.location[i].y < 0)
            {
                if(data.velocity[i].y < 0)data.velocity[i].y = -data.velocity[i].y;
                data.location[i].y = 0;
            }
            else if(data.location[i].y > 300)
            {
                if(data.velocity[i].y > 0)data.velocity[i].y = -data.velocity[i].y;
                data.location[i].y = 300;
            }
        }
    }
}

struct BallCollisionSystem
{
    mixin ECS.System!64;

    mixin ECS.ReadOnlyDependencies!(ShootGridDependency, BVHDependency);

    struct EntitiesData
    {
        ///variable named "length" contain entites count
        uint length;
        const (Entity)[] entity;
        CVelocity[] velocity;
        @readonly CLocation[] location;
        @readonly CScale[] scale;
        @readonly CBall[] ball_flag;
    }

    struct State
    {
        bool test(EntityID id)
        {
            Entity* entity = launcher.manager.getEntity(id);
            if(entity)
            {
                CLocation* location = entity.getComponent!CLocation;
                CScale* scale = entity.getComponent!CScale;
                if(location && scale)
                {
                    float radius = data.scale[i].x;
                    vec2 rel_pos = *location - data.location[i];
                    vec2 abs_rel_pos = rel_pos;
                    if(abs_rel_pos.x < 0)abs_rel_pos.x = -abs_rel_pos.x;
                    if(abs_rel_pos.y < 0)abs_rel_pos.y = -abs_rel_pos.y;
                    
                    vec2 half_scale = *scale * 0.25f;

                    if(abs_rel_pos.x < half_scale.x + radius &&
                        abs_rel_pos.y < half_scale.y + radius)
                    {
                        if(abs_rel_pos.x < half_scale.x)
                        {
                            if(rel_pos.y * data.velocity[i].y > 0)
                            {
                                data.velocity[i].y = -data.velocity[i].y;
                                launcher.manager.sendEvent(id,EDamage(1));
                                return false;
                            }
                        }
                        else if(abs_rel_pos.y < half_scale.y)
                        {
                            if(rel_pos.x * data.velocity[i].x > 0)
                            {
                                data.velocity[i].x = -data.velocity[i].x;
                                launcher.manager.sendEvent(id,EDamage(1));
                                return false;
                            }
                        }
                        else 
                        {
                            vec2 vector = abs_rel_pos - half_scale;
                            if(rel_pos.x > 0)vector.x = -vector.x;
                            if(rel_pos.y > 0)vector.y = -vector.y;

                            float pow_dist = vector.length2();
                            if(pow_dist < radius*radius)
                            {
                                vector = vector / sqrtf(pow_dist);
                                data.velocity[i] = data.velocity[i] - vector * (2 * dot(vector, data.velocity[i]));
                                launcher.manager.sendEvent(id,EDamage(1));
                                return false;
                            }
                        }
                    }
                }
            }
            return true;
        }

        EntitiesData data;
        uint i;
    }

    ShootGrid* grid;
    BVHTree* tree;
    BVHTree* static_tree;

    bool onBegin()
    {
        //grid = launcher.manager.getSystem!ShootGridManager().grid;
        tree = launcher.manager.getSystem!BVHBuilder().tree;
        static_tree = launcher.manager.getSystem!StaticBVHBuilder().tree;
        //if(grid is null)return false;
        if(tree is null || static_tree is null)return false;
        else return true;
    }

    void onUpdate(EntitiesData data)
    {
        // State state;
        // state.data = data;
        // EntityID id;
        // foreach(i; 0..data.length)
        // {
        //     state.i = i;
        //     float radius = data.scale[i].x;
        //     if(grid.test(id, data.location[i] - radius, data.location[i] + radius, ubyte.max))
        //     {
        //         state.test(id);
        //     }
        // }
        State state;
        state.data = data;
        foreach(i; 0..data.length)
        {
            state.i = i;
            //float radius = data.scale[i].x;
            AABB bounding = AABB(data.location[i]-data.scale[i], data.location[i]+data.scale[i]);
            tree.test(bounding, cast(bool delegate(EntityID id))&state.test);
            static_tree.test(bounding, cast(bool delegate(EntityID id))&state.test);
        }
    }
}

struct DamageSystem
{
    mixin ECS.System!64;

    mixin ECS.ReadOnlyDependencies!(ShootGridDependency);

    struct EntitiesData
    {
        ///variable named "length" contain entites count
        uint length;
        const (Entity)[] entity;
        CHitPoints[] hit_points;
    }

    void handleEvent(Entity* entity, EDamage event)
    {
        EntityMeta meta = entity.getMeta();
        CHitPoints* hp = meta.getComponent!CHitPoints;
        hp.value -= event.damage;
        if(hp.value < 0)launcher.manager.removeEntity(entity.id);
    }

}

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

struct BrickBreakerDemo
{
    __gshared const (char)* tips = "Brick breaker demo. It's a game about destroying evil bricks.";

    //EntityTemplate* tmpl;
    Texture texture;
}

__gshared BrickBreakerDemo* demo;

void brickBreakerRegister()
{
    demo = Mallocator.make!BrickBreakerDemo;

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

    launcher.manager.beginRegister();

    registerRenderingModule(launcher.manager);
    registerCollisionModule(launcher.manager);

    launcher.manager.registerComponent!CLocation;
    launcher.manager.registerComponent!CRotation;
    launcher.manager.registerComponent!CScale;
    launcher.manager.registerComponent!CTexCoords;
    launcher.manager.registerComponent!CTexCoordsIndex;
    launcher.manager.registerComponent!CVelocity;
    launcher.manager.registerComponent!CInput;
    launcher.manager.registerComponent!CPaddle;
    launcher.manager.registerComponent!CDamping;
    launcher.manager.registerComponent!CVelocityFactor;
    launcher.manager.registerComponent!CBall;
    launcher.manager.registerComponent!CHitPoints;

    launcher.manager.registerEvent!EDamage;
    
    launcher.manager.registerSystem!MoveSystem(-100);
    launcher.manager.registerSystem!EdgeCollisionSystem(-99);
    launcher.manager.registerSystem!BallCollisionSystem(-79);
    launcher.manager.registerSystem!InputMovementSystem(-120);
    launcher.manager.registerSystem!DampingSystem(-120);
    launcher.manager.registerSystem!DamageSystem(-120);
    
    launcher.manager.endRegister();
}

void brickBreakerStart()
{
    DrawSystem* draw_system = launcher.manager.getSystem!DrawSystem;
    draw_system.default_data.color = 0x80808080;
    draw_system.default_data.texture = demo.texture;
    draw_system.default_data.size = vec2(16,16);
    draw_system.default_data.coords = vec4(246,64,2,2)*px;
    draw_system.default_data.material_id = 0;

    EntityTemplate* brick_tmpl = launcher.manager.allocateTemplate(
                [CLocation.component_id, CScale.component_id, CColor.component_id, 
                CTexCoordsIndex.component_id, CBVH.component_id, CHitPoints.component_id,
                CAABB.component_id, CStatic.component_id].staticArray
                );
    brick_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(304,40,16,8)*px);
    brick_tmpl.getComponent!CColor().value = 0x80206020;
    brick_tmpl.getComponent!CScale().value = vec2(16,8);
    brick_tmpl.getComponent!CHitPoints().value = 2;
    //brick_tmpl.getComponent!CAABB().bounding = AABB(vec2(),vec2());

    EntityTemplate* big_brick_tmpl = launcher.manager.allocateTemplate(brick_tmpl);
    big_brick_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(320,32,16,16)*px);
    big_brick_tmpl.getComponent!CScale().value = vec2(16,16);

    EntityTemplate* paddle_tmpl = launcher.manager.allocateTemplate(
                [CLocation.component_id, CScale.component_id, CInput.component_id,
                CTexCoordsIndex.component_id, CPaddle.component_id, CVelocity.component_id,
                CDamping.component_id, CVelocityFactor.component_id, CBVH.component_id,
                CAABB.component_id].staticArray
                );
    paddle_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(272,48,64,10)*px);
    paddle_tmpl.getComponent!CScale().value = vec2(64,10);
    paddle_tmpl.getComponent!CDamping().value = 14;
    paddle_tmpl.getComponent!CVelocityFactor().value = vec2(1,0);

    EntityTemplate* ball_tmpl = launcher.manager.allocateTemplate(
                [CLocation.component_id, CScale.component_id, //CDamping.component_id,
                CTexCoordsIndex.component_id, CBall.component_id, CVelocity.component_id].staticArray
                );
    ball_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(304,32,8,8)*px);
    ball_tmpl.getComponent!CScale().value = vec2(8,8);
    ball_tmpl.getComponent!CVelocity().value = vec2(0.1,0.1);
    // paddle_tmpl.getComponent!CDamping().value = 14;

    launcher.gui_manager.addComponent(CLocation(), "Location");
    launcher.gui_manager.addComponent(CRotation(), "Rotation");
    launcher.gui_manager.addComponent(CScale(), "Scale");
    launcher.gui_manager.addComponent(CColor(), "Color");
    launcher.gui_manager.addComponent(CTexCoords(), "Tex Coords");
    launcher.gui_manager.addComponent(CTexCoordsIndex(), "Tex Coords Index");
    launcher.gui_manager.addComponent(CVelocity(), "Velocity");
    launcher.gui_manager.addComponent(CInput(), "Velocity");
    launcher.gui_manager.addComponent(CDamping(), "Damping");
    launcher.gui_manager.addComponent(CBall(), "Ball");
    launcher.gui_manager.addComponent(CBVH(), "BVH");
    launcher.gui_manager.addComponent(CAABB(), "AABB");

    launcher.gui_manager.addSystem(MoveSystem.system_id, "Move System");
    launcher.gui_manager.addSystem(EdgeCollisionSystem.system_id, "Edge Collision System");
    launcher.gui_manager.addSystem(BallCollisionSystem.system_id, "Ball Collision System");
    launcher.gui_manager.addSystem(InputMovementSystem.system_id, "Input Movement System");
    launcher.gui_manager.addSystem(DampingSystem.system_id, "Damping System");
    launcher.gui_manager.addSystem(DamageSystem.system_id, "Damage System");

    launcher.gui_manager.addTemplate(brick_tmpl, "Brick");
    launcher.gui_manager.addTemplate(big_brick_tmpl, "Big Brick");
    launcher.gui_manager.addTemplate(paddle_tmpl, "Paddle");
    launcher.gui_manager.addTemplate(ball_tmpl, "Ball");

    foreach(i;0..10)
    {
        CColor color;
        final switch(i)
        {
            case 0:color = 0x80206020;break;
            case 1:color = 0x80602020;break;
            case 2:color = 0x80202060;break;
            case 3:color = 0x80206060;break;
            case 4:color = 0x80606020;break;
            case 5:color = 0x80602060;break;
            case 6:color = 0x80606060;break;
            case 7:color = 0x80202020;break;
            case 8:color = 0x80008030;break;
            case 9:color = 0x80206080;break;
        }
        foreach (j; 0..20)
        {
            launcher.manager.addEntity(brick_tmpl,[CLocation(vec2(j*18,300-i*10)).ref_, color.ref_].staticArray);
        }
    }

    launcher.manager.addEntity(paddle_tmpl,[CLocation(vec2(190,20)).ref_].staticArray);
    launcher.manager.addEntity(ball_tmpl,[CLocation(vec2(190,40)).ref_].staticArray);

}

void brickBreakerEnd()
{
    demo.texture.destroy();

    Mallocator.dispose(demo);
}

void brickBreakerEvent(SDL_Event* event)
{
}

bool brickBreakerLoop()
{
    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 getBrickBreakerDemo()
{
    DemoCallbacks demo;
    demo.register = &brickBreakerRegister;
    demo.initialize = &brickBreakerStart;
    demo.deinitialize = &brickBreakerEnd;
    demo.loop = &brickBreakerLoop;
    demo.tips = .demo.tips;
    return demo;
}