
using BizHawk.Common;
using BizHawk.Emulation.Common;

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
    /*
    /// <summary>
    /// ULA (Uncommitted Logic Array) implementation
    /// </summary>
    public abstract class ULABase : IVideoProvider
    {
        #region General

        /// <summary>
        /// Length of the frame in T-States
        /// </summary>
        public int FrameLength;
        
        /// <summary>
        /// Emulated clock speed
        /// </summary>
        public int ClockSpeed;

        /// <summary>
        /// Whether machine is late or early timing model
        /// </summary>
        public bool LateTiming; //currently not implemented

        /// <summary>
        /// The current cycle within the current frame
        /// </summary>
        public int CurrentTStateInFrame;


        protected SpectrumBase _machine;

        #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 Contention

        /// <summary>
        /// T-State at which to start applying contention
        /// </summary>
        public int contentionStartPeriod;
        /// <summary>
        /// T-State at which to end applying contention
        /// </summary>
        public int contentionEndPeriod;
        /// <summary>
        /// T-State memory contention delay mapping
        /// </summary>
        public byte[] contentionTable;

        /// <summary>
        /// Contention offset (used for testing)
        /// </summary>
        public int contentionOffset = 0; // -5;

        #endregion

        #region Screen Rendering

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

        /// <summary>
        /// Screen rendering T-State info
        /// </summary>
        public RenderCycle[] RenderTable;

        /// <summary>
        /// Display memory
        /// </summary>
        protected byte[] screen;
        /// <summary>
        /// Attribute memory lookup (mapped 1:1 to screen for convenience)
        /// </summary>
        protected short[] attr;
        /// <summary>
        /// T-State display mapping
        /// </summary>
        protected short[] tstateToDisp;
        /// <summary>
        /// Table that stores T-State to screen/attribute address values
        /// </summary>
        public short[] floatingBusTable;
        /// <summary>
        /// Cycle at which the last render update took place
        /// </summary>
        protected long lastTState;
        /// <summary>
        /// T-States elapsed since last render update
        /// </summary>
        protected long elapsedTStates;
        /// <summary>
        /// T-State of top left raster pixel
        /// </summary>
        protected int actualULAStart;
        /// <summary>
        /// Offset into display memory based on current T-State
        /// </summary>
        protected int screenByteCtr;
        /// <summary>
        /// Offset into current pixel of rasterizer
        /// </summary>
        protected int ULAByteCtr;
        /// <summary>
        /// The current border colour
        /// </summary>
        public int borderColour;
        /// <summary>
        /// Signs whether the colour flash is ON or OFF
        /// </summary>
        protected bool flashOn = false;


        protected int flashCounter;
        public int FlashCounter
        {
            get { return flashCounter; }
            set
            {
                flashCounter = value;
            }
        }


        /// <summary>
        /// Internal frame counter used for flasher operations
        /// </summary>
        protected int frameCounter = 0;

        /// <summary>
        /// Last 8-bit bitmap read from display memory
        /// (Floating bus implementation)
        /// </summary>
        protected int lastPixelValue;
        /// <summary>
        /// Last 8-bit attr val read from attribute memory
        /// (Floating bus implementation)
        /// </summary>
        protected int lastAttrValue;
        /// <summary>
        /// Last 8-bit bitmap read from display memory+1
        /// (Floating bus implementation)
        /// </summary>
        protected int lastPixelValuePlusOne; 
        /// <summary>
        /// Last 8-bit attr val read from attribute memory+1
        /// (Floating bus implementation)
        /// </summary>
        protected int lastAttrValuePlusOne;

        /// <summary>
        /// Used to create the non-border display area
        /// </summary>
        protected int TtateAtLeft;
        protected int TstateWidth;
        protected int TstateAtTop;
        protected int TstateHeight;
        protected int TstateAtRight;
        protected int TstateAtBottom;

        /// <summary>
        /// Total T-States in one scanline
        /// </summary>
        protected int TstatesPerScanline;
        /// <summary>
        /// Total pixels in one scanline
        /// </summary>
        protected int ScanLineWidth;
        /// <summary>
        /// Total chars in one PRINT row
        /// </summary>
        protected int CharRows;
        /// <summary>
        /// Total chars in one PRINT column
        /// </summary>
        protected int CharCols;
        /// <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>
        /// Memory address of display start
        /// </summary>
        protected int DisplayStart;
        /// <summary>
        /// Total number of bytes of display memory
        /// </summary>
        protected int DisplayLength;
        /// <summary>
        /// Memory address of attribute start
        /// </summary>
        protected int AttributeStart;
        /// <summary>
        /// Total number of bytes of attribute memory
        /// </summary>
        protected int AttributeLength;

        /// <summary>
        /// Raised when ULA has finished painting the entire screen
        /// </summary>
        public bool needsPaint = false;

        #endregion

        #region Interrupt

        /// <summary>
        /// The t-state within the frame that an interrupt is raised
        /// </summary>
        public int InterruptStart;

        /// <summary>
        /// The number of T-States that the INT pin is simulated to be held low
        /// </summary>
        public int InterruptDuration = 32;

        /// <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 < InterruptStart)
            {
                // interrupt does not need to be raised yet
                return;
            }

            if (currentCycle > InterruptStart + InterruptDuration)
            {
                // 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;

            // Signal the start of ULA processing
            if (_machine._render)
                ULAUpdateStart();

            CalcFlashCounter();
        }

        #endregion        

        #region Construction & Initialisation

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

        #endregion

        #region Methods

        /// <summary>
        /// Resets the ULA chip
        /// </summary>
        public abstract void Reset();

        /// <summary>
        /// Builds the contention table for the emulated model
        /// </summary>
        public abstract void BuildContentionTable();

        /// <summary>
        /// Returns true if the given memory address should be contended
        /// </summary>
        /// <param name="addr"></param>
        /// <returns></returns>
        public abstract bool IsContended(int addr);
       

        /// <summary>
        /// Resets render state once interrupt is generated
        /// </summary>
        public void ULAUpdateStart()
        {
            ULAByteCtr = 0;
            screenByteCtr = DisplayStart;
            lastTState = actualULAStart;
            needsPaint = true;
        }

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

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


        /// <summary>
        /// Builds the T-State to attribute map used with the floating bus
        /// </summary>
        public void BuildAttributeMap()
        {
            int start = DisplayStart;

            for (int f = 0; f < DisplayLength; f++, start++)
            {
                int addrH = start >> 8; //div by 256
                int addrL = start % 256;

                int pixelY = (addrH & 0x07);
                pixelY |= (addrL & (0xE0)) >> 2;
                pixelY |= (addrH & (0x18)) << 3;

                int attrIndex_Y = AttributeStart + ((pixelY >> 3) << 5);// pixel/8 * 32

                addrL = start % 256;
                int pixelX = addrL & (0x1F);

                attr[f] = (short)(attrIndex_Y + pixelX);
            }
        }

        /// <summary>
        /// Returns the floating bus value
        /// </summary>
        public virtual void ReadFloatingBus(ref int result)
        {
            // Floating bus is read on the previous cycle
            long _tStates = _machine.CurrentFrameCycle - 1;

            // if we are on the top or bottom border return 0xff
            if ((_tStates < contentionStartPeriod) || (_tStates > contentionEndPeriod))
            {
                result = 0xff;
            }
            else
            {
                if (floatingBusTable[_tStates] < 0)
                {
                    result = 0xff;
                }
                else
                {
                    result = _machine.ReadBus((ushort)floatingBusTable[_tStates]);
                }
            }
        }

        /// <summary>
        /// Updates the screen buffer based on the number of T-States supplied
        /// </summary>
        /// <param name="_tstates"></param>
        public virtual void UpdateScreenBuffer(long _tstates)
        {
            if (_tstates < actualULAStart)
            {
                return;
            }
            else if (_tstates >= FrameLength)
            {
                _tstates = FrameLength - 1;

                needsPaint = true;
            }

            //the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna
            elapsedTStates = (_tstates + 1 - lastTState);

            //It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state.

            long numBytes = (elapsedTStates >> 2) + ((elapsedTStates % 4) > 0 ? 1 : 0);

            int pixelData;
            int pixel2Data = 0xff;
            int attrData;
            int attr2Data;
            int bright;
            int ink;
            int paper;
            int flash;

            for (int i = 0; i < numBytes; i++)
            {
                if (tstateToDisp[lastTState] > 1)
                {
                    screenByteCtr = tstateToDisp[lastTState] - 16384; //adjust for actual screen offset

                    pixelData = _machine.FetchScreenMemory((ushort)screenByteCtr); //screen[screenByteCtr];
                    attrData = _machine.FetchScreenMemory((ushort)(attr[screenByteCtr] - 16384)); //screen[attr[screenByteCtr] - 16384];

                    lastPixelValue = pixelData;
                    lastAttrValue = attrData;
                    
                    bright = (attrData & 0x40) >> 3;
                    flash = (attrData & 0x80) >> 7;
                    ink = (attrData & 0x07);
                    paper = ((attrData >> 3) & 0x7);
                    int paletteInk = ULAPalette[ink + bright];
                    int palettePaper = ULAPalette[paper + bright];

                    if (flashOn && (flash != 0)) //swap paper and ink when flash is on
                    {
                        int temp = paletteInk;
                        paletteInk = palettePaper;
                        palettePaper = temp;
                    }
                    
                    for (int a = 0; a < 8; ++a)
                    {
                        if ((pixelData & 0x80) != 0)
                        {
                            ScreenBuffer[ULAByteCtr++] = paletteInk;
                            lastAttrValue = ink;
                            //pixelIsPaper = false;
                        }
                        else
                        {
                            ScreenBuffer[ULAByteCtr++] = palettePaper;
                            lastAttrValue = paper;
                        }
                        pixelData <<= 1;
                    }
                }
                else if (tstateToDisp[lastTState] == 1)
                {
                    int bor = ULAPalette[borderColour];
                    
                    for (int g = 0; g < 8; g++)
                        ScreenBuffer[ULAByteCtr++] = bor;
                }
                lastTState += 4;
            }
        }

        #endregion

        #region IVideoProvider

        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()
        {
            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 IStatable

        public void SyncState(Serializer ser)
        {
            ser.BeginSection("ULA");
            ser.Sync("ScreenBuffer", ref ScreenBuffer, false);
            ser.Sync("FrameLength", ref FrameLength);
            ser.Sync("ClockSpeed", ref ClockSpeed);
            ser.Sync("LateTiming", ref LateTiming);
            ser.Sync("borderColour", ref borderColour);
            ser.EndSection();
        }

        #endregion

        #region Attribution

        /*
         * Based on code from ArjunNair's Zero emulator (MIT Licensed)
         * https://github.com/ArjunNair/Zero-Emulator
        
        The MIT License (MIT)

        Copyright (c) 2009 Arjun Nair

        Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
        documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
        the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
        and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
        WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
        COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
        ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

        */
        /*
        #endregion
    }

    /// <summary>
    /// T-State display mapping
    /// </summary>
    public class RenderCycle
    {
        public RenderType Type { get; set; }
        public short DisplayAddress { get; set; }
        public short AttributeAddress { get; set; }
        public int ContentionValue { get; set; }
        public short FloatingBusAddress { get; set; }
    }

    public enum RenderType
    {
        None,
        Border,
        Display        
    }
    */
}
