#include <PSX/Auxiliary/math_helper.hpp>
#include <PSX/System/syscalls.hpp>
#include <string.hpp>

namespace JabyEngine {
    namespace boot {
        namespace BIOS {
            using Version = JabyEngine::BIOS::Version;

            static uint32_t get_bcd_version() {
                return *reinterpret_cast<uint32_t*>(0xBFC00100);
            }

            static const char* get_kernel_maker_str() {
                return reinterpret_cast<const char*>(0xBFC00108);
            }

            static const char* get_version_str() {
                const char* kernel_maker = get_kernel_maker_str();
                const char* ver_start    = kernel_maker + (strlen(kernel_maker) + 1);

                while(*ver_start == 0) {
                    ver_start++;
                }
                return ver_start;
            }

            static Version::Type get_raw_bios_type(const char* version_str) {
                static const auto str_length = []<size_t N>(const char (&name)[N]) -> size_t {
                    return N - 1;
                };
                const char dev_str[]           = "DTL-";
                const char ps1_str[]           = "CEX-";
                const char ps_compatible_str[] = "PS compatible mode";
                const char no$psx_str[]        = "no$psx";
                const char xebra_str[]         = "XEBRA";

                const struct {
                    const char*   name;
                    size_t        name_length;
                    Version::Type type;
                } bioses[] = {
                    // Sorted by likeliness
                    {.name = ps1_str,           .name_length = str_length(ps1_str),           .type = Version::Type::PS1},
                    {.name = ps_compatible_str, .name_length = str_length(ps_compatible_str), .type = Version::Type::PSCompatible},
                    {.name = no$psx_str,        .name_length = str_length(no$psx_str),        .type = Version::Type::No$psx},
                    {.name = xebra_str,         .name_length = str_length(xebra_str),         .type = Version::Type::XEBRA},
                    {.name = dev_str,           .name_length = str_length(dev_str),           .type = Version::Type::Devboard}
                };
         
                for(const auto& bios : bioses) {
                    if(strncmp(version_str, bios.name, bios.name_length) == 0) {
                        return bios.type;
                    }
                }
                return Version::Type::Unkown;
            }

            static Version::Type refine_bios_type(Version::Type type) {
                if(type == Version::Type::PSCompatible) {
                    const auto bios_year_bcd = get_bcd_version() >> 16;
                    return bios_year_bcd == 0x2011 ? Version::Type::PS3 : Version::Type::PS2; 
                }
                return type;
            }

            static Version get_bios_version() {
                Version          version;
                const auto       date_bcd    = get_bcd_version();
                const char*const version_str = get_version_str();

                version.date.day     = from_bcd(static_cast<uint8_t>(date_bcd & 0xFF));
                version.date.month   = from_bcd(static_cast<uint8_t>((date_bcd >> 8) & 0xFF));
                version.date.year    = from_bcd(static_cast<uint16_t>(date_bcd >> 16));
                version.type         = refine_bios_type(get_raw_bios_type(version_str));
                version.kernel_maker = get_kernel_maker_str();
                version.version_str  = version_str;
                version.gui_version  = reinterpret_cast<const char*>(0xBFC7FF32);
                version.copyright    = version.gui_version + (strlen(version.gui_version) + 1);
                return version;
            }

            void identify() {
                const_cast<JabyEngine::BIOS::Version&>(JabyEngine::BIOS::version) = get_bios_version();
            }
        }
    }
}