#pragma once
#include "../../../internal-include/GPU/gpu_internal.hpp"
#include <PSX/GPU/gpu_auto_load_font.hpp>

namespace JabyEngine {
    namespace GPU {
        namespace SJIS {
            struct FontBuffer {                
                // A line of 16 Pixel
                struct Line {
                    LookUpColor4 pixel[BIOS_Font::Size.width/4];

                    void load(uint16_t bit_pattern) {
                        static constexpr auto BitsPerRound = 4;

                        size_t px_idx = 0;
                        for(uint16_t shift = 1 << 15; shift; shift = shift >> BitsPerRound) {
                            uint8_t lu_ids[BitsPerRound];

                            for(size_t n = 0; n < BitsPerRound; n++) {
                                lu_ids[n] = (bit_pattern & (shift >> n)) ? 1 : 0;
                            }

                            this->pixel[px_idx++] = LookUpColor4::create(lu_ids);
                        }
                    }

                    static PositionU16 vram_offset(uint16_t tile_id) {
                        return tile_id_for16<PositionU16>(tile_id, SizeU16::create(4, 16));
                    }
                };

                // Letters are actually only 16x15 but should be treated 16x16
                struct Letter {
                    PositionU16 position; 
                    Line        lines[BIOS_Font::Size.height - 1];
                    Line        empty_line;

                    void setup() {
                        this->empty_line.load(0);
                    }

                    void load_to(const PositionU16& pos, const uint16_t* bit_map) {
                        this->position = pos;
                        for(auto& cur_line : this->lines) {
                            cur_line.load(__builtin_bswap16(*bit_map++));
                        }
                    }
                };

                //                    v double buffer do not change size without adjusting
                Letter  letter_buffer[2];
                uint8_t         free_idx;

                void setup() {
                    for(auto& letter : this->letter_buffer) {
                        letter.setup();
                    }
                    this->free_idx = 0;
                    GPU::internal::DMA::Receive::prepare();
                }

                void shutdown() {
                    GPU::internal::DMA::wait();
                    GPU::internal::DMA::end();
                }

                void load_to(const PositionU16& pos, const uint16_t* bit_map) {
                    auto& cur_letter = this->letter_buffer[this->free_idx];
                    this->free_idx ^= 1;

                    cur_letter.load_to(pos, bit_map);
                    GPU::internal::DMA::wait();
                    GPU::internal::DMA::Receive::set_src(reinterpret_cast<uintptr_t>(cur_letter.lines));
                    //                                                                         v 4 Pixel per byte
                    GPU::internal::DMA::Receive::set_dst(cur_letter.position, SizeU16::create(16/4, 16));
                    GPU::internal::DMA::Receive::start(sizeof(Line) >> 2);
                }

                static PositionU16 vram_offset(uint16_t tile_id) {
                    return Line::vram_offset(tile_id);
                }             
            };

            struct SpecialChar {
                uint8_t base_offset;
                uint8_t tile_id;
            };

            struct RangeChar {
                SpecialChar start_char;
                uint8_t     length;
            };

            static_assert(BIOS_Font::Size == SizeU16::create(16, 16));
        }
    }
}