#pragma once
#include "../Auxiliary/type_traits.hpp"
#include "../System/IOPorts/gpu_io.hpp"
#include "../jabyengine_config.hpp"
#include "gpu_primitives.hpp"

#if !defined(JABYENGINE_NTSC) && !defined(JABYENGINE_PAL)
    #error "JABYENGINE_NTSC or JABYENGINE_PAL must be defined"
#else
    #if defined(JABYENGINE_NTSC) && defined(JABYENGINE_PAL)
        #error "Please define only JABYENGINE_NTSC or JABYENGINE_PAL"
    #endif
#endif

namespace JabyEngine {
    namespace GPU {
        namespace internal {
            void render(const uint32_t* data, size_t words);
            void render_dma(const uint32_t* data);
        }

        struct Display {
            #ifdef JABYENGINE_PAL
                static constexpr size_t   Width          = 320;
                static constexpr size_t   Height         = 256;
                static constexpr uint32_t frames_per_sec = 50;
            #else
                static constexpr size_t   Width          = 320;
                static constexpr size_t   Height         = 240;
                static constexpr uint32_t frames_per_sec = 60;
            #endif

            static uint8_t current_id;

            template<typename T>
            static constexpr Area<T> center(const Area<T>& area) {
                return Area<T>::create(Position<T>::create((Display::Width - area.size.width)/2, (Display::Height - area.size.height)/2), area.size);
            }

            static void enable() {
                GPU_IO::GP1.set_display_state(GPU_IO_Values::DisplayMode::State::On);
            }

            static void disable() {
                GPU_IO::GP1.set_display_state(GPU_IO_Values::DisplayMode::State::Off);
            }

            static void set_offset(int16_t x, int16_t y);
        };
        using VSyncCallback = void (*)();

        static uint32_t update_id() {
            return Display::current_id;
        }

        static uint32_t render_id() {
            return Display::current_id ^ 1;
        }

        void set_vsync_callback(VSyncCallback callback);

        template<typename T>
        static void render(const LinkedElement<T>& linked_primitives) {
            internal::render_dma(&linked_primitives.link_value);
        }

        template<typename T>
        static enable_if<T::is_render_primitive>::type render(const T& primitive) {
            internal::render(reinterpret_cast<const uint32_t*>(&primitive), sizeof(T)/sizeof(uint32_t));
        }

        template<typename T, size_t N>
        static enable_if<T::is_render_primitive>::type render(const LINE_F (&primitives)[N]) {
            internal::render(reinterpret_cast<const uint32_t*>(&primitives), (sizeof(T)/sizeof(uint32_t))*N);
        }

        static void wait_for_render() {
            while(!GPU_IO::GPUSTAT.read().is_set(GPU_IO_Values::GPUSTAT::GP0ReadyForCMD));
        }

        void    swap_buffers(bool clear_screen = true);
        uint8_t swap_buffers_vsync(uint8_t syncs, bool clear_screen = true);
    }
}