#pragma once
#include <PSX/AutoLBA/auto_lba.hpp>
#include <PSX/Auxiliary/math_helper.hpp>
#include <PSX/System/IOPorts/cd_io.hpp>
#include <stddef.hpp>

namespace JabyEngine {
    namespace CD {
        using BCD        = uint8_t;
        using CodingInfo = uint8_t;
        using Mode       = uint8_t;
        using SubMode    = uint8_t;

        struct Header {
            BCD  minute;
            BCD  second;
            BCD  sector;
            Mode mode;
        };

        struct SubHeader {
            uint8_t    file_number;
            uint8_t    channel_number;
            SubMode    sub_mode;
            CodingInfo coding_info;
        };

        namespace internal {
            struct RawXADataSector {
                Header    header;
                SubHeader sub_header;
                /*SubHeader copy_sub_header;
                uint8_t   data[0x800];
                uint32_t  edc;
                uint8_t   ecc[0x114];*/
            };

            class SectorBufferAllocator {
            private:
                typedef CD_IO::DataSector* (*AllocatorFunction)(void* ctx);

            private:
                void*             ctx      = nullptr;
                AllocatorFunction allocate = nullptr;

                constexpr SectorBufferAllocator(void* ctx, AllocatorFunction function) : ctx(ctx), allocate(function) {
                }

            public:
                constexpr SectorBufferAllocator() = default;

                static constexpr SectorBufferAllocator create(void* obj, AllocatorFunction function) {
                    return SectorBufferAllocator(obj, function);
                }

                static constexpr SectorBufferAllocator invalid() {
                    return SectorBufferAllocator(nullptr, nullptr);
                }

                inline CD_IO::DataSector* allocate_sector() const {
                    if(this->allocate) {
                        return this->allocate(this->ctx);
                    }
                    return nullptr;
                }
            };

            struct BCDTimeStamp {
                static constexpr size_t MaxSector  = 75;
                static constexpr size_t MaxSeconds = 60;

                uint8_t min;
                uint8_t sec;
                uint8_t sector;

                static constexpr BCDTimeStamp from_time(uint8_t min, uint8_t sec, uint8_t sectors) {
                    return BCDTimeStamp{.min = to_bcd(min), .sec = to_bcd(sec), .sector = to_bcd(sectors)};
                }

                static constexpr BCDTimeStamp from(uint32_t lba) {
                    const auto [min, new_lba] = div_and_mod(lba, MaxSector*MaxSeconds);
                    const auto [sec, sectors] = div_and_mod(new_lba, MaxSector);

                    return BCDTimeStamp::from_time(min, sec, sectors);
                }

                constexpr bool is_zero() const {
                    return this->min == 0 && this->sec == 0 && this->sector == 0;
                }
            };
        }
    }
}