using Core.Cpu;
using Core.Io;
using Core.Memory;
using Core.Video;
using Core.Audio;
using System;
using System.IO;
using System.Collections.Generic;

namespace Core
{
    public class SmsMachine
    {
        public Z80 Cpu { get; private set; }
        public SmsMemoryBus MemoryBus { get; private set; }
        public SmsIoBus IoBus { get; private set; }
        public SmsVdp VideoProcessor { get; private set; }
        public SmsApu AudioProcessor { get; private set; }
        public ushort? Breakpoint { get; set; } = null;
        private int _tStateCarryover = 0;

        // NTSC SMS T-States per frame
        public const int TStatesPerFrame = 59736; //NTSC
        //public const int TStatesPerFrame = 49780; //PAL

        public SmsMachine()
        { 
            MemoryBus = new SmsMemoryBus();
            VideoProcessor = new SmsVdp();
            AudioProcessor = new SmsApu();
            IoBus = new SmsIoBus { VideoProcessor = this.VideoProcessor, AudioProcessor = this.AudioProcessor };
            Cpu = new Z80(MemoryBus, IoBus);
        }

        public void LoadCartridge(byte[] romData)
        {
            MemoryBus.LoadCartridge(romData);
            Reset();
        }

        public void Reset()
        {
            MemoryBus.CleanRAMData();
            VideoProcessor.Reset();
            Cpu.Reset();
            _tStateCarryover = 0;
        }


        public void RunFrame()
        {
            int tStatesThisFrame = _tStateCarryover;
            while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time
            {
                // 1. Run one CPU instruction
                int cycles = Cpu.Step();
                tStatesThisFrame += cycles;

                // 2. Tell the VDP to catch up
                VideoProcessor.Update(cycles);
                AudioProcessor.Update(cycles);

                // 3. Check if the VDP is begging for attention!
                //if (VideoProcessor.InterruptPending && Cpu.IFF1)
                if (VideoProcessor.InterruptPending)
                {
                    int intCycles = Cpu.RequestInterrupt();
                    if (intCycles > 0)
                    {
                        tStatesThisFrame += intCycles;
                        VideoProcessor.Update(intCycles);
                        AudioProcessor.Update(intCycles);
                    }
                }

                // 4. THE RESTORED BREAKPOINT TRAP
                if (Breakpoint.HasValue && Cpu.PC == Breakpoint.Value)
                {
                    break; // Instantly abort the frame so the debugger can take over!
                }
            }
        }
        public void SaveState(string filePath)
        {
            using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
            using (var bw = new System.IO.BinaryWriter(fs))
            {
                Cpu.SaveState(bw);
                MemoryBus.SaveState(bw);
                VideoProcessor.SaveState(bw);
                AudioProcessor.SaveState(bw);
            }
        }

        public void LoadState(string filePath)
        {
            if (!System.IO.File.Exists(filePath)) return;

            using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
            using (var br = new System.IO.BinaryReader(fs))
            {
                Cpu.LoadState(br);
                MemoryBus.LoadState(br);
                VideoProcessor.LoadState(br);
                AudioProcessor.LoadState(br);
            }
        }
    }
}