#nullable disable

using System;
using System.Buffers;
using System.Text;

namespace SharpCompress.Compressors.PPMd.H;

internal class SubAllocator : IDisposable
{
    public virtual int FakeUnitsStart
    {
        get => _fakeUnitsStart;
        set => _fakeUnitsStart = value;
    }

    public virtual int HeapEnd => _heapEnd;

    public virtual int PText
    {
        get => _pText;
        set => _pText = value;
    }

    public virtual int UnitsStart
    {
        get => _unitsStart;
        set => _unitsStart = value;
    }

    public virtual byte[] Heap => _heap;

    //UPGRADE_NOTE: Final was removed from the declaration of 'N4 '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
    public const int N1 = 4;
    public const int N2 = 4;
    public const int N3 = 4;
    public static readonly int N4 = (128 + 3 - (1 * N1) - (2 * N2) - (3 * N3)) / 4;

    //UPGRADE_NOTE: Final was removed from the declaration of 'N_INDEXES '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
    public static readonly int N_INDEXES = N1 + N2 + N3 + N4;

    //UPGRADE_NOTE: Final was removed from the declaration of 'UNIT_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
    //UPGRADE_NOTE: The initialization of  'UNIT_SIZE' was moved to static method 'SharpCompress.Unpack.PPM.SubAllocator'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
    public static readonly int UNIT_SIZE;

    public const int FIXED_UNIT_SIZE = 12;

    private int _subAllocatorSize;

    // byte Indx2Units[N_INDEXES], Units2Indx[128], GlueCount;
    private readonly int[] _indx2Units = new int[N_INDEXES];
    private readonly int[] _units2Indx = new int[128];
    private int _glueCount;

    // byte *HeapStart,*LoUnit, *HiUnit;
    private int _heapStart,
        _loUnit,
        _hiUnit;

    //UPGRADE_NOTE: Final was removed from the declaration of 'freeList '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
    private readonly RarNode[] _freeList = new RarNode[N_INDEXES];

    // byte *pText, *UnitsStart,*HeapEnd,*FakeUnitsStart;
    private int _pText,
        _unitsStart,
        _heapEnd,
        _fakeUnitsStart;

    private byte[] _heap;

    private int _freeListPos;

    private int _tempMemBlockPos;

    // Temp fields
    private RarNode _tempRarNode;
    private RarMemBlock _tempRarMemBlock1;
    private RarMemBlock _tempRarMemBlock2;
    private RarMemBlock _tempRarMemBlock3;

    public SubAllocator() => Clean();

    public virtual void Clean() => _subAllocatorSize = 0;

    private void InsertNode(int p, int indx)
    {
        var temp = _tempRarNode;
        temp.Address = p;
        temp.SetNext(_freeList[indx].GetNext());
        _freeList[indx].SetNext(temp);
    }

    public virtual void IncPText() => _pText++;

    private int RemoveNode(int indx)
    {
        var retVal = _freeList[indx].GetNext();
        var temp = _tempRarNode;
        temp.Address = retVal;
        _freeList[indx].SetNext(temp.GetNext());
        return retVal;
    }

    private int U2B(int nu) => UNIT_SIZE * nu;

    /* memblockptr */

    private int MbPtr(int basePtr, int items) => (basePtr + U2B(items));

    private void SplitBlock(int pv, int oldIndx, int newIndx)
    {
        int i,
            uDiff = _indx2Units[oldIndx] - _indx2Units[newIndx];
        var p = pv + U2B(_indx2Units[newIndx]);
        if (_indx2Units[i = _units2Indx[uDiff - 1]] != uDiff)
        {
            InsertNode(p, --i);
            p += U2B(i = _indx2Units[i]);
            uDiff -= i;
        }
        InsertNode(p, _units2Indx[uDiff - 1]);
    }

    public virtual void StopSubAllocator()
    {
        if (_subAllocatorSize != 0)
        {
            var heap = _heap;
            _subAllocatorSize = 0;
            _heap = null;
            _heapStart = 1;

            // rarfree(HeapStart);
            for (var i = 0; i < _freeList.Length; i++)
            {
                _freeList[i] = null;
            }

            // Free temp fields
            _tempRarNode = null;
            _tempRarMemBlock1 = null;
            _tempRarMemBlock2 = null;
            _tempRarMemBlock3 = null;

            if (heap is not null)
            {
                ArrayPool<byte>.Shared.Return(heap, clearArray: true);
            }
        }
    }

    public void Dispose() => StopSubAllocator();

    public virtual int GetAllocatedMemory() => _subAllocatorSize;

