using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Nvdec.Vp9.Common;
using Ryujinx.Graphics.Video;
using System;
using System.Diagnostics;

namespace Ryujinx.Graphics.Nvdec.Vp9.Types
{
    internal struct Vp9Decoder
    {
        public Vp9Common Common;

        public int ReadyForNewData;

        public int RefreshFrameFlags;

        public int NeedResync; // Wait for key/intra-only frame.
        public int HoldRefBuf; // Hold the reference buffer.

        private static void DecreaseRefCount(int idx, Span<RefCntBuffer> frameBuffs, ref BufferPool pool)
        {
            if (idx >= 0 && frameBuffs[idx].RefCount > 0)
            {
                --frameBuffs[idx].RefCount;
                // A worker may only get a free framebuffer index when calling GetFreeFb.
                // But the private buffer is not set up until finish decoding header.
                // So any error happens during decoding header, the frame_bufs will not
                // have valid priv buffer.
                if (frameBuffs[idx].Released == 0 && frameBuffs[idx].RefCount == 0 &&
                    !frameBuffs[idx].RawFrameBuffer.Priv.IsNull)
                {
                    FrameBuffers.ReleaseFrameBuffer(pool.CbPriv, ref frameBuffs[idx].RawFrameBuffer);
                    frameBuffs[idx].Released = 1;
                }
            }
        }

        public void Create(MemoryAllocator allocator, ref BufferPool pool)
        {
            ref Vp9Common cm = ref Common;

            cm.CheckMemError(ref cm.Fc,
                new Ptr<Vp9EntropyProbs>(ref allocator.Allocate<Vp9EntropyProbs>(1)[0]));
            cm.CheckMemError(ref cm.FrameContexts,
                allocator.Allocate<Vp9EntropyProbs>(Constants.FrameContexts));

            Span<Array10<Array9<byte>>> cKfYModeProbSpan1 = cm.Fc.Value.KfYModeProb.AsSpan();
            
            for (int i = 0; i < EntropyMode.KfYModeProb.Length; i++)
            {
                Span<Array9<byte>> cKfYModeProbSpan2 = cKfYModeProbSpan1[i].AsSpan();
                
                for (int j = 0; j < EntropyMode.KfYModeProb[i].Length; j++)
                {
                    Span<byte> cKfYModeProbSpan3 = cKfYModeProbSpan2[j].AsSpan();
                    
                    for (int k = 0; k < EntropyMode.KfYModeProb[i][j].Length; k++)
                    {
                        cKfYModeProbSpan3[k] = EntropyMode.KfYModeProb[i][j][k];
                    }
                }
            }
            
            Span<Array9<byte>> cKfUvModeProbSpan1 = cm.Fc.Value.KfUvModeProb.AsSpan();

            for (int i = 0; i < EntropyMode.KfUvModeProb.Length; i++)
            {
                Span<byte> cKfUvModeProbSpan2 = cKfUvModeProbSpan1[i].AsSpan();
                
                for (int j = 0; j < EntropyMode.KfUvModeProb[i].Length; j++)
                {
                    cKfUvModeProbSpan2[j] = EntropyMode.KfUvModeProb[i][j];
                }
            }

            byte[][] kfPartitionProbs =
            [
                // 8x8 . 4x4
                [158, 97, 94], // a/l both not split
                [93, 24, 99], // a split, l not split
                [85, 119, 44], // l split, a not split
                [62, 59, 67], // a/l both split

                // 16x16 . 8x8
                [149, 53, 53], // a/l both not split
                [94, 20, 48], // a split, l not split
                [83, 53, 24], // l split, a not split
                [52, 18, 18], // a/l both split

                // 32x32 . 16x16
                [150, 40, 39], // a/l both not split
                [78, 12, 26], // a split, l not split
                [67, 33, 11], // l split, a not split
                [24, 7, 5], // a/l both split

                // 64x64 . 32x32
                [174, 35, 49], // a/l both not split
                [68, 11, 27], // a split, l not split
                [57, 15, 9], // l split, a not split
                [12, 3, 3] // a/l both split
            ];
            
            Span<Array3<byte>> cKfPartitionProbSpan1 = cm.Fc.Value.KfPartitionProb.AsSpan();

            for (int i = 0; i < kfPartitionProbs.Length; i++)
            {
                Span<byte> cKfPartitionProbSpan2 = cKfPartitionProbSpan1[i].AsSpan();
                
                for (int j = 0; j < kfPartitionProbs[i].Length; j++)
                {
                    cKfPartitionProbSpan2[j] = kfPartitionProbs[i][j];
                }
            }

            cm.Counts = new Ptr<Vp9BackwardUpdates>(ref allocator.Allocate<Vp9BackwardUpdates>(1)[0]);

            NeedResync = 1;

            Span<int> refFrameMapSpan = cm.RefFrameMap.AsSpan();
            Span<int> nextRefFrameMapSpan = cm.NextRefFrameMap.AsSpan();

            // Initialize the references to not point to any frame buffers.
            for (int i = 0; i < 8; i++)
            {
                refFrameMapSpan[i] = -1;
                nextRefFrameMapSpan[i] = -1;
            }

            cm.CurrentVideoFrame = 0;
            ReadyForNewData = 1;
            Common.BufferPool = new Ptr<BufferPool>(ref pool);

            cm.BitDepth = BitDepth.Bits8;
            cm.DequantBitDepth = BitDepth.Bits8;

            // vp9_loop_filter_init(ref cm);
        }

