#nullable disable

using System;
using System.Buffers;
using SharpCompress.Common;
using size_t = System.UInt32;

namespace SharpCompress.Compressors.Rar.UnpackV2017;

internal partial class FragmentedWindow
{
    public FragmentedWindow()
    {
        //memset(Mem,0,sizeof(Mem));
        //memset(MemSize,0,sizeof(MemSize));
    }

    //FragmentedWindow::~FragmentedWindow()
    //{
    //  Reset();
    //}

    public void Reset()
    {
        for (uint I = 0; I < Mem.Length; I++)
        {
            if (Mem[I] != null)
            {
                ArrayPool<byte>.Shared.Return(Mem[I]);
                Mem[I] = null;
            }
            MemSize[I] = 0;
        }
    }

    public void Init(size_t WinSize)
    {
        Reset();

        uint BlockNum = 0;
        size_t TotalSize = 0; // Already allocated.
        while (TotalSize < WinSize && BlockNum < Mem.Length)
        {
            var Size = WinSize - TotalSize; // Size needed to allocate.

            // Minimum still acceptable block size. Next allocations cannot be larger
            // than current, so we do not need blocks if they are smaller than
            // "size left / attempts left". Also we do not waste time to blocks
            // smaller than some arbitrary constant.
            var MinSize = Math.Max(Size / (size_t)(Mem.Length - BlockNum), 0x400000);

            byte[] NewMem = null;
            while (Size >= MinSize)
            {
                NewMem = ArrayPool<byte>.Shared.Rent(checked((int)Size));
                if (NewMem != null)
                {
                    break;
                }

                Size -= Size / 32;
            }

            // Clean the window to generate the same output when unpacking corrupt
            // RAR files, which may access to unused areas of sliding dictionary.
            // sharpcompress: don't need this, freshly allocated above
            //Utility.Memset(NewMem,0,Size);

            Mem[BlockNum] = NewMem ?? throw new ArchiveOperationException();
            TotalSize += Size;
            MemSize[BlockNum] = TotalSize;
            BlockNum++;
        }
        if (TotalSize < WinSize) // Not found enough free blocks.
        //throw std::bad_alloc();
        {
            throw new ArchiveOperationException();
        }
    }

    public byte this[size_t Item]
    {
        get
        {
            if (Item < MemSize[0])
            {
                return Mem[0][Item];
            }

            for (uint I = 1; I < MemSize.Length; I++)
            {
                if (Item < MemSize[I])
                {
                    return Mem[I][Item - MemSize[I - 1]];
                }
            }

            return Mem[0][0]; // Must never happen;
        }
        set
        {
            if (Item < MemSize[0])
            {
                Mem[0][Item] = value;
                return;
            }
            for (uint I = 1; I < MemSize.Length; I++)
            {
                if (Item < MemSize[I])
                {
                    Mem[I][Item - MemSize[I - 1]] = value;
                    return;
                }
            }

            Mem[0][0] = value; // Must never happen;
        }
    }

    // sharpcompress: added the following code
    public void GetBuffer(size_t Item, out byte[] buf, out uint offset)
    {
        if (Item < MemSize[0])
        {
            //return Mem[0][Item];
            buf = Mem[0];
            offset = Item;
            return;
        }
        for (uint I = 1; I < MemSize.Length; I++)
        {
            if (Item < MemSize[I])
            {
                //return Mem[I][Item-MemSize[I-1]];
                buf = Mem[I];
                offset = Item - MemSize[I - 1];
                return;
            }
        }
        //return Mem[0][0]; // Must never happen;
        buf = Mem[0];
        offset = 0;
        return; // Must never happen;
    }

    public void CopyString(uint Length, uint Distance, ref size_t UnpPtr, size_t MaxWinMask)
    {
        var SrcPtr = UnpPtr - Distance;
        while (Length-- > 0)
        {
            this[UnpPtr] = this[SrcPtr++ & MaxWinMask];
            // We need to have masked UnpPtr after quit from loop, so it must not
            // be replaced with '(*this)[UnpPtr++ & MaxWinMask]'
            UnpPtr = (UnpPtr + 1) & MaxWinMask;
        }
    }

    public void CopyData(byte[] Dest, size_t destOffset, size_t WinPos, size_t Size)
    {
        for (size_t I = 0; I < Size; I++)
        {
            Dest[destOffset + I] = this[WinPos + I];
        }
    }

    public size_t GetBlockSize(size_t StartPos, size_t RequiredSize)
    {
        for (uint I = 0; I < MemSize.Length; I++)
        {
            if (StartPos < MemSize[I])
            {
                return Math.Min(MemSize[I] - StartPos, RequiredSize);
            }
        }

        return 0; // Must never be here.
    }
}
