/********************\
|  Copyright 2024,   |
|       Ulysse Cura  |
\********************/

////////////////////////////////////////////
//                                        //
//   Système pour le joueur, gère tout ce //
// qui est necessaire pour le joueur.     //
//                                        //
////////////////////////////////////////////

#ifndef PLAYER_HPP
#define PLAYER_HPP

#include <algorithm>
#include <cmath>
#include <vector>
#include "AnimationSystem.hpp"
#include "ECS.hpp"
#include "InteractableComponent.hpp"
#include "SpriteComponent.hpp"
#include "TransformComponent.hpp"
#include "../Camera.hpp"
#include "../Events.hpp"
#include "../Game.hpp"
#include "../TileMap.hpp"
#include "../Vector2D.hpp"

using std::lerp, std::abs, std::vector, std::sort;

constexpr const float EPSILON = 1e-6f;  // Tolérance

class PlayerSystem : public Component {
  public:
    void init() override
    {
        m_transform = &entity->getComponent<TransformComponent>();
        m_sprite = &entity->getComponent<SpriteComponent>();
        m_hitbox = &entity->getComponent<HitboxComponent>();
        m_animation = &entity->getComponent<AnimationSystem>();
    }

    void update() override
    {
        m_getInputs();
        m_handleHitboxes();
        m_adjustVelocity();
        m_setStateAndAnimation();
        m_setFlip();
        m_setCamera();
        m_checkInteractions();
    }

#ifdef DEBUG_MODE
    void draw() override
    {
        SDL_SetRenderDrawColor(Game::renderer, 200, 20, 18, 255);

        for(auto it {Game::entityManager.getEntities().begin() + 1}; it < Game::entityManager.getEntities().end(); it++)
        {
            if(it->get()->hasComponent<InteractableComponent>())
            {
                SDL_Rect interactableR;

                if(it->get()->hasComponent<HitboxComponent>())
                {
                    interactableR = it->get()->getComponent<HitboxComponent>().hitboxR;
                }
                else
                {
                    interactableR.x = static_cast<int>(it->get()->getComponent<TransformComponent>().position.x);
                    interactableR.y = static_cast<int>(it->get()->getComponent<TransformComponent>().position.y);
                    interactableR.w = it->get()->getComponent<TransformComponent>().dimension.x * it->get()->getComponent<TransformComponent>().scale;
                    interactableR.h = it->get()->getComponent<TransformComponent>().dimension.y * it->get()->getComponent<TransformComponent>().scale;
                }

                interactableR.x -= m_interactionRange;
                interactableR.y -= m_interactionRange;
                interactableR.w += (m_interactionRange * 2);
                interactableR.h += (m_interactionRange * 2);

                SDL_RenderDrawRect(Game::renderer, &interactableR);
            }
        }

        SDL_Rect futureHitboxR = m_hitbox->hitboxR;
        futureHitboxR.x += static_cast<int>(m_transform->velocity.x * static_cast<float>(m_transform->speed));
        futureHitboxR.y += static_cast<int>(m_transform->velocity.y * static_cast<float>(m_transform->speed));

        SDL_RenderDrawRect(Game::renderer, &futureHitboxR);

        SDL_SetRenderDrawColor(Game::renderer, 20, 20, 18, 255);
    }
#endif

  private:
    void m_getInputs()
    {
        m_transform->velocity.x = Game::events.keys[SDL_SCANCODE_A] * -1 + Game::events.keys[SDL_SCANCODE_D] * 1;
        m_transform->velocity.y = Game::events.keys[SDL_SCANCODE_W]   * -1 + Game::events.keys[SDL_SCANCODE_S]  * 1;
    }

    void m_handleHitboxes()
    {
        for(int i {0}; i < 2; i++)
        {
            SDL_Rect futureHitboxR = m_hitbox->hitboxR;
            if(i == 0) futureHitboxR.x += static_cast<int>(m_transform->velocity.x * static_cast<float>(m_transform->speed));
            if(i == 1) futureHitboxR.y += static_cast<int>(m_transform->velocity.y * static_cast<float>(m_transform->speed));

            SDL_Rect hitboxTilesR;
            hitboxTilesR.x = futureHitboxR.x / (TILE_SIZE * TILEMAP_SCALE);
            hitboxTilesR.y = futureHitboxR.y / (TILE_SIZE * TILEMAP_SCALE);
            hitboxTilesR.w = (futureHitboxR.w + futureHitboxR.x) / (TILE_SIZE * TILEMAP_SCALE);
            hitboxTilesR.h = (futureHitboxR.h + futureHitboxR.y) / (TILE_SIZE * TILEMAP_SCALE);

            vector<int> touchedHitboxes {};

            for(int tileY = hitboxTilesR.y; tileY <= hitboxTilesR.h; tileY++)
            {
                for(int tileX = hitboxTilesR.x; tileX <= hitboxTilesR.w; tileX++)
                {
                    touchedHitboxes.emplace_back(Game::tileMap.hitboxes[tileX + tileY * Game::tileMap.tileMapWidth]);
                }
            }

            sort(touchedHitboxes.begin(), touchedHitboxes.end());

            for(const auto &touchedHitbox : touchedHitboxes)
            {
                if(touchedHitbox == 1)
                {
                    if(i == 0) m_transform->velocity.x = 0;
                    if(i == 1) m_transform->velocity.y = 0;
                    break;
                }
                else if(touchedHitbox != 0)
                {
                    Game::tileMap.LoadNextMap(touchedHitbox);
                    m_transform->position = Game::tileMap.getPlayerInitPos();

                    Game::camera.camR.x = static_cast<int>(m_transform->position.x - static_cast<float>(Game::camera.camR.w / 2 - m_transform->dimension.x * m_transform->scale / 2));
                    Game::camera.camR.y = static_cast<int>(m_transform->position.y - static_cast<float>(Game::camera.camR.h / 2 - m_transform->dimension.y * m_transform->scale / 2));

                    playerState = State::None;
                    break;
                }
            }

            for(auto it {Game::entityManager.getEntities().begin() + 1}; it < Game::entityManager.getEntities().end(); it++)
            {
                if(it->get()->hasComponent<HitboxComponent>())
                {
                    if(it->get()->getComponent<HitboxComponent>().hitboxActivated)
                    {
                        if(SDL_HasIntersection(&futureHitboxR, &it->get()->getComponent<HitboxComponent>().hitboxR))
                        {
                            if(i == 0) m_transform->velocity.x = 0;
                            if(i == 1) m_transform->velocity.y = 0;
                            break;
                        }
                    }
                }
            }
        }
    }