        /* If any buffer updating is signaled it should be done here. */
        private void SwapFrameBuffers()
        {
            int refIndex = 0, mask;
            ref Vp9Common cm = ref Common;
            ref BufferPool pool = ref cm.BufferPool.Value;
            Span<RefCntBuffer> frameBufs = cm.BufferPool.Value.FrameBufs.AsSpan();
            
            Span<int> refFrameMapSpan = cm.RefFrameMap.AsSpan();
            Span<int> nextRefFrameMapSpan = cm.NextRefFrameMap.AsSpan();
            Span<RefBuffer> frameRefsSpan = cm.FrameRefs.AsSpan();

            for (mask = RefreshFrameFlags; mask != 0; mask >>= 1)
            {
                int oldIdx = refFrameMapSpan[refIndex];
                // Current thread releases the holding of reference frame.
                DecreaseRefCount(oldIdx, frameBufs, ref pool);

                // Release the reference frame in reference map.
                if ((mask & 1) != 0)
                {
                    DecreaseRefCount(oldIdx, frameBufs, ref pool);
                }

                refFrameMapSpan[refIndex] = nextRefFrameMapSpan[refIndex];
                ++refIndex;
            }

            // Current thread releases the holding of reference frame.
            for (; refIndex < Constants.RefFrames && cm.ShowExistingFrame == 0; ++refIndex)
            {
                int oldIdx = refFrameMapSpan[refIndex];
                DecreaseRefCount(oldIdx, frameBufs, ref pool);
                refFrameMapSpan[refIndex] = nextRefFrameMapSpan[refIndex];
            }

            HoldRefBuf = 0;
            cm.FrameToShow = new Ptr<Surface>(ref cm.GetFrameNewBuffer());

            --frameBufs[cm.NewFbIdx].RefCount;

            // Invalidate these references until the next frame starts.
            for (refIndex = 0; refIndex < 3; refIndex++)
            {
                frameRefsSpan[refIndex].Idx = RefBuffer.InvalidIdx;
            }
        }

        public CodecErr ReceiveCompressedData(MemoryAllocator allocator, ulong size, ref ArrayPtr<byte> psource)
        {
            ref Vp9Common cm = ref Common;
            ref BufferPool pool = ref cm.BufferPool.Value;
            Span<RefCntBuffer> frameBufs = cm.BufferPool.Value.FrameBufs.AsSpan();
            ArrayPtr<byte> source = psource;
            CodecErr retcode = 0;
            cm.Error.ErrorCode = CodecErr.Ok;

            if (size == 0)
            {
                // This is used to signal that we are missing frames.
                // We do not know if the missing frame(s) was supposed to update
                // any of the reference buffers, but we act conservative and
                // mark only the last buffer as corrupted.
                
                Span<RefBuffer> frameRefsSpan = cm.FrameRefs.AsSpan();

                if (frameRefsSpan[0].Idx > 0)
                {
                    frameRefsSpan[0].Buf.Corrupted = 1;
                }
            }

            ReadyForNewData = 0;

            // Check if the previous frame was a frame without any references to it.
            if (cm.NewFbIdx >= 0 && frameBufs[cm.NewFbIdx].RefCount == 0 &&
                frameBufs[cm.NewFbIdx].Released == 0)
            {
                FrameBuffers.ReleaseFrameBuffer(pool.CbPriv, ref frameBufs[cm.NewFbIdx].RawFrameBuffer);
                frameBufs[cm.NewFbIdx].Released = 1;
            }

            // Find a free frame buffer. Return error if can not find any.
            cm.NewFbIdx = cm.GetFreeFb();
            if (cm.NewFbIdx == RefBuffer.InvalidIdx)
            {
                ReadyForNewData = 1;
                cm.Error.InternalError(CodecErr.MemError, "Unable to find free frame buffer");

                return cm.Error.ErrorCode;
            }

            // Assign a MV array to the frame buffer.
            cm.CurFrame = new Ptr<RefCntBuffer>(ref pool.FrameBufs[cm.NewFbIdx]);

            HoldRefBuf = 0;

            DecodeFrame.Decode(allocator, ref this, new ArrayPtr<byte>(ref source[0], (int)size), out psource);

            SwapFrameBuffers();

            // vpx_clear_system_state();

            if (cm.ShowExistingFrame == 0)
            {
                cm.LastShowFrame = cm.ShowFrame;
                cm.PrevFrame = cm.CurFrame;

                if (cm.PrevFrameMvs.IsNull || cm.PrevFrameMvs.Length != cm.CurFrameMvs.Length)
                {
                    allocator.Free(cm.PrevFrameMvs);
                    cm.PrevFrameMvs = allocator.Allocate<MvRef>(cm.CurFrameMvs.Length);
                }

                cm.CurFrameMvs.AsSpan().CopyTo(cm.PrevFrameMvs.AsSpan());
                if (cm.Seg.Enabled)
                {
                    cm.SwapCurrentAndLastSegMap();
                }
            }

            if (cm.ShowFrame != 0)
            {
                cm.CurShowFrameFbIdx = cm.NewFbIdx;
            }

            // Update progress in frame parallel decode.
            cm.LastWidth = cm.Width;
            cm.LastHeight = cm.Height;
            if (cm.ShowFrame != 0)
            {
                cm.CurrentVideoFrame++;
            }

            return retcode;
        }