    public virtual bool StartSubAllocator(int saSize)
    {
        var t = saSize;
        if (_subAllocatorSize == t)
        {
            return true;
        }
        StopSubAllocator();
        var allocSize = (t / FIXED_UNIT_SIZE * UNIT_SIZE) + UNIT_SIZE;

        // adding space for freelist (needed for poiters)
        // 1+ for null pointer
        var realAllocSize = 1 + allocSize + (4 * N_INDEXES);

        // adding space for an additional memblock
        _tempMemBlockPos = realAllocSize;
        realAllocSize += RarMemBlock.SIZE;

        _heap = ArrayPool<byte>.Shared.Rent(realAllocSize);
        new Span<byte>(_heap, 0, realAllocSize).Clear();
        _heapStart = 1;
        _heapEnd = _heapStart + allocSize - UNIT_SIZE;
        _subAllocatorSize = t;

        // Bug fixed
        _freeListPos = _heapStart + allocSize;

        //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'"
        //assert(realAllocSize - tempMemBlockPos == RarMemBlock.size): realAllocSize
        //+   + tempMemBlockPos +   + RarMemBlock.size;

        // Init freeList
        for (int i = 0, pos = _freeListPos; i < _freeList.Length; i++, pos += RarNode.SIZE)
        {
            _freeList[i] = new RarNode(_heap);
            _freeList[i].Address = pos;
        }

        // Init temp fields
        _tempRarNode = new RarNode(_heap);
        _tempRarMemBlock1 = new RarMemBlock(_heap);
        _tempRarMemBlock2 = new RarMemBlock(_heap);
        _tempRarMemBlock3 = new RarMemBlock(_heap);

        return true;
    }

    private void GlueFreeBlocks()
    {
        var s0 = _tempRarMemBlock1;
        s0.Address = _tempMemBlockPos;
        var p = _tempRarMemBlock2;
        var p1 = _tempRarMemBlock3;
        int i,
            k,
            sz;
        if (_loUnit != _hiUnit)
        {
            _heap[_loUnit] = 0;
        }
        for (i = 0, s0.SetPrev(s0), s0.SetNext(s0); i < N_INDEXES; i++)
        {
            while (_freeList[i].GetNext() != 0)
            {
                p.Address = RemoveNode(i); // =(RAR_MEM_BLK*)RemoveNode(i);
                p.InsertAt(s0); // p->insertAt(&s0);
                p.Stamp = 0xFFFF; // p->Stamp=0xFFFF;
                p.SetNu(_indx2Units[i]); // p->NU=Indx2Units[i];
            }
        }
        for (p.Address = s0.GetNext(); p.Address != s0.Address; p.Address = p.GetNext())
        {
            // while ((p1=MBPtr(p,p->NU))->Stamp == 0xFFFF && int(p->NU)+p1->NU
            // < 0x10000)
            // Bug fixed
            p1.Address = MbPtr(p.Address, p.GetNu());
            while (p1.Stamp == 0xFFFF && p.GetNu() + p1.GetNu() < 0x10000)
            {
                p1.Remove();
                p.SetNu(p.GetNu() + p1.GetNu()); // ->NU += p1->NU;
                p1.Address = MbPtr(p.Address, p.GetNu());
            }
        }

        // while ((p=s0.next) != &s0)
        // Bug fixed
        p.Address = s0.GetNext();
        while (p.Address != s0.Address)
        {
            for (p.Remove(), sz = p.GetNu(); sz > 128; sz -= 128, p.Address = MbPtr(p.Address, 128))
            {
                InsertNode(p.Address, N_INDEXES - 1);
            }
            if (_indx2Units[i = _units2Indx[sz - 1]] != sz)
            {
                k = sz - _indx2Units[--i];
                InsertNode(MbPtr(p.Address, sz - k), k - 1);
            }
            InsertNode(p.Address, i);
            p.Address = s0.GetNext();
        }
    }

    private int AllocUnitsRare(int indx)
    {
        if (_glueCount == 0)
        {
            _glueCount = 255;
            GlueFreeBlocks();
            if (_freeList[indx].GetNext() != 0)
            {
                return RemoveNode(indx);
            }
        }
        var i = indx;
        do
        {
            if (++i == N_INDEXES)
            {
                _glueCount--;
                i = U2B(_indx2Units[indx]);
                var j = FIXED_UNIT_SIZE * _indx2Units[indx];
                if (_fakeUnitsStart - _pText > j)
                {
                    _fakeUnitsStart -= j;
                    _unitsStart -= i;
                    return _unitsStart;
                }
                return (0);
            }
        } while (_freeList[i].GetNext() == 0);
        var retVal = RemoveNode(i);
        SplitBlock(retVal, i, indx);
        return retVal;
    }

    public virtual int AllocUnits(int nu)
    {
        var indx = _units2Indx[nu - 1];
        if (_freeList[indx].GetNext() != 0)
        {
            return RemoveNode(indx);
        }
        var retVal = _loUnit;
        _loUnit += U2B(_indx2Units[indx]);
        if (_loUnit <= _hiUnit)
        {
            return retVal;
        }
        _loUnit -= U2B(_indx2Units[indx]);
        return AllocUnitsRare(indx);
    }

    public virtual int AllocContext()
    {
        if (_hiUnit != _loUnit)
        {
            return (_hiUnit -= UNIT_SIZE);
        }
        if (_freeList[0].GetNext() != 0)
        {
            return RemoveNode(0);
        }
        return AllocUnitsRare(0);
    }