    void m_adjustVelocity()
    {
        if(isNotZero(m_transform->velocity.x) && isNotZero(m_transform->velocity.y))
        {
            m_transform->velocity.x *= 0.707106781186548f;
            m_transform->velocity.y *= 0.707106781186548f;
        }
    }

    void m_setStateAndAnimation()
    {
        if((isNotZero(m_transform->velocity.x) || isNotZero(m_transform->velocity.y)) && playerState != State::Running)
        {
            playerState = State::Running;
            m_animation->changeAnimation("ressources/heroes/knight/run-sheet.png", 6, 10);
        }
        else if(!(isNotZero(m_transform->velocity.x) || isNotZero(m_transform->velocity.y)) && playerState != State::Idle)
        {
            playerState = State::Idle;
            m_animation->changeAnimation("ressources/heroes/knight/idle-sheet.png", 4, 15);
        }
    }

    void m_setFlip()
    {
        if(m_transform->velocity.x < 0.0f)
            m_sprite->flipSprite = true;
        if(m_transform->velocity.x > 0.0f)
            m_sprite->flipSprite = false;
    }

    void m_setCamera()
    {
        float targetX = m_transform->position.x - static_cast<float>(Game::camera.camR.w / 2 - m_transform->dimension.x * m_transform->scale / 2);
        float targetY = m_transform->position.y - static_cast<float>(Game::camera.camR.h / 2 - m_transform->dimension.y * m_transform->scale / 2);

        float smoothingFactor {0.08f};

        if(playerState == State::Running) smoothingFactor = 0.12f;

        Game::camera.camR.x = static_cast<int>(lerp(Game::camera.camR.x, targetX, smoothingFactor));
        Game::camera.camR.y = static_cast<int>(lerp(Game::camera.camR.y, targetY, smoothingFactor));
    }

    void m_checkInteractions()
    {
        if(Game::events.keys[SDL_SCANCODE_E])
        {
            for(auto it {Game::entityManager.getEntities().begin() + 1}; it < Game::entityManager.getEntities().end(); it++)
            {
                if(it->get()->hasComponent<InteractableComponent>())
                {
                    SDL_Rect interactableR;

                    if(it->get()->hasComponent<HitboxComponent>())
                    {
                        interactableR = it->get()->getComponent<HitboxComponent>().hitboxR;
                    }
                    else
                    {
                        interactableR.x = static_cast<int>(it->get()->getComponent<TransformComponent>().position.x);
                        interactableR.y = static_cast<int>(it->get()->getComponent<TransformComponent>().position.y);
                        interactableR.w = it->get()->getComponent<TransformComponent>().dimension.x * it->get()->getComponent<TransformComponent>().scale;
                        interactableR.h = it->get()->getComponent<TransformComponent>().dimension.y * it->get()->getComponent<TransformComponent>().scale;
                    }

                    if(m_isInRange(interactableR))
                    {
                        it->get()->getComponent<InteractableComponent>().interact();
                    }
                }
            }
        }
    }

    bool m_isInRange(const SDL_Rect &entityR)
    {
        SDL_Rect intersectR {entityR};

        intersectR.x -= m_interactionRange;
        intersectR.y -= m_interactionRange;
        intersectR.w += (m_interactionRange * 2);
        intersectR.h += (m_interactionRange * 2);

        return SDL_HasIntersection(&m_hitbox->hitboxR, &intersectR);
    }

    inline bool isNotZero(float value)
    {
        return abs(value) > EPSILON;
    }

    enum class State {
        None,
        Idle,
        Running
    };

    TransformComponent *m_transform;
    SpriteComponent *m_sprite;
    HitboxComponent *m_hitbox;
    AnimationSystem *m_animation;

    int m_interactionRange {10};

    State playerState {State::None};
};

#endif