        public int GetRawFrame(ref Surface sd)
        {
            ref Vp9Common cm = ref Common;
            int ret = -1;

            if (ReadyForNewData == 1)
            {
                return ret;
            }

            ReadyForNewData = 1;

            if (cm.ShowFrame == 0)
            {
                return ret;
            }

            ReadyForNewData = 1;

            sd = cm.FrameToShow.Value;
            ret = 0;

            return ret;
        }

        public CodecErr Decode(MemoryAllocator allocator, ArrayPtr<byte> data)
        {
            ArrayPtr<byte> dataStart = data;
            CodecErr res;
            Array8<uint> frameSizes = new();
            Span<uint> frameSizesSpan = frameSizes.AsSpan();
            
            res = Decoder.ParseSuperframeIndex(data, (ulong)data.Length, frameSizesSpan, out int frameCount);
            if (res != CodecErr.Ok)
            {
                return res;
            }

            // Decode in serial mode.
            if (frameCount > 0)
            {
                for (int i = 0; i < frameCount; ++i)
                {
                    ArrayPtr<byte> dataStartCopy = dataStart;
                    uint frameSize = frameSizesSpan[i];
                    if (frameSize > (uint)dataStart.Length)
                    {
                        return CodecErr.CorruptFrame;
                    }

                    res = ReceiveCompressedData(allocator, frameSize, ref dataStartCopy);
                    if (res != CodecErr.Ok)
                    {
                        return res;
                    }

                    dataStart = dataStart.Slice((int)frameSize);
                }
            }
            else
            {
                while (dataStart.Length != 0)
                {
                    uint frameSize = (uint)dataStart.Length;
                    res = ReceiveCompressedData(allocator, frameSize, ref dataStart);
                    if (res != CodecErr.Ok)
                    {
                        return res;
                    }

                    // Account for suboptimal termination by the encoder.
                    while (dataStart.Length != 0)
                    {
                        byte marker = Decoder.ReadMarker(dataStart);
                        if (marker != 0)
                        {
                            break;
                        }

                        dataStart = dataStart.Slice(1);
                    }
                }
            }

            return res;
        }
    }

    internal static class Decoder
    {
        public static byte ReadMarker(ArrayPtr<byte> data)
        {
            return data[0];
        }

        public static CodecErr ParseSuperframeIndex(ArrayPtr<byte> data, ulong dataSz, Span<uint> sizes, out int count)
        {
            // A chunk ending with a byte matching 0xc0 is an invalid chunk unless
            // it is a super frame index. If the last byte of real video compression
            // data is 0xc0 the encoder must add a 0 byte. If we have the marker but
            // not the associated matching marker byte at the front of the index we have
            // an invalid bitstream and need to return an error.

            byte marker;

            Debug.Assert(dataSz != 0);
            marker = ReadMarker(data.Slice((int)dataSz - 1));
            count = 0;

            if ((marker & 0xe0) == 0xc0)
            {
                uint frames = (uint)(marker & 0x7) + 1;
                uint mag = (uint)((marker >> 3) & 0x3) + 1;
                ulong indexSz = 2 + (mag * frames);

                // This chunk is marked as having a superframe index but doesn't have
                // enough data for it, thus it's an invalid superframe index.
                if (dataSz < indexSz)
                {
                    return CodecErr.CorruptFrame;
                }

                {
                    byte marker2 = ReadMarker(data.Slice((int)(dataSz - indexSz)));

                    // This chunk is marked as having a superframe index but doesn't have
                    // the matching marker byte at the front of the index therefore it's an
                    // invalid chunk.
                    if (marker != marker2)
                    {
                        return CodecErr.CorruptFrame;
                    }
                }

                {
                    // Found a valid superframe index.
                    ArrayPtr<byte> x = data.Slice((int)(dataSz - indexSz + 1));

                    for (int i = 0; i < frames; ++i)
                    {
                        uint thisSz = 0;

                        for (int j = 0; j < mag; ++j)
                        {
                            thisSz |= (uint)x[0] << j * 8;
                            x = x.Slice(1);
                        }

                        sizes[i] = thisSz;
                    }

                    count = (int)frames;
                }
            }

            return CodecErr.Ok;
        }
    }
}
