using Core.Interfaces;
using System;
using System.IO;

namespace Core.Memory
{
    public class SmsMemoryBus : IMemory
    {
        private bool _isCodemastersMapper = false; //Codemasters used their own memory mapper
        // SMS has 8KB of internal Work RAM
        private readonly byte[] _workRam = new byte[0x2000];

        // The entire game cartridge loaded into one big array
        private byte[] _cartridgeRom = Array.Empty<byte>();

        // The Paging Registers (Which 16KB chunk is in which slot?)
        // Default startup state: Bank 0, 1, and 2 in order.
        private int _romBank0 = 0;
        private int _romBank1 = 1;
        private int _romBank2 = 2;

        // --- NEW: Cartridge RAM ---
        private byte _mapperControl = 0; // 0xFFFC
        private byte[] _cartridgeRam = new byte[0x8000]; // 32KB Max Cart RAM

        // A flag to handle cartridges that don't use paging (like early 32KB games)
        private bool _isCartridgeLoaded = false;
        public bool SramUsed { get; private set; } = false;

        public void LoadCartridge(byte[] romData)
        {
            _cartridgeRom = romData;
            _isCartridgeLoaded = true;

            // Reset the mapper on boot
            _romBank0 = 0;
            _romBank1 = 1;
            _romBank2 = 2;

            // Clean the RAM
            CleanRAMData();
        }

        public byte Read(ushort address)
        {
            if (address < 0xC000 && !_isCartridgeLoaded)
            {
                return 0xFF;
            }

            if (address < 0x4000)
            {
                if (!_isCodemastersMapper && address < 0x0400)
                {
                    return _cartridgeRom[address];
                }
                if (address < 0x0400)
                {
                    return _cartridgeRom[address];
                }
                // Slot 0
                return _cartridgeRom[(_romBank0 * 0x4000) + address];
            }
            if (address < 0x8000)
            {
                // Slot 1
                return _cartridgeRom[(_romBank1 * 0x4000) + (address - 0x4000)];
            }
            if (address < 0xC000)
            {
                // Slot 2 (Or SRAM)
                if ((_mapperControl & 0x08) != 0)
                    return _cartridgeRam[address - 0x8000];
                else
                    return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)];
            }

