#include "../utils.h"
#include "../factory_objects.h"
#include "../resources.h"
#include "../camera.h"
#include "../level.h"
#include "../helper.h"
#include "../particles.h"
#include "../player.h"

typedef struct actor_robo_s
{
    object_t           *self_object;
    object_t           *object_hook;
    actor_hook_t       *actor_hook;
    actor_checkpoint_t *actor_checkpoint;
    sprite_t           *sprite;
    vector_t           position;
    vector_t           velocity;
    bool               on_ground; // флаг: стоим ли на земле
    bool               flip;      // флаг: отражать спрайты или нет
    bool               shot_hook;
    bool               hit;
    bool               hit_draw;
    int                hit_sleep;
    int                hit_draw_sleep;
    //костыль для контроллера
    bool               controller_up;
    bool               controller_right;
    bool               controller_down;
    bool               controller_left;
    bool               controller_space;
} actor_robo_t;

static void _Actor_Robo_CreateAnimations(actor_robo_t *actor)
{
    Sprite_AnimationAdd(actor->sprite, "idle", 8);
    Sprite_AnimationAdd(actor->sprite, "move", 8);
    Sprite_AnimationAdd(actor->sprite, "hook", 5);
    Sprite_AnimationAdd(actor->sprite, "up", 5);
    Sprite_AnimationAdd(actor->sprite, "down", 5);

    Sprite_AnimationAddFrame(actor->sprite, "idle", Utils_GetRect(0,  0, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "idle", Utils_GetRect(24, 0, 24, 24));

    Sprite_AnimationAddFrame(actor->sprite, "move", Utils_GetRect(48, 0, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "move", Utils_GetRect(72, 0, 24, 24));

    Sprite_AnimationAddFrame(actor->sprite, "hook", Utils_GetRect(0,  24, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "hook", Utils_GetRect(24, 24, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "hook", Utils_GetRect(48, 24, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "hook", Utils_GetRect(72, 24, 24, 24));

    Sprite_AnimationAddFrame(actor->sprite, "up", Utils_GetRect(0,  48, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "up", Utils_GetRect(24, 48, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "up", Utils_GetRect(48, 48, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "up", Utils_GetRect(72, 48, 24, 24));

    Sprite_AnimationAddFrame(actor->sprite, "down", Utils_GetRect(0,  72, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "down", Utils_GetRect(24, 72, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "down", Utils_GetRect(48, 72, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "down", Utils_GetRect(72, 72, 24, 24));
    Sprite_AnimationAddFrame(actor->sprite, "down", Utils_GetRect(0,  0, 24, 24));
}

static void _Actor_Robo_GetInfo(actor_robo_t *actor, object_info_t *info)
{
    if (actor == NULL)
        return;

    if (info == NULL)
        return;

    info->box_rect       = Utils_GetRect(0, 0, 72, 72);
    info->collision_rect = Utils_GetRect(0, 0, 42, 48);
    info->position       = actor->position;

    info->box_rect.x = (Uint32)actor->position.x - 36;
    info->box_rect.y = (Uint32)actor->position.y - 45;

    info->collision_rect.x = (Uint32)actor->position.x - 21;
    info->collision_rect.y = (Uint32)actor->position.y - 21;

    info->collision_cell_ul.x = info->collision_rect.x / CELL_WIDTH;
    info->collision_cell_ul.y = info->collision_rect.y / CELL_HEIGHT;
    info->collision_cell_br.x = (info->collision_rect.x + info->collision_rect.w) / CELL_WIDTH;
    info->collision_cell_br.y = (info->collision_rect.y + info->collision_rect.h) / CELL_HEIGHT;
}

static void _Actor_Robo_Collision(actor_robo_t *actor, void *actor_collision, actor_type_t actor_type)
{
    if (actor == NULL)
        return;
}

static void _Actor_Robo_Update(actor_robo_t *actor, float dt)
{
    if (actor == NULL)
        return;

    bool is_grap_hook = false;

    if (actor->object_hook != NULL)
        is_grap_hook = Actor_Hook_IsGrab(actor->actor_hook);

    if (is_grap_hook)
        actor->velocity.y = 0;

    if (actor->controller_space)
    {
        if (actor->object_hook == NULL)
        {
            actor->object_hook = FactoryObjects_Create();
            actor->actor_hook  = Actor_Hook_Create(actor->object_hook, actor, actor->position.x, actor->position.y);

            actor->shot_hook = true;
            Sprite_AnimationPlay(actor->sprite, "hook", false);
        }
        else
        {
            Actor_Robo_HideHook(actor);

            Helper_SetHookShow();
            Helper_SetMoveHorizontal();
        }
    }

    if (actor->controller_up)
    {
        if (actor->shot_hook)
        {
            if (is_grap_hook)
            {
                actor->velocity.y -= ACTOR_ROBO_VELOCITY_VERTICAL;

                if (!Sprite_IsNameCurrentAnimation(actor->sprite, "up"))
                    Sprite_AnimationPlay(actor->sprite, "up", false);
            }
        }
    }

    if (actor->controller_right)
    {
        if (!actor->shot_hook)
        {
            actor->velocity.x = ACTOR_ROBO_VELOCITY_HORIZONTAL;

            if (actor->flip)
            {
                actor->flip = false;
                Sprite_SetFlip(actor->sprite, actor->flip);
            }
        }
    }

    if (actor->controller_down)
    {
        if (actor->shot_hook)
        {
            if (is_grap_hook)
            {
                actor->velocity.y += ACTOR_ROBO_VELOCITY_VERTICAL;

                if (!actor->on_ground)
                {
                    if (!Sprite_IsNameCurrentAnimation(actor->sprite, "up"))
                        Sprite_AnimationPlay(actor->sprite, "up", false);
                }
                else Sprite_AnimationPlay(actor->sprite, "idle", true);
            }
        }
    }

    if (actor->controller_left)
    {
        if (!actor->shot_hook)
        {
            actor->velocity.x = -ACTOR_ROBO_VELOCITY_HORIZONTAL;

            if (!actor->flip)
            {
                actor->flip = true;
                Sprite_SetFlip(actor->sprite, actor->flip);
            }
        }
    }

    actor->controller_up    = false;
    actor->controller_right = false;
    actor->controller_down  = false;
    actor->controller_left  = false;
    actor->controller_space = false;

    object_info_t info;
    float         correct_x = 0;
    float         correct_y = 0;

    actor->position.x += actor->velocity.x;

    _Actor_Robo_GetInfo(actor, &info);

    bool test_collx = Level_CheckCollision(info, actor->velocity, true, &correct_x);

    if (test_collx)
        actor->position.x = correct_x;

    if (!is_grap_hook)
    {
        if (!actor->on_ground)
            actor->velocity.y += ACTOR_GRAVITY;
    }

    actor->position.y += actor->velocity.y;
    actor->on_ground   = false;

    _Actor_Robo_GetInfo(actor, &info);

    bool test_colly = Level_CheckCollision(info, actor->velocity, false, &correct_y);

    if (test_colly)
    {
        actor->position.y = correct_y;

        if (!is_grap_hook)
        {
            if (actor->velocity.y > 0)
            {
                if (actor->velocity.y > 3.9f)
                {
                    Particles_SpawnGroundParticles(actor->position.x, actor->position.y + 27);
                    Camera_PlaySound(on_groundSnd, actor->position);
                }
            }

            actor->on_ground = true;

            if (Sprite_IsNameCurrentAnimation(actor->sprite, "down"))
                Sprite_AnimationPlay(actor->sprite, "idle", true);
        }

        actor->velocity.y = 0;
    }

    // скидываем значения
    actor->velocity.x = 0;

    if (actor->hit)
    {
        actor->hit_sleep++;
        actor->hit_draw_sleep++;

        if (actor->hit_draw_sleep >= 10)
        {
            actor->hit_draw_sleep = 0;

            if (actor->hit_draw)
            {
                actor->hit_draw = false;

                if (actor->sprite != NULL)
                    Sprite_SetTexture(actor->sprite, robo_hitTex);
            }
            else
            {
                actor->hit_draw = true;

                if (actor->sprite != NULL)
                    Sprite_SetTexture(actor->sprite, roboTex);
            }

        }

        if (actor->hit_sleep > 90)
        {
            actor->hit            = false;
            actor->hit_draw       = false;
            actor->hit_sleep      = 0;
            actor->hit_draw_sleep = 0;

            if (actor->sprite != NULL)
                Sprite_SetTexture(actor->sprite, roboTex);
        }
    }

    Level_OpenFogInPosition(actor->position);
    Camera_SetFollowPosition(actor->position);

    Sprite_Update(actor->sprite);
}

static void _Actor_Robo_Draw(SDL_Renderer *renderer, actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    Sprite_Draw(renderer, actor->sprite, actor->position);


    #ifdef DEBUG_DRAW_ROBO_COLLISION_REGION
    vector_t camera_pos = Camera_GetPosition();
    SDL_Rect rect1      = Utils_GetRect(0, 0, CELL_WIDTH, CELL_HEIGHT);
    SDL_Rect rect2      = Utils_GetRect(0, 0, CELL_WIDTH, CELL_HEIGHT);
    object_info_t info;
    _Actor_Robo_GetInfo(actor, &info);

    rect1.x = (info.collision_cell_ul.x * CELL_WIDTH)  - camera_pos.x;
    rect1.y = (info.collision_cell_ul.y * CELL_HEIGHT) - camera_pos.y;
    rect2.x = (info.collision_cell_br.x * CELL_WIDTH)  - camera_pos.x;
    rect2.y = (info.collision_cell_br.y * CELL_HEIGHT) - camera_pos.y;

    SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
    SDL_RenderDrawRect(renderer, &rect1);
    SDL_RenderDrawRect(renderer, &rect2);
    SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a);
    #endif // DEBUG_DRAW_ROBO_COLLISION_REGION
}

static void _Actor_Robo_Free(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    Sprite_Free(actor->sprite);

    free(actor);
}

actor_robo_t* Actor_Robo_Create(object_t *object, float x, float y)
{
    actor_robo_t *actor = NULL;

    if (object != NULL)
    {
        actor = (actor_robo_t*) malloc(sizeof(actor_robo_t));

        actor->self_object      = object;
        actor->object_hook      = NULL;
        actor->actor_hook       = NULL;
        actor->actor_checkpoint = NULL;

        FactoryObjects_SetActor(object, (void*) actor, AT_ROBO, OLAY_ROBO);

        FactoryObjects_SetFunc(object, _Actor_Robo_GetInfo,   OFUNC_GETINFO);
        FactoryObjects_SetFunc(object, _Actor_Robo_Collision, OFUNC_COLLISION);
        FactoryObjects_SetFunc(object, _Actor_Robo_Update,    OFUNC_UPDATE);
        FactoryObjects_SetFunc(object, _Actor_Robo_Draw,      OFUNC_DRAW);
        FactoryObjects_SetFunc(object, _Actor_Robo_Free,      OFUNC_FREE);

        actor->sprite = Sprite_Create(Utils_GetPoint(36, 45), 72, 72, roboTex);

        actor->position  = Utils_GetVector(x, y);
        actor->velocity  = Utils_GetVector(0, 0);
        actor->on_ground = false;
        actor->flip      = false;
        actor->shot_hook = false;
        actor->hit       = false;
        actor->hit_draw  = false;
        actor->hit_sleep      = 0;
        actor->hit_draw_sleep = 0;
        actor->controller_up    = false;
        actor->controller_right = false;
        actor->controller_down  = false;
        actor->controller_left  = false;
        actor->controller_space = false;

        _Actor_Robo_CreateAnimations(actor);

        Sprite_AnimationPlay(actor->sprite, "idle", true);

        Player_SetHealth(10);
        Level_OpenFogInPosition(actor->position);
    }

    return actor;
}

vector_t Actor_Robo_GetPosition(actor_robo_t *actor)
{
    vector_t result = Utils_GetVector(0, 0);

    if (actor != NULL)
        result = actor->position;

    return result;
}

void Actor_Robo_HideHook(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    if (actor->object_hook != NULL)
    {
        FactoryObjects_SignRemove(actor->object_hook);
        actor->object_hook = NULL;

        actor->shot_hook = false;

        if (Actor_Hook_IsGrab(actor->actor_hook))
             Sprite_AnimationPlay(actor->sprite, "down", false);
        else Sprite_AnimationPlay(actor->sprite, "idle", false);
    }
}

void Actor_Robo_ConnectCheckpoint(actor_robo_t *actor, actor_checkpoint_t *actor_checkpoint)
{
    if (actor == NULL)
        return;

    if (actor_checkpoint == NULL)
        return;

    if (actor->actor_checkpoint != NULL)
        Actor_Checkpoint_LowerFlag(actor->actor_checkpoint);

    actor->actor_checkpoint = actor_checkpoint;

    Player_BindCheckpoint(actor->actor_checkpoint);
}

void Actor_Robo_ControllUp(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    actor->controller_up    = true;
}

void Actor_Robo_ControllRight(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    actor->controller_right = true;
}

void Actor_Robo_ControllDown(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    actor->controller_down  = true;
}

void Actor_Robo_ControllLeft(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    actor->controller_left  = true;
}

void Actor_Robo_ControllSpace(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    actor->controller_space = true;
}

void Actor_Robo_Kill(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    #ifndef DEBUG_GOD_MODE
    if (!actor->hit)
    {
        Actor_Robo_HideHook(actor);

        Camera_PlaySound(hitSnd, actor->position);
        Particles_SpawnGibletParticles(actor->position.x, actor->position.y);

        Player_UnbindRobo(false);
        Player_SetHealth(0);
        FactoryObjects_SignRemove(actor->self_object);
    }
    #endif // DEBUG_GOD_MODE
}

void Actor_Robo_Hit(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    #ifndef DEBUG_GOD_MODE
    if (!actor->hit)
    {
        int health = Player_GetHealth();
        health--;

        if(health < 0)
            health = 0;

        Player_SetHealth(health);

        if (health < 1)
        {
            Actor_Robo_Kill(actor);
        }
        else
        {
            actor->hit            = true;
            actor->hit_draw       = true;
            actor->hit_sleep      = 0;
            actor->hit_draw_sleep = 10;

            Camera_PlaySound(hitSnd, actor->position);
        }
    }
    #endif // DEBUG_GOD_MODE
}

void Actor_Robo_Remove(actor_robo_t *actor)
{
    if (actor == NULL)
        return;

    Player_UnbindRobo(true);
    Player_SetHealth(0);
    FactoryObjects_SignRemove(actor->self_object);
}

bool Actor_Robo_IsHit(actor_robo_t *actor)
{
    bool result = false;

    if (actor != NULL)
        result = actor->hit;

    return result;
}