    public virtual int ExpandUnits(int oldPtr, int oldNu)
    {
        var i0 = _units2Indx[oldNu - 1];
        var i1 = _units2Indx[oldNu - 1 + 1];
        if (i0 == i1)
        {
            return oldPtr;
        }
        var ptr = AllocUnits(oldNu + 1);
        if (ptr != 0)
        {
            // memcpy(ptr,OldPtr,U2B(OldNU));
            Array.Copy(_heap, oldPtr, _heap, ptr, U2B(oldNu));
            InsertNode(oldPtr, i0);
        }
        return ptr;
    }

    public virtual int ShrinkUnits(int oldPtr, int oldNu, int newNu)
    {
        // System.out.println("SubAllocator.shrinkUnits(" + OldPtr + ", " +
        // OldNU + ", " + NewNU + ")");
        var i0 = _units2Indx[oldNu - 1];
        var i1 = _units2Indx[newNu - 1];
        if (i0 == i1)
        {
            return oldPtr;
        }
        if (_freeList[i1].GetNext() != 0)
        {
            var ptr = RemoveNode(i1);

            // memcpy(ptr,OldPtr,U2B(NewNU));
            // for (int i = 0; i < U2B(NewNU); i++) {
            // heap[ptr + i] = heap[OldPtr + i];
            // }
            Array.Copy(_heap, oldPtr, _heap, ptr, U2B(newNu));
            InsertNode(oldPtr, i0);
            return ptr;
        }
        SplitBlock(oldPtr, i0, i1);
        return oldPtr;
    }

    public virtual void FreeUnits(int ptr, int oldNu) => InsertNode(ptr, _units2Indx[oldNu - 1]);

    public virtual void DecPText(int dPText) => PText -= dPText;

    public virtual void InitSubAllocator()
    {
        int i,
            k;
        new Span<byte>(_heap, _freeListPos, SizeOfFreeList()).Clear();

        _pText = _heapStart;

        var size2 = FIXED_UNIT_SIZE * (_subAllocatorSize / 8 / FIXED_UNIT_SIZE * 7);
        var realSize2 = size2 / FIXED_UNIT_SIZE * UNIT_SIZE;
        var size1 = _subAllocatorSize - size2;
        var realSize1 = (size1 / FIXED_UNIT_SIZE * UNIT_SIZE) + (size1 % FIXED_UNIT_SIZE);
        _hiUnit = _heapStart + _subAllocatorSize;
        _loUnit = _unitsStart = _heapStart + realSize1;
        _fakeUnitsStart = _heapStart + size1;
        _hiUnit = _loUnit + realSize2;

        for (i = 0, k = 1; i < N1; i++, k += 1)
        {
            _indx2Units[i] = k & 0xff;
        }
        for (k++; i < N1 + N2; i++, k += 2)
        {
            _indx2Units[i] = k & 0xff;
        }
        for (k++; i < N1 + N2 + N3; i++, k += 3)
        {
            _indx2Units[i] = k & 0xff;
        }
        for (k++; i < (N1 + N2 + N3 + N4); i++, k += 4)
        {
            _indx2Units[i] = k & 0xff;
        }

        for (_glueCount = 0, k = 0, i = 0; k < 128; k++)
        {
            i += ((_indx2Units[i] < (k + 1)) ? 1 : 0);
            _units2Indx[k] = i & 0xff;
        }
    }

    private int SizeOfFreeList() => _freeList.Length * RarNode.SIZE;

    // Debug
    // public void dumpHeap() {
    // File file = new File("P:\\test\\heapdumpj");
    // OutputStream out = null;
    // try {
    // out = new FileOutputStream(file);
    // out.write(heap, heapStart, heapEnd - heapStart);
    // out.flush();
    // System.out.println("Heap dumped to " + file.getAbsolutePath());
    // }
    // catch (IOException e) {
    // e.printStackTrace();
    // }
    // finally {
    // FileUtil.close(out);
    // }
    // }

    // Debug
    public override string ToString()
    {
        var buffer = new StringBuilder();
        buffer.Append("SubAllocator[");
        buffer.Append("\n  subAllocatorSize=");
        buffer.Append(_subAllocatorSize);
        buffer.Append("\n  glueCount=");
        buffer.Append(_glueCount);
        buffer.Append("\n  heapStart=");
        buffer.Append(_heapStart);
        buffer.Append("\n  loUnit=");
        buffer.Append(_loUnit);
        buffer.Append("\n  hiUnit=");
        buffer.Append(_hiUnit);
        buffer.Append("\n  pText=");
        buffer.Append(_pText);
        buffer.Append("\n  unitsStart=");
        buffer.Append(_unitsStart);
        buffer.Append("\n]");
        return buffer.ToString();
    }

    static SubAllocator() => UNIT_SIZE = Math.Max(PpmContext.SIZE, RarMemBlock.SIZE);
}