            // Bitwise AND perfectly forces 0xE000-0xFFFF to mirror down to 0xC000!
            return _workRam[address & 0x1FFF];
        }

        public void Write(ushort address, byte value)
        {
            // Codemasters games bank-switch by writing directly to ROM!
            if (address == 0x0000 || address == 0x4000 || address == 0x8000)
            {
                _isCodemastersMapper = true;
                int cTotalBanks = _cartridgeRom.Length / 0x4000;
                if (cTotalBanks == 0) cTotalBanks = 1;

                if (address == 0x0000) _romBank0 = value % cTotalBanks;
                else if (address == 0x4000) _romBank1 = value % cTotalBanks;
                else if (address == 0x8000) _romBank2 = value % cTotalBanks;
                return;
            }
            if (address < 0x8000) return; // Cannot write to physical ROM

            if (address < 0xC000)
            {
                if ((_mapperControl & 0x08) != 0)
                    _cartridgeRam[address - 0x8000] = value;
                return;
            }

            // THE FIX 1: Save to RAM using the mirror mask
            _workRam[address & 0x1FFF] = value;

            // THE FIX 2: Mapper Control Registers live at the very end of the mirrored RAM!
            if (address >= 0xFFFC)
            {
                // Calculate the absolute maximum number of banks inside the currently loaded ROM
                int totalBanks = _cartridgeRom.Length / 0x4000;

                // Safety catch for empty slots
                if (totalBanks == 0) totalBanks = 1;

                if (address == 0xFFFC)
                {
                    _mapperControl = value;
                }
                else if (address == 0xFFFD)
                {
                    _romBank0 = value % totalBanks; // Force the bank to stay inside the array bounds!
                }
                else if (address == 0xFFFE)
                {
                    _romBank1 = value % totalBanks;
                }
                else if (address == 0xFFFF)
                {
                    _romBank2 = value % totalBanks;
                }
            }
        }

        //public byte Read(ushort address)
        //{
        //    if (address < 0x4000) // ROM Slot 0 (0x0000 - 0x3FFF)
        //    {
        //        // SMS Hardware Quirk: The first 1KB (0x0000 - 0x03FF) is NEVER paged. 
        //        if (address < 0x0400) return ReadFromCartridge(0, address);

        //        return ReadFromCartridge(_romBank0, address);
        //    }
        //    if (address < 0x8000) // ROM Slot 1 (0x4000 - 0x7FFF)
        //    {
        //        return ReadFromCartridge(_romBank1, address & 0x3FFF);
        //    }
        //    if (address < 0xC000) // ROM Slot 2 (0x8000 - 0xBFFF)
        //    {
        //        // --- THE MISSING LINK: Read from Save RAM if enabled! ---
        //        if ((_mapperControl & 0x08) != 0)
        //        {
        //            int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0;
        //            int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF);
        //            return _cartridgeRam[ramOffset];
        //        }

        //        // Otherwise, read from the ROM as usual
        //        return ReadFromCartridge(_romBank2, address & 0x3FFF);
        //    }

        //    // System RAM (0xC000 - 0xFFFF)
        //    return _workRam[address & 0x1FFF];
        //}


        //public void Write(ushort address, byte value)
        //{
        //    if (address < 0xC000)
        //    {
        //        // Bypass the lock if they are trying to save their game!
        //        if (address >= 0x8000 && (_mapperControl & 0x08) != 0)
        //        {
        //            SramUsed = true;
        //            int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0;
        //            int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF);
        //            _cartridgeRam[ramOffset] = value;
        //            return;
        //        }

        //        // You cannot write to a ROM cartridge!
        //        return;
        //    }

        //    // Write to System RAM
        //    _workRam[address & 0x1FFF] = value;

        //    // --- THE SEGA MAPPER ---
        //    if (address == 0xFFFC) // <-- ADD THIS
        //    {
        //        _mapperControl = value;
        //    }
        //    else if (address == 0xFFFD)
        //    {
        //        _romBank0 = value;
        //    }

        //    // Write to System RAM
        //    _workRam[address & 0x1FFF] = value;

        //    // --- THE SEGA MAPPER ---
        //    // If the CPU wrote to the very top of memory, it is commanding a bank swap!
        //    if (address == 0xFFFD)
        //    {
        //        _romBank0 = value;
        //    }
        //    else if (address == 0xFFFE)
        //    {
        //        _romBank1 = value;
        //    }
        //    else if (address == 0xFFFF)
        //    {
        //        _romBank2 = value;
        //    }
        //}

        private byte ReadFromCartridge(int bankIndex, int offset)
        {
            if (!_isCartridgeLoaded) return 0xFF; // Floating bus if no game

            int absoluteAddress = (bankIndex * 0x4000) + offset;

            // Safety check in case a game asks for a bank outside its file size
            if (absoluteAddress >= _cartridgeRom.Length)
            {
                // Wrap around (mirrors the ROM if it's smaller than the requested bank)
                absoluteAddress %= _cartridgeRom.Length;
            }

            return _cartridgeRom[absoluteAddress];
        }
        public void LoadSaveData(string filePath)
        {
            if (System.IO.File.Exists(filePath))
            {
                byte[] saveData = System.IO.File.ReadAllBytes(filePath);
                Array.Copy(saveData, _cartridgeRam, Math.Min(saveData.Length, _cartridgeRam.Length));
                SramUsed = true; // We loaded a save, so it's active!
            }
            else
            {
                for (int i = 0; i < _cartridgeRam.Length; i++)
                {
                    _cartridgeRam[i] = 0xFF;
                }
                SramUsed = false;
            }
        }

        public void SaveSaveData(string filePath)
        {
            // Only write a file to the hard drive if the game actually used the Save RAM!
            if (SramUsed)
            {
                System.IO.File.WriteAllBytes(filePath, _cartridgeRam);
            }
        }

        public void SaveState(BinaryWriter bw)
        {
            bw.Write(_workRam);
            bw.Write(_cartridgeRam);
            bw.Write(SramUsed);
            bw.Write(_mapperControl);
            bw.Write(_romBank0);
            bw.Write(_romBank1);
            bw.Write(_romBank2);
        }

        public void LoadState(BinaryReader br)
        {
            Array.Copy(br.ReadBytes(_workRam.Length), _workRam, _workRam.Length);
            Array.Copy(br.ReadBytes(_cartridgeRam.Length), _cartridgeRam, _cartridgeRam.Length);
            SramUsed = br.ReadBoolean();
            _mapperControl = br.ReadByte();
            _romBank0 = br.ReadInt32();
            _romBank1 = br.ReadInt32();
            _romBank2 = br.ReadInt32();
        }

        public void CleanRAMData()
        {
            Array.Clear(_workRam, 0, _workRam.Length);
            _mapperControl = 0;
            _isCodemastersMapper = false;
        }
    }
}