
#include "game.hh"

#include <algorithm>

#include "itty/vec.hh"
#include "itty/input.hh"

#include "conf.hh"
#include "mob_player.hh"
#include "strings.hh"
#include "scene.hh"

struct Game {
        bool keep_going{true};
        bool paused{false};
        itty::Box view;
        int reset_counter = 0;

        Scene sc;
        itty::Vec start_p;

        Game(const std::filesystem::path& path);
        ~Game();

        void run();

        void update();
        void render();
        void input();

        void room_init();
        void room_update();
        void room_reset();
        void room_populate();

        void recalculate_view();
};

Game::Game(const std::filesystem::path& path)
{
        sc.load(path);
        room_init();
        SDL_HideCursor();
}

Game::~Game()
{
        MIX_StopAllTracks(itty::mixer, 0);
        MIX_DestroyTrack(sc.music);
        SDL_ShowCursor();
}

void Game::run()
{
        while (keep_going) {
                update();
                render();
                input();
        }
}

//////////////////////////////////////////////////////////////////////////////
/// Update
//////////////////////////////////////////////////////////////////////////////

void Game::update()
{
        if (reset_counter > 0) {
                if (--reset_counter == 0)
                        room_reset();
        } else if (sc.mobs.pc->is(Mob_player::DOWN)) {
                reset_counter = itty::conf.fps * 0.5f;
        } else if (!paused) {
                room_update();
                sc.mobs.update(sc);
                sc.phys.step(30.f / itty::conf.fps);
                recalculate_view();
        }
}

//////////////////////////////////////////////////////////////////////////////
/// Render
//////////////////////////////////////////////////////////////////////////////

void Game::render()
{
        itty::Render_lock _;
        glClearColor(0.f, 0.f, 0.f, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        sc.bkgr.render(view);
        sc.mobs.render(view);
}

//////////////////////////////////////////////////////////////////////////////
/// Input
//////////////////////////////////////////////////////////////////////////////

void Game::input()
{
        auto handle_event = [this](int input, float value) {
                switch (input) {
                 case Input_accept:
                        if (value) sc.mobs.pc->begin_jump();
                        else sc.mobs.pc->end_jump();
                        break;
                 case Input_cancel:
                        if (value) sc.mobs.pc->begin_attack();
                        else sc.mobs.pc->end_attack();
                        break;
                 case Input_start:
                        if (value) paused = !paused;
                        break;
                 case Input_menu:
                        if (value) keep_going = false;
                        break;
                }
        };
        itty::handle_events(handle_event);
}

//////////////////////////////////////////////////////////////////////////////
/// Rooms
//////////////////////////////////////////////////////////////////////////////

void Game::room_init()
{
        auto it = std::find_if(sc.rooms.begin(), sc.rooms.end(),
                        [this](auto& p){ return p.contains(sc.mobs.pc->p); });

        if (it == sc.rooms.end()) {
                log_warn("Player fell outside the world");
                sc.room = sc.mobs.pc->box.grow(100);
                return;
        }

        start_p = sc.mobs.pc->p;
        sc.room = *it;
        sc.mobs.clear();
        room_populate();
        sc.bkgr.upload(sc.room);
}

void Game::room_update()
{
        switch (sc.room.ninth(sc.mobs.pc->p)) {
         case itty::Box::CT:
                return;
         case itty::Box::DL:
         case itty::Box::DN:
         case itty::Box::DR:
                room_reset();
                return;
         default:
                room_init();
                return;
        }
}

void Game::room_reset()
{
        sc.mobs.pc->p = start_p;
        sc.mobs.pc->reset();
        sc.mobs.clear();
        room_populate();
}

void Game::room_populate()
{
        for (auto& p : sc.places)
                if (sc.room.overlaps(p.box))
                        sc.mobs.construct(p.type, p.box, &p);
}

//////////////////////////////////////////////////////////////////////////////
/// Camera
//////////////////////////////////////////////////////////////////////////////

void Game::recalculate_view()
{
        static const itty::Vec real{384.f, 216.f};
        view = sc.room.clamp(itty::Box::from_center(sc.mobs.pc->p.floor(), real / 2));
        auto zoom = std::floor(std::min(itty::wsize.x / real.x, itty::wsize.y / real.y));
        itty::viewport(view, zoom);
}

//////////////////////////////////////////////////////////////////////////////
/// Wrapper
//////////////////////////////////////////////////////////////////////////////

void play(const std::filesystem::path& path)
{
        Game game(path);
        game.run();
}

