#include "../../../include/shared.hpp"
#include "include/frame.hpp"
#include <FontWriter/fonts.hpp>
#include <PSX/GPU/gpu.hpp>
#include <PSX/GPU/make_gpu_primitives.hpp>
#include <PSX/Periphery/periphery.hpp>
#include <PSX/Timer/frame_timer.hpp>

namespace ScreenCenter {
    using namespace JabyEngine;
    using GenericButton = Periphery::GenericController::Button;

    struct ButtonPulser {
        static constexpr auto StartTime = 2500_ms;
        static constexpr auto PulseTime = 100_ms;

        enum struct State {
            WentDown,
            Pulse,
            Unkown
        };

        SimpleTimer<uint8_t> timer;
        uint8_t              pulse_time;

        void setup() {
            this->timer.reset();
            this->pulse_time = StartTime;
        }

        void reset() {
            ButtonPulser::setup();
        }

        State check(GenericButton button) {
            const auto controller = Periphery::get_primary_controller_as<Periphery::GenericController>();

            if(!controller.button.was_down(button)) {
                ButtonPulser::reset();
                if(controller.button.went_down(button)) {
                    return State::WentDown;
                }
            }

            if(this->timer.is_expired_for(this->pulse_time)) {
                this->pulse_time = PulseTime;
                this->timer.reset();

                return State::Pulse;
            }
            return State::Unkown;
        }
    };

    #ifdef JABYENGINE_PAL
        static const char         TVModeStr[] = "PAL";
        static constexpr uint16_t ScanlinesV  = 288;
    #else
        static const char         TVModeStr[] = "NTSC";
        static constexpr uint16_t ScanlinesV  = 240;
    #endif //JABYENGINE_PAL

    namespace PSYQ {
        static const char*const Name = "PSYQ";

        static void set_offset(uint16_t x, uint16_t y) {
            GPU::Display::set_offset(x, y);
        }
    }

    struct Formular {
        const char* name;
        void (*function)(uint16_t, uint16_t);
    };

    static const Formular ScreenFormulars[] = {
        Formular{.name = PSYQ::Name, .function = PSYQ::set_offset}
    };

    static constexpr const GPU::VRAM2VRAM background_img[] = { 
        // current_id of 0 will be rendering on (0, 256)
        GPU::VRAM2VRAM::create(Make::AreaU16(384, 240, 256, 240), Make::PositionU16(32, GPU::Display::Height)),
        GPU::VRAM2VRAM::create(Make::AreaU16(384, 240, 256, 240), Make::PositionU16(32,                    0)),
    };

    static auto frame = Frame::create();
   
    static ButtonPulser  button_pulse[4];
    static void        (*update)()    = nullptr;
    static int16_t       offset_x     = 0;
    static int16_t       offset_y     = 0;
    static uint8_t       formular_sel = 0;

    static void update_interactive();
    static void reset_screen();

    static void update_enter_state() {
        static const char     IntroductionTest[]     = "Press START to begin with\n";
        static constexpr auto IntroductionTestLength = BIOSFont::Info.estimate_str_render_length(IntroductionTest);
        static constexpr auto CenterPoint            = Make::PositionI16((GPU::Display::Width - IntroductionTestLength)/2, (GPU::Display::Height - (2*16))/2);

        const auto controller = Periphery::get_primary_controller_as<Periphery::GenericController>();

        if(controller.button.went_up(GenericButton::R1) || controller.button.went_up(GenericButton::L1)) {
            // Only one mode supported
            //formular_sel ^= 1;
        }

        if(controller.button.went_down(GenericButton::ST)) {
            update = update_interactive;
        }

        auto cursor = FontWriter::update(CenterPoint);
        FontWriter::bios_font_writer.write(cursor, IntroductionTest,                   GPU::Color24::White());
        FontWriter::bios_font_writer.write(cursor, ScreenFormulars[formular_sel].name, GPU::Color24::White());
    }

    static void update_interactive() {
        static const auto handle_button = [](ButtonPulser& button_pulse, GenericButton button, int16_t &dst, const int16_t mlp) {
            switch(button_pulse.check(button)) {
                case ButtonPulser::State::WentDown:
                    dst += (1*mlp);
                    break;

                case ButtonPulser::State::Pulse:
                    dst += (2*mlp);
                    break;
            }
        };

        static const char*const ModeStr     = "TV-Mode: %s\n";
        static const char*const FormularStr = "<<%s>>\n";
        static const char       OffsetStr[] = "Offset: %i, %i";

        static constexpr auto CenterLength = FontWriter::BIOSFont::Info.estimate_str_render_length(OffsetStr);
        static constexpr auto CenterPoint  = Make::PositionI16((GPU::Display::Width - CenterLength)/2, (GPU::Display::Height - 3*16)/2);

        const auto& screen_formular = ScreenFormulars[formular_sel];
        const auto  controller      = Periphery::get_primary_controller_as<Periphery::GenericController>();

        handle_button(button_pulse[0], GenericButton::Left,  offset_x, -1);
        handle_button(button_pulse[1], GenericButton::Right, offset_x,  1);
        handle_button(button_pulse[2], GenericButton::Up,    offset_y, -1);
        handle_button(button_pulse[3], GenericButton::Down,  offset_y,  1);

        if(controller.button.is_down(GenericButton::R1) && controller.button.is_down(GenericButton::L1)) {
            for(auto& pulse : button_pulse) {
                pulse.setup();
            }
            reset_screen();
        }

        auto cursor = FontWriter::update(CenterPoint.move(-offset_x, -offset_y));
        FontWriter::bios_font_writer.write(cursor, ModeStr,     GPU::Color24::White(), TVModeStr);
        FontWriter::bios_font_writer.write(cursor, FormularStr, GPU::Color24::White(), screen_formular.name);
        FontWriter::bios_font_writer.write(cursor, OffsetStr,   GPU::Color24::White(), offset_x, offset_y); 

        screen_formular.function(offset_x, offset_y);
    }

    static void reset_screen() {
        PSYQ::set_offset(0, 0);
        offset_x = 0;
        offset_y = 0;
    }

    static void setup() {
        Shared::back_menu.reset();
        frame.setup();

        for(auto& pulse : button_pulse) {
            pulse.setup();
        }

        update       = update_enter_state;
        formular_sel = 0;
        offset_x     = 0;
        offset_y     = 0;
    }

    static bool update_or_exit() {
        Periphery::query_controller();
        if(Shared::back_menu.update(Make::PositionI16(0, GPU::Display::Height - 32))) {
            return true;
        }

        update();
        return false;
    }

    static void render() {
        GPU::render(background_img[GPU::Display::current_id]);
        frame.render();
        Shared::back_menu.render();
    }

    void main() {
        setup();

        while(true) {
            if(update_or_exit()) {
                reset_screen();
                break;
            }
            GPU::swap_buffers_vsync(1);
            render();
        }
    }
}