#include "../../../internal-include/CD/cd_internal.hpp"
#include <PSX/File/Processor/cd_file_processor.hpp>
#include <stdio.hpp>

namespace JabyEngine {
    static constexpr auto DisabledCircularBufferSize = 512;

    void CDFileProcessor :: start_cur_job(const AutoLBAEntry* lba, const BufferConfiguration& buf_cfg) {
        using CD::internal::SectorBufferAllocator;
            const auto configurate_for = [this](const CDFile& file, const BufferConfiguration& buf_cfg, bool is_lz4) -> FileProcessor::State {
                const uint32_t* data_adr = [this](const CDFile& file, const BufferConfiguration& buf_cfg, bool is_lz4) -> const uint32_t* {
                    const auto disable_lz4 = [this](uint32_t* work_area, size_t size, uint32_t* overwrite_dst = nullptr) -> uint32_t* {
                        reinterpret_cast<uint8_t*>(this->circular_buffer.setup(reinterpret_cast<CD_IO::DataSector*>(work_area), size));

                        this->lz4_decomp.disable();
                        return overwrite_dst ? overwrite_dst : reinterpret_cast<uint32_t*>(work_area);
                    };

                    const auto enable_lz4 = [this](uint32_t* work_area, size_t size, uint32_t* override_dst = nullptr) -> uint32_t* {
                        uint8_t* dst_adr = reinterpret_cast<uint8_t*>(this->circular_buffer.setup(reinterpret_cast<CD_IO::DataSector*>(work_area), size));
                        
                        if(override_dst) {
                            dst_adr = reinterpret_cast<uint8_t*>(override_dst);
                        }

                        this->lz4_decomp.setup(dst_adr);
                        return reinterpret_cast<uint32_t*>(dst_adr);
                    };

                    if(file.type == CDFileType::CopyTo) {
                        return is_lz4 ? enable_lz4(buf_cfg.adr, buf_cfg.sector_count, file.payload.overlay.dst) : disable_lz4(file.payload.copy_to.dst, DisabledCircularBufferSize);
                    }
                    
                    else {
                        return is_lz4 ? enable_lz4(buf_cfg.adr, buf_cfg.sector_count) : disable_lz4(buf_cfg.adr, DisabledCircularBufferSize);
                    }
                }(file, buf_cfg, is_lz4);

                switch(file.type) {
                    case CDFileType::CopyTo:
                        return FileProcessor::create(data_adr, Nothing());

                    case CDFileType::SimpleTIM:
                        return FileProcessor::create(data_adr, file.payload.simple_tim);

                    case CDFileType::SonyTIM:
                        return FileProcessor::create(data_adr, file.payload.tim);

                    case CDFileType::SonyVAG:
                        return FileProcessor::create(data_adr, file.payload.vag);

                    default:
                        return FileProcessor::create_custom(data_adr, static_cast<CDFileType_t>(file.type) - static_cast<CDFileType_t>(CDFileType::Custom), file.payload);     
                }
            };

        const auto& cur_job = *this->jobs.files;
        const auto& cur_lba = lba[cur_job.rel_lba_idx];
        
        this->file_state = configurate_for(cur_job, buf_cfg, cur_lba.is_lz4());
        CD::internal::read_file(cur_lba, SectorBufferAllocator::create(this,
            [](void* ctx) -> CD_IO::DataSector* {
                CDFileProcessor &self = *reinterpret_cast<CDFileProcessor*>(ctx);
                return self.circular_buffer.allocate();
            }));

        //printf(">>> %i.) CD needs to load LBA: %i -> %i (is LZ4: [%s])\n", cur_job.rel_lba_idx, cur_lba.get_lba(), cur_lba.get_size_in_sectors(), cur_lba.is_lz4() ? "Yes" : "No");
    }

    bool CDFileProcessor :: process_data() {
        while(this->circular_buffer.has_data()) {
            ArrayRange<const uint8_t> cur_sector(reinterpret_cast<uint8_t*>(this->circular_buffer.get_next()->data), CD_IO::DataSector::SizeBytes);
            
            //                                                       v We can not know if there will be more data or not - but it also doesn't matter much for us
            const auto result = this->lz4_decomp.process(cur_sector, false);
            this->circular_buffer.pop();

            if(result) {
                // Process the data in the tmp_area
                if(this->file_state.process(result.bytes_ready) == Progress::Error) {
                    return false;
                }
            }

            else {
                return false;
            }
        }
        return true;
    }

    void CDFileProcessor :: setup(const volatile AutoLBAEntry* lba, JobArray jobs, const BufferConfiguration& buf_cfg) {
        this->jobs = jobs;
        CDFileProcessor::start_cur_job(const_cast<const AutoLBAEntry*>(lba), buf_cfg);
    }

    void CDFileProcessor :: shutdown() {
        CD::internal::end_read_file();
    }

    Progress CDFileProcessor :: process() {
        const auto cur_state = CD::internal::read_current_state();
        CDFileProcessor::process_data();
        switch(cur_state) {
            case CD::internal::State::Done:
                /* 
                    We are done now!
                    The user decides if he wants the next value
                */
                //while(this->file_state.process(0) != Progress::Done);
                return Progress::Done;

            case CD::internal::State::BufferFull:
                /* We processed data and unpause the CD drive */
                CD::internal::continue_reading();
                return Progress::InProgress;

            case CD::internal::State::Reading:
                return Progress::InProgress;

            case CD::internal::State::Error:
            default:
                /* Error for real */
                return Progress::Error;
        }
    }
}