using BizHawk.Common;
using BizHawk.Emulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
    /// <summary>
    /// 3rd attempt at a base ULA implementation
    /// </summary>
    public abstract class ULA : IVideoProvider
    {
        #region Other Devices

        /// <summary>
        /// The emulated spectrum
        /// </summary>
        protected SpectrumBase _machine;

        /// <summary>
        /// The CPU monitor class
        /// </summary>
        protected CPUMonitor CPUMon;

        #endregion

        #region Construction & Initialisation

        public ULA (SpectrumBase machine)
        {
            _machine = machine;
            CPUMon = _machine.CPUMon;
            borderType = _machine.Spectrum.SyncSettings.BorderType;
        }

        #endregion

        #region Palettes

        /// <summary>
        /// The standard ULA palette
        /// </summary>
        private static readonly int[] ULAPalette =
        {
            Colors.ARGB(0x00, 0x00, 0x00), // Black
            Colors.ARGB(0x00, 0x00, 0xD7), // Blue
            Colors.ARGB(0xD7, 0x00, 0x00), // Red
            Colors.ARGB(0xD7, 0x00, 0xD7), // Magenta
            Colors.ARGB(0x00, 0xD7, 0x00), // Green
            Colors.ARGB(0x00, 0xD7, 0xD7), // Cyan
            Colors.ARGB(0xD7, 0xD7, 0x00), // Yellow
            Colors.ARGB(0xD7, 0xD7, 0xD7), // White
            Colors.ARGB(0x00, 0x00, 0x00), // Bright Black
            Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
            Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
            Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
            Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
            Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
            Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
            Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
        };

        #endregion

        #region Timing

        /// <summary>
        /// The CPU speed
        /// </summary>
        public int ClockSpeed;

        /// <summary>
        /// Length of frame in T-State cycles
        /// </summary>
        public int FrameCycleLength;

        /// <summary>
        /// The T-State at which the interrupt should be raised within the frame
        /// </summary>
        public int InterruptStartTime;

        /// <summary>
        /// The period for which the interrupt should he held
        /// (simulated /INT pin held low)
        /// </summary>
        public int InterruptLength;

        /// <summary>
        /// The time in T-States for one scanline to complete
        /// </summary>
        public int ScanlineTime;

        /// <summary>
        /// T-States at the left border
        /// </summary>
        public int BorderLeftTime;

        /// <summary>
        /// T-States at the right border
        /// </summary>
        public int BorderRightTime;

        public int FirstPaperLine;
        public int FirstPaperTState;
        public bool Border4T;
        public int Border4TStage;

        #endregion

        #region Interrupt Generation

        /// <summary>
        /// Signs that an interrupt has been raised in this frame.
        /// </summary>
        protected bool InterruptRaised;

        /// <summary>
        /// Signs that the interrupt signal has been revoked
        /// </summary>
        protected bool InterruptRevoked;

        /// <summary>
        /// Resets the interrupt - this should happen every frame in order to raise
        /// the VBLANK interrupt in the proceding frame
        /// </summary>
        public virtual void ResetInterrupt()
        {
            InterruptRaised = false;
            InterruptRevoked = false;
        }

        /// <summary>
        /// Generates an interrupt in the current phase if needed
        /// </summary>
        /// <param name="currentCycle"></param>
        public virtual void CheckForInterrupt(long currentCycle)
        {
            if (InterruptRevoked)
            {
                // interrupt has already been handled
                return;
            }

            if (currentCycle < InterruptStartTime)
            {
                // interrupt does not need to be raised yet
                return;
            }

            if (currentCycle >= InterruptStartTime + InterruptLength)
            {
                // interrupt should have already been raised and the cpu may or
                // may not have caught it. The time has passed so revoke the signal
                InterruptRevoked = true;
                _machine.CPU.FlagI = false;
                return;
            }

            if (InterruptRaised)
            {
                // INT is raised but not yet revoked
                // CPU has NOT handled it yet
                return;
            }

            // Raise the interrupt
            InterruptRaised = true;
            _machine.CPU.FlagI = true;

            CalcFlashCounter();
        }

        /// <summary>
        /// Flash processing
        /// </summary>
        public void CalcFlashCounter()
        {
            flashCounter++;

            if (flashCounter > 15)
            {
                flashOn = !flashOn;
                flashCounter = 0;
            }
        }

        #endregion

        #region Screen Layout

        /// <summary>
        /// Total pixels in one display row
        /// </summary>
        protected int ScreenWidth;
        /// <summary>
        /// Total pixels in one display column
        /// </summary>
        protected int ScreenHeight;
        /// <summary>
        /// Total pixels in top border
        /// </summary>
        protected int BorderTopHeight;
        /// <summary>
        /// Total pixels in bottom border
        /// </summary>
        protected int BorderBottomHeight;
        /// <summary>
        /// Total pixels in left border width
        /// </summary>
        protected int BorderLeftWidth;
        /// <summary>
        /// Total pixels in right border width
        /// </summary>
        protected int BorderRightWidth;
        /// <summary>
        /// Total pixels in one scanline
        /// </summary>
        protected int ScanLineWidth;

        #endregion

        #region State

        /// <summary>
        /// The last T-State cycle at which the screen was rendered
        /// </summary>
        public int LastTState;

        /// <summary>
        /// Flash state
        /// </summary>
        public bool flashOn;

        private int flashCounter;

        protected byte fetchB1;
        protected byte fetchA1;
        protected byte fetchB2;
        protected byte fetchA2;
        protected int ink;
        protected int paper;
        protected int fetchBorder;
        protected int bright;
        protected int flash;

        public int palPaper;
        public int palInk;

        public int BorderColor = 7;

        #endregion

        #region Conversions

        public int FrameLength => FrameCycleLength;

        #endregion

        #region Rendering Configuration

        /// <summary>
        /// Holds all information regarding rendering the screen based on the current T-State
        /// </summary>
        public RenderTable RenderingTable;

        /// <summary>
        /// Holds all information regarding rendering the screen based on the current T-State
        /// </summary>
        public class RenderTable
        {
            /// <summary>
            /// The ULA device
            /// </summary>
            private ULA _ula;

            /// <summary>
            /// Array of rendercycle entries
            /// Starting from the interrupt
            /// </summary>
            public RenderCycle[] Renderer;

            /// <summary>
            /// The emulated machine
            /// </summary>
            public MachineType _machineType;

            public int Offset;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="contPattern"></param>
            public RenderTable(ULA ula, MachineType machineType)
            {
                _ula = ula;
                _machineType = machineType;
                Renderer = new RenderCycle[_ula.FrameCycleLength];
                InitRenderer(machineType);
            }

            /// <summary>
            /// Initializes the renderer
            /// </summary>
            /// <param name="machineType"></param>
            private void InitRenderer(MachineType machineType)
            {
                switch (machineType)
                {
                    case MachineType.ZXSpectrum16:
                    case MachineType.ZXSpectrum48:
                        Offset = 0;
                        break;
                }

                for (var t = 0; t < _ula.FrameCycleLength; t++)
                {
                    var tStateScreen = t + _ula.InterruptStartTime;

                    if (tStateScreen < 0)
                        tStateScreen += _ula.FrameCycleLength;
                    else if (tStateScreen >= _ula.FrameCycleLength)
                        tStateScreen -= _ula.FrameCycleLength;

                    CalculateRenderItem(t, tStateScreen / _ula.ScanlineTime, tStateScreen % _ula.ScanlineTime);
                }

                CreateContention(machineType);
            }

            private void CalculateRenderItem(int item, int line, int pix)
            {
                Renderer[item] = new RenderCycle();

                Renderer[item].RAction = RenderAction.None;
                int pitchWidth = _ula.ScreenWidth + _ula.BorderRightWidth + _ula.BorderLeftWidth;

                int scrPix = pix - _ula.FirstPaperTState;
                int scrLin = line - _ula.FirstPaperLine;

                if ((line >= (_ula.FirstPaperLine - _ula.BorderTopHeight)) && (line < (_ula.FirstPaperLine + 192 + _ula.BorderBottomHeight)) &&
                    (pix >= (_ula.FirstPaperTState - _ula.BorderLeftTime)) && (pix < (_ula.FirstPaperTState + 128 + _ula.BorderRightTime)))
                {
                    // visibleArea (vertical)
                    if ((line >= _ula.FirstPaperLine) && (line < (_ula.FirstPaperLine + 192)) &&
                        (pix >= _ula.FirstPaperTState) && (pix < (_ula.FirstPaperTState + 128)))
                    {
                        // pixel area
                        switch (scrPix & 7)
                        {
                            case 0:
                                Renderer[item].RAction = RenderAction.Shift1AndFetchByte2;   // shift 1 + fetch B2
                                                                                       // +4 = prefetch!
                                Renderer[item].ByteAddress = CalculateByteAddress(scrPix + 4, scrLin);
                                break;
                            case 1:
                                Renderer[item].RAction = RenderAction.Shift1AndFetchAttribute2;   // shift 1 + fetch A2
                                                                                       // +3 = prefetch!
                                Renderer[item].AttributeAddress = CalculateAttributeAddress(scrPix + 3, scrLin);
                                break;
                            case 2:
                                Renderer[item].RAction = RenderAction.Shift1;   // shift 1
                                break;
                            case 3:
                                Renderer[item].RAction = RenderAction.Shift1Last;   // shift 1 (last)
                                break;
                            case 4:
                                Renderer[item].RAction = RenderAction.Shift2;   // shift 2
                                break;
                            case 5:
                                Renderer[item].RAction = RenderAction.Shift2;   // shift 2
                                break;
                            case 6:
                                if (pix < (_ula.FirstPaperTState + 128 - 2))
                                {
                                    Renderer[item].RAction = RenderAction.Shift2AndFetchByte1;   // shift 2 + fetch B2
                                }
                                else
                                {
                                    Renderer[item].RAction = RenderAction.Shift2;             // shift 2
                                }

                                // +2 = prefetch!
                                Renderer[item].ByteAddress = CalculateByteAddress(scrPix + 2, scrLin);
                                break;
                            case 7:
                                if (pix < (_ula.FirstPaperTState + 128 - 2))
                                {
                                    //???
                                    Renderer[item].RAction = RenderAction.Shift2AndFetchAttribute1;   // shift 2 + fetch A2
                                }
                                else
                                {
                                    Renderer[item].RAction = RenderAction.Shift2;             // shift 2
                                }

                                // +1 = prefetch!
                                Renderer[item].AttributeAddress = CalculateAttributeAddress(scrPix + 1, scrLin);
                                break;
                        }
                    }
                    else if ((line >= _ula.FirstPaperLine) && (line < (_ula.FirstPaperLine + 192)) &&
                             (pix == (_ula.FirstPaperTState - 2)))  // border & fetch B1
                    {
                        Renderer[item].RAction = RenderAction.BorderAndFetchByte1; // border & fetch B1
                                                                             // +2 = prefetch!
                        Renderer[item].ByteAddress = CalculateByteAddress(scrPix + 2, scrLin);
                    }
                    else if ((line >= _ula.FirstPaperLine) && (line < (_ula.FirstPaperLine + 192)) &&
                             (pix == (_ula.FirstPaperTState - 1)))  // border & fetch A1
                    {
                        Renderer[item].RAction = RenderAction.BorderAndFetchAttribute1; // border & fetch A1
                                                                             // +1 = prefetch!
                        Renderer[item].AttributeAddress = CalculateAttributeAddress(scrPix + 1, scrLin);
                    }
                    else
                    {
                        Renderer[item].RAction = RenderAction.Border; // border
                    }

                    int wy = line - (_ula.FirstPaperLine - _ula.BorderTopHeight);
                    int wx = (pix - (_ula.FirstPaperTState - _ula.BorderLeftTime)) * 2;
                    Renderer[item].LineOffset = wy * pitchWidth + wx;
                }
            }

            private void CreateContention(MachineType machineType)
            {
                int[] conPattern = new int[8];

                switch (machineType)
                {
                    case MachineType.ZXSpectrum16:
                    case MachineType.ZXSpectrum48:
                    case MachineType.ZXSpectrum128:
                    case MachineType.ZXSpectrum128Plus2:
                        conPattern = new int[] { 6, 5, 4, 3, 2, 1, 0, 0 };
                        break;

                    case MachineType.ZXSpectrum128Plus2a:
                    case MachineType.ZXSpectrum128Plus3:
                        conPattern = new int[] { 1, 0, 7, 6, 5, 4, 3, 2 };
                        break;
                }

                // calculate contention values
                for (int t = 0; t < _ula.FrameCycleLength; t++)
                {
                    int shifted = (t + 1) + _ula.InterruptStartTime + Offset;
                    if (shifted < 0)
                        shifted += _ula.FrameCycleLength;
                    shifted %= _ula.FrameCycleLength;

                    Renderer[t].ContentionValue = 0;

                    int line = shifted / _ula.ScanlineTime;
                    int pix = shifted % _ula.ScanlineTime;
                    if (line < _ula.FirstPaperLine || line >= (_ula.FirstPaperLine + 192))
                    {
                        Renderer[t].ContentionValue = 0;
                        continue;
                    }
                    int scrPix = pix - _ula.FirstPaperTState;
                    if (scrPix < 0 || scrPix >= 128)
                    {
                        Renderer[t].ContentionValue = 0;
                        continue;
                    }
                    int pixByte = scrPix % 8;

                    Renderer[t].ContentionValue = conPattern[pixByte];
                }

                // calculate floating bus values
                for (int t = 0; t < _ula.FrameCycleLength; t++)
                {
                    int shifted = (t + 10) + _ula.InterruptStartTime;
                    if (shifted < 0)
                        shifted += _ula.FrameCycleLength;
                    shifted %= _ula.FrameCycleLength;

                    Renderer[t].FloatingBusAddress = 0;

                    int line = shifted / _ula.ScanlineTime;
                    int pix = shifted % _ula.ScanlineTime;
                    if (line < _ula.FirstPaperLine || line >= (_ula.FirstPaperLine + 192))
                    {
                        Renderer[t].FloatingBusAddress = 0;
                        continue;
                    }
                    int scrPix = pix - _ula.FirstPaperTState;
                    if (scrPix < 0 || scrPix >= 128)
                    {
                        Renderer[t].FloatingBusAddress = 0;
                        continue;
                    }

                    int pixByte = scrPix % 8;
                    int chunk = scrPix % 16;

                    switch (chunk)
                    {
                        case 0:
                        case 2:
                            Renderer[t].FloatingBusAddress = CalculateByteAddress(scrPix, line);
                            break;
                        case 1:
                        case 3:
                            Renderer[t].FloatingBusAddress = CalculateAttributeAddress(scrPix, line);
                            break;
                    }
                }
            }

            private ushort CalculateByteAddress(int x, int y)
            {
                x >>= 2;
                var vp = x | (y << 5);
                return (ushort)((vp & 0x181F) | ((vp & 0x0700) >> 3) | ((vp & 0x00E0) << 3));
            }

            private ushort CalculateAttributeAddress(int x, int y)
            {
                x >>= 2;
                var ap = x | ((y >> 3) << 5);
                return (ushort)(6144 + ap);
            }

            /// <summary>
            /// Render/contention information for a single T-State
            /// </summary>
            public class RenderCycle
            {
                /// <summary>
                /// The ULA render action at this T-State
                /// </summary>
                public RenderAction RAction;
                /// <summary>
                /// The contention value at this T-State
                /// </summary>
                public int ContentionValue;
                /// <summary>
                /// The screen byte address at this T-State
                /// </summary>
                public ushort ByteAddress;
                /// <summary>
                /// The screen attribute address at this T-State
                /// </summary>
                public ushort AttributeAddress;
                /// <summary>
                /// The byte address returned by the floating bus at this T-State
                /// </summary>
                public ushort FloatingBusAddress;
                /// <summary>
                /// The offset
                /// </summary>
                public int LineOffset;
            }

            public enum RenderAction
            {
                None,
                Border,
                BorderAndFetchByte1,
                BorderAndFetchAttribute1,
                Shift1AndFetchByte2,
                Shift1AndFetchAttribute2,
                Shift1,
                Shift1Last,
                Shift2,
                Shift2Last,
                Shift2AndFetchByte1,
                Shift2AndFetchAttribute1
            }
        }

        #endregion

        #region Render Methods

        /// <summary>
        /// Renders to the screen buffer based on the current cycle
        /// </summary>
        /// <param name="toCycle"></param>
        public void RenderScreen(int toCycle)
        {
            // check boundaries
            if (toCycle > FrameCycleLength)
                toCycle = FrameCycleLength;
            if (LastTState > toCycle)
                LastTState = toCycle - 2;
            if (toCycle < 0)
                toCycle = 0;

            // render the required number of cycles
            for (int t = LastTState; t < toCycle; t++)
            {
                if (!Border4T || (t & 3) == Border4TStage)
                {
                    fetchBorder = BorderColor;
                }
                else
                {

                }

                //fetchBorder = BorderColor;

                // get the table entry
                var item = RenderingTable.Renderer[t];

                switch (item.RAction)
                {
                    case RenderTable.RenderAction.None:
                        break;

                    case RenderTable.RenderAction.Border:
                        ScreenBuffer[item.LineOffset] = ULAPalette[fetchBorder];
                        ScreenBuffer[item.LineOffset + 1] = ULAPalette[fetchBorder];
                        break;

                    case RenderTable.RenderAction.BorderAndFetchByte1:
                        ScreenBuffer[item.LineOffset] = ULAPalette[fetchBorder];
                        ScreenBuffer[item.LineOffset + 1] = ULAPalette[fetchBorder];
                        fetchB1 = _machine.FetchScreenMemory(item.ByteAddress);
                        break;

                    case RenderTable.RenderAction.BorderAndFetchAttribute1:
                        ScreenBuffer[item.LineOffset] = ULAPalette[fetchBorder];
                        ScreenBuffer[item.LineOffset + 1] = ULAPalette[fetchBorder];
                        fetchA1 = _machine.FetchScreenMemory(item.AttributeAddress);
                        ProcessInkPaper(fetchA1);
                        break;

                    case RenderTable.RenderAction.Shift1AndFetchByte2:
                        ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper;
                        fetchB1 <<= 2;
                        fetchB2 = _machine.FetchScreenMemory(item.ByteAddress);
                        break;

                    case RenderTable.RenderAction.Shift1AndFetchAttribute2:
                        ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper;
                        fetchB1 <<= 2;
                        fetchA2 = _machine.FetchScreenMemory(item.AttributeAddress);
                        break;

                    case RenderTable.RenderAction.Shift1:
                        ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper;
                        fetchB1 <<= 2;
                        break;

                    case RenderTable.RenderAction.Shift1Last:
                        ScreenBuffer[item.LineOffset] = ((fetchB1 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB1 & 0x40) != 0) ? palInk : palPaper;
                        fetchB1 <<= 2;
                        ProcessInkPaper(fetchA2);
                        break;

                    case RenderTable.RenderAction.Shift2:
                        ScreenBuffer[item.LineOffset] = ((fetchB2 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB2 & 0x40) != 0) ? palInk : palPaper;
                        fetchB2 <<= 2;
                        break;

                    case RenderTable.RenderAction.Shift2AndFetchByte1:
                        ScreenBuffer[item.LineOffset] = ((fetchB2 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB2 & 0x40) != 0) ? palInk : palPaper;
                        fetchB2 <<= 2;
                        fetchB1 = _machine.FetchScreenMemory(item.ByteAddress);
                        break;

                    case RenderTable.RenderAction.Shift2AndFetchAttribute1:
                        ScreenBuffer[item.LineOffset] = ((fetchB2 & 0x80) != 0) ? palInk : palPaper;
                        ScreenBuffer[item.LineOffset + 1] = ((fetchB2 & 0x40) != 0) ? palInk : palPaper;
                        fetchB2 <<= 2;
                        fetchA1 = _machine.FetchScreenMemory(item.AttributeAddress);
                        ProcessInkPaper(fetchA1);
                        break;
                }
            }

            LastTState = toCycle;
        }

        private void ProcessInkPaper(byte attrData)
        {
            bright = (attrData & 0x40) >> 3;
            flash = (attrData & 0x80) >> 7;
            ink = (attrData & 0x07);
            paper = ((attrData >> 3) & 0x7);

            palInk = ULAPalette[ink + bright];
            palPaper = ULAPalette[paper + bright];

            // swap paper and ink when flash is on
            if (flashOn && (flash != 0)) 
            {
                int temp = palInk;
                palInk = palPaper;
                palPaper = temp;
            }
        }

        /// <summary>
        /// Returns floating bus value (if available)
        /// </summary>
        /// <param name="tstate"></param>
        /// <returns></returns>
        public void ReadFloatingBus(int tstate, ref int result)
        {
            int off = 1;
            tstate += off;
            if (tstate >= RenderingTable.Renderer.Length)
                tstate -= RenderingTable.Renderer.Length;
            if (tstate < 0)
                tstate += RenderingTable.Renderer.Length;

            var item = RenderingTable.Renderer[tstate];

            switch (RenderingTable._machineType)
            {
                case MachineType.ZXSpectrum16:
                case MachineType.ZXSpectrum48:
                case MachineType.ZXSpectrum128:
                case MachineType.ZXSpectrum128Plus2:
                    /*
                    if (item.FloatingBusAddress > 0)
                    {
                        result = _machine.FetchScreenMemory(item.FloatingBusAddress);
                        //result = 0x00;
                    }
                    */
                    switch (item.RAction)
                    {
                        case RenderTable.RenderAction.BorderAndFetchByte1:
                        case RenderTable.RenderAction.Shift1AndFetchByte2:
                        case RenderTable.RenderAction.Shift2AndFetchByte1:
                            result = _machine.FetchScreenMemory(item.ByteAddress);
                            break;
                        case RenderTable.RenderAction.BorderAndFetchAttribute1:
                        case RenderTable.RenderAction.Shift1AndFetchAttribute2:
                        case RenderTable.RenderAction.Shift2AndFetchAttribute1:
                            result = _machine.FetchScreenMemory(item.AttributeAddress);
                            break;
                        default:
                            //result = _machine.FetchScreenMemory(fetchA2);
                            break;
                    }
                    

                    break;

                case MachineType.ZXSpectrum128Plus2a:
                case MachineType.ZXSpectrum128Plus3:
                    break;
            }
        }

        #endregion

        #region Contention

        /// <summary>
        /// Returns the contention value for the current t-state
        /// </summary>
        /// <returns></returns>
        public int GetContentionValue()
        {
            return GetContentionValue((int)_machine.CurrentFrameCycle);
        }

        /// <summary>
        /// Returns the contention value for the supplied t-state
        /// </summary>
        /// <returns></returns>
        public int GetContentionValue(int tstate)
        {
            int off = 5;
            tstate += off;
            if (tstate >= FrameCycleLength)
                tstate -= FrameCycleLength;

            if (tstate < 0)
                tstate += FrameCycleLength;

            return RenderingTable.Renderer[tstate].ContentionValue;
        }

        /// <summary>
        /// Returns the contention value for the supplied t-state
        /// </summary>
        /// <returns></returns>
        public int GetPortContentionValue(int tstate)
        {
            int off = 1;
            tstate += off;
            if (tstate >= FrameCycleLength)
                tstate -= FrameCycleLength;

            if (tstate < 0)
                tstate += FrameCycleLength;

            return RenderingTable.Renderer[tstate].ContentionValue;
        }

        #endregion

        #region IVideoProvider

        /// <summary>
        /// Video output buffer
        /// </summary>
        public int[] ScreenBuffer;

        private int _virtualWidth;
        private int _virtualHeight;
        private int _bufferWidth;
        private int _bufferHeight;

        public int BackgroundColor
        {
            get { return ULAPalette[7]; } //ULAPalette[borderColour]; }
        }

        public int VirtualWidth
        {
            get { return _virtualWidth; }
            set { _virtualWidth = value; }
        }

        public int VirtualHeight
        {
            get { return _virtualHeight; }
            set { _virtualHeight = value; }
        }

        public int BufferWidth
        {
            get { return _bufferWidth; }
            set { _bufferWidth = value; }
        }

        public int BufferHeight
        {
            get { return _bufferHeight; }
            set { _bufferHeight = value; }
        }

        public int VsyncNumerator
        {
            get { return ClockSpeed * 50; }// ClockSpeed; }
            set { }
        }

        public int VsyncDenominator
        {
            get { return ClockSpeed; }//FrameLength; }
        }

        public int[] GetVideoBuffer()
        {
            switch (borderType)
            {
                // Full side borders, no top or bottom border (giving *almost* 16:9 output)
                case ZXSpectrum.BorderType.Widescreen:
                    // we are cropping out the top and bottom borders
                    var startPixelsToCrop = ScanLineWidth * BorderTopHeight;
                    var endPixelsToCrop = ScanLineWidth * BorderBottomHeight;
                    int index = 0;
                    for (int i = startPixelsToCrop; i < ScreenBuffer.Length - endPixelsToCrop; i++)
                    {
                        croppedBuffer[index++] = ScreenBuffer[i];
                    }
                    return croppedBuffer;

                // The full spectrum border
                case ZXSpectrum.BorderType.Full:
                    return ScreenBuffer;

                case ZXSpectrum.BorderType.Medium:
                    // all border sizes now 24
                    var lR = BorderLeftWidth - 24;
                    var rR = BorderRightWidth - 24;
                    var tR = BorderTopHeight - 24;
                    var bR = BorderBottomHeight - 24;
                    var startP = ScanLineWidth * tR;
                    var endP = ScanLineWidth * bR;

                    int index2 = 0;
                    // line by line
                    for (int i = startP; i < ScreenBuffer.Length - endP; i += ScreenWidth + BorderLeftWidth + BorderRightWidth)
                    {
                        // each pixel in each line
                        for (int p = lR; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR; p++)
                        {
                            if (index2 == croppedBuffer.Length)
                                break;
                            croppedBuffer[index2++] = ScreenBuffer[i + p];
                        }
                    }

                    return croppedBuffer;

                case ZXSpectrum.BorderType.Small:
                    // all border sizes now 24
                    var lR_ = BorderLeftWidth - 10;
                    var rR_ = BorderRightWidth - 10;
                    var tR_ = BorderTopHeight - 10;
                    var bR_ = BorderBottomHeight - 10;
                    var startP_ = ScanLineWidth * tR_;
                    var endP_ = ScanLineWidth * bR_;

                    int index2_ = 0;
                    // line by line
                    for (int i = startP_; i < ScreenBuffer.Length - endP_; i += ScreenWidth + BorderLeftWidth + BorderRightWidth)
                    {
                        // each pixel in each line
                        for (int p = lR_; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR_; p++)
                        {
                            if (index2_ == croppedBuffer.Length)
                                break;
                            croppedBuffer[index2_++] = ScreenBuffer[i + p];
                        }
                    }

                    return croppedBuffer;

                case ZXSpectrum.BorderType.None:
                    // all border sizes now 24
                    var lR__ = BorderLeftWidth;
                    var rR__ = BorderRightWidth;
                    var tR__ = BorderTopHeight;
                    var bR__ = BorderBottomHeight;
                    var startP__ = ScanLineWidth * tR__;
                    var endP__ = ScanLineWidth * bR__;

                    int index2__ = 0;
                    // line by line
                    for (int i = startP__; i < ScreenBuffer.Length - endP__; i += ScreenWidth + BorderLeftWidth + BorderRightWidth)
                    {
                        // each pixel in each line
                        for (int p = lR__; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR__; p++)
                        {
                            if (index2__ == croppedBuffer.Length)
                                break;
                            croppedBuffer[index2__++] = ScreenBuffer[i + p];
                        }
                    }

                    return croppedBuffer;
            }

            return ScreenBuffer;
        }

        protected void SetupScreenSize()
        {
            BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth;
            BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight;
            VirtualHeight = BufferHeight;
            VirtualWidth = BufferWidth;
            ScreenBuffer = new int[BufferWidth * BufferHeight];

            switch (borderType)
            {
                case ZXSpectrum.BorderType.Full:
                    BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth;
                    BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight;
                    VirtualHeight = BufferHeight;
                    VirtualWidth = BufferWidth;
                    ScreenBuffer = new int[BufferWidth * BufferHeight];
                    break;

                case ZXSpectrum.BorderType.Widescreen:
                    BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth;
                    BufferHeight = ScreenHeight;
                    VirtualHeight = BufferHeight;
                    VirtualWidth = BufferWidth;
                    croppedBuffer = new int[BufferWidth * BufferHeight];
                    break;

                case ZXSpectrum.BorderType.Medium:
                    BufferWidth = ScreenWidth + (24) + (24);
                    BufferHeight = ScreenHeight + (24) + (24);
                    VirtualHeight = BufferHeight;
                    VirtualWidth = BufferWidth;
                    croppedBuffer = new int[BufferWidth * BufferHeight];
                    break;

                case ZXSpectrum.BorderType.Small:
                    BufferWidth = ScreenWidth + (10) + (10);
                    BufferHeight = ScreenHeight + (10) + (10);
                    VirtualHeight = BufferHeight;
                    VirtualWidth = BufferWidth;
                    croppedBuffer = new int[BufferWidth * BufferHeight];
                    break;

                case ZXSpectrum.BorderType.None:
                    BufferWidth = ScreenWidth;
                    BufferHeight = ScreenHeight;
                    VirtualHeight = BufferHeight;
                    VirtualWidth = BufferWidth;
                    croppedBuffer = new int[BufferWidth * BufferHeight];
                    break;
            }
        }

        protected int[] croppedBuffer;

        private ZXSpectrum.BorderType _borderType;

        public ZXSpectrum.BorderType borderType
        {
            get { return _borderType; }
            set { _borderType = value; }
        }

        #endregion

        #region Serialization

        public void SyncState(Serializer ser)
        {
            ser.BeginSection("ULA");
            if (ScreenBuffer != null)
                ser.Sync("ScreenBuffer", ref ScreenBuffer, false);
            ser.Sync("FrameLength", ref FrameCycleLength);
            ser.Sync("ClockSpeed", ref ClockSpeed);
            ser.Sync("BorderColor", ref BorderColor);
            ser.Sync("LastTState", ref LastTState);
            ser.Sync("flashOn", ref flashOn);
            ser.Sync("fetchB1", ref fetchB1);
            ser.Sync("fetchA1", ref fetchA1);
            ser.Sync("fetchB2", ref fetchB2);
            ser.Sync("fetchA2", ref fetchA2);
            ser.Sync("ink", ref ink);
            ser.Sync("paper", ref paper);
            ser.Sync("fetchBorder", ref fetchBorder);
            ser.Sync("bright", ref bright);
            ser.Sync("flash", ref flash);
            ser.Sync("palPaper", ref palPaper);
            ser.Sync("palInk", ref palInk);
            ser.EndSection();
        }

        #endregion
    }
}
