#include "include/global_primitive_buffer.hpp"
#include <PSX/GPU/gpu.hpp>
#include <PSX/GPU/make_gpu_primitives.hpp>
#include <FontWriter/font_writer.hpp>

namespace JabyEngine {
    static constexpr auto ITOABufferSize = 32;

    // Can we not find a better version of this?
    static char* simple_itoa(char (&buffer)[ITOABufferSize], bool is_neg, uint32_t value, uint32_t base) {
        //                       v terminating 0 and space for sign
        int i = ITOABufferSize - 2;
    
        if(value == 0) {
            buffer[0] = '0';
            buffer[1] = '\0';
            return buffer;
        }

        for(; value && i; i--, value /= base) {
            char chr = '0' + value%base;
            if(chr > '9') {
                chr += ('A' - '9' - 1);
            }
            buffer[i] = chr;
        }

        buffer[ITOABufferSize - 1] = '\0';
        if(is_neg) {
            buffer[i] = '-';
            return &buffer[i];
        }
        return &buffer[i + 1];
    }

    static const char* simple_itoa(char (&buffer)[ITOABufferSize], int32_t value, uint32_t base) {
        const auto is_neg = value < 0;
        if(is_neg) {
            value *= -1;
        }
        return simple_itoa(buffer, is_neg, value, base);
    }

    static const char* simple_ptoa(char (&buffer)[ITOABufferSize], void* value) {
        //                                                     v terminating 0
        static constexpr auto BufferStartID = ITOABufferSize - 1 - 8;

        char* str = simple_itoa(buffer, false, reinterpret_cast<int>(value), 16);
        while(str > &buffer[BufferStartID]) {
            str--;
            *str = '0';
        }
        return &buffer[BufferStartID];
    }

    void FontWriter :: setup(const SimpleTIM& vram_dst, const FontInfo::RenderInfo& font_render_info) {
        for(auto& tex_page : this->tex_page) {
            tex_page = Make::TexPage(vram_dst.get_texture_position(), GPU::TextureColorMode::clut4).linked();
        }
        this->render_info    = font_render_info;
        this->clut           = Make::PageClut(vram_dst.get_clut_position());
        this->last_primitive = nullptr;
    }

    void FontWriter :: clear() {
        this->last_primitive = &this->tex_page[GPU::update_id()];
    }

    void FontWriter :: write(Cursor& cursor, const char* str, GPU::Color24 color, const Wiggle* wiggle, va_list list) {
        static const auto exchang_str = [](const char* str, const char* new_str) -> pair<const char*, const char*> {
            return {str + 1, new_str};
        };

        if(!this->last_primitive) {
            FontWriter::clear();
        }

        const auto sprt_size = GPU::SizeI16::from(this->render_info.font_size);
        const auto row_count = 256/sprt_size.width;
        const auto push_char = [&](Cursor& cursor, char cur_char) -> bool {
             auto& primitive = GlobalPrimitiveFactory.new_primitive();

            primitive->position = cursor.pos;
            primitive->size     = sprt_size;
            if(wiggle) {
                const auto [off_x, off_y] =  (*wiggle)[(cursor.wiggle_count++)&0x7];
                primitive->position.move(off_x, off_y);
            }
            primitive->tex_offset = GPU::PageOffset::from_tile_id(cur_char - '!', row_count, sprt_size);
            primitive->clut       = this->clut;
            primitive->color      = color;

            this->last_primitive->concat(primitive);
            this->last_primitive = &primitive;
            cursor.pos.move(this->render_info.kern_size.width, 0);
            return true;
        };
        const auto  old_x    = cursor.pos.x;
        const char* prev_str = nullptr;
        char        buffer[32]; 

        while(true) {
            auto cur_char = *str++;
            switch(cur_char) {
                case '\0':
                    if(prev_str) {
                        str = prev_str;
                        prev_str = nullptr;
                        continue;
                    }
                    return;

                case '\n':
                    cursor.pos.x  = old_x;
                    cursor.pos.y += this->render_info.kern_size.height;
                    continue;

                case ' ':
                    cursor.pos.x += this->render_info.kern_size.width;
                    continue;

                case '%':
                    switch(*str) {
                        case '%':
                            str++;
                            break;

                        case 'i':
                            tie(prev_str, str) = exchang_str(str, simple_itoa(buffer, va_arg(list, int), 10));
                            continue;

                        case 'p':
                            tie(prev_str, str) = exchang_str(str, simple_ptoa(buffer, va_arg(list, void*)));
                            continue;

                        case 's':
                            tie(prev_str, str) = exchang_str(str, va_arg(list, const char*));
                            continue;
                    }

                default:
                    if(!push_char(cursor, cur_char)) {
                        return;
                    }
            }   
        }
    }

    void FontWriter :: render() {
        this->last_primitive->terminate();
        this->last_primitive = nullptr;

        GPU::render(this->tex_page[GPU::render_id()]);
    }
}