#nullable disable

using System;
using System.Text;

namespace SharpCompress.Compressors.PPMd.H
{
    internal class SubAllocator
    {
        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)
        {
            RarNode temp = _tempRarNode;
            temp.Address = p;
            temp.SetNext(_freeList[indx].GetNext());
            _freeList[indx].SetNext(temp);
        }

        public virtual void IncPText()
        {
            _pText++;
        }

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

        private int U2B(int nu)
        {
            return UNIT_SIZE * nu;
        }

        /* memblockptr */

        private int MbPtr(int basePtr, int items)
        {
            return (basePtr + U2B(items));
        }

        private void SplitBlock(int pv, int oldIndx, int newIndx)
        {
            int i, uDiff = _indx2Units[oldIndx] - _indx2Units[newIndx];
            int 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)
            {
                _subAllocatorSize = 0;

                //ArrayFactory.BYTES_FACTORY.recycle(heap);
                _heap = null;
                _heapStart = 1;

                // rarfree(HeapStart);
                // Free temp fields
                _tempRarNode = null;
                _tempRarMemBlock1 = null;
                _tempRarMemBlock2 = null;
                _tempRarMemBlock3 = null;
            }
        }

        public virtual int GetAllocatedMemory()
        {
            return _subAllocatorSize;
        }

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

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

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

            _heap = new byte[realAllocSize];
            _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()
        {
            RarMemBlock s0 = _tempRarMemBlock1;
            s0.Address = _tempMemBlockPos;
            RarMemBlock p = _tempRarMemBlock2;
            RarMemBlock 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);
                }
            }
            int i = indx;
            do
            {
                if (++i == N_INDEXES)
                {
                    _glueCount--;
                    i = U2B(_indx2Units[indx]);
                    int j = FIXED_UNIT_SIZE * _indx2Units[indx];
                    if (_fakeUnitsStart - _pText > j)
                    {
                        _fakeUnitsStart -= j;
                        _unitsStart -= i;
                        return _unitsStart;
                    }
                    return (0);
                }
            }
            while (_freeList[i].GetNext() == 0);
            int retVal = RemoveNode(i);
            SplitBlock(retVal, i, indx);
            return retVal;
        }

        public virtual int AllocUnits(int nu)
        {
            int indx = _units2Indx[nu - 1];
            if (_freeList[indx].GetNext() != 0)
            {
                return RemoveNode(indx);
            }
            int 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)
        {
            int i0 = _units2Indx[oldNu - 1];
            int i1 = _units2Indx[oldNu - 1 + 1];
            if (i0 == i1)
            {
                return oldPtr;
            }
            int 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 + ")");
            int i0 = _units2Indx[oldNu - 1];
            int i1 = _units2Indx[newNu - 1];
            if (i0 == i1)
            {
                return oldPtr;
            }
            if (_freeList[i1].GetNext() != 0)
            {
                int 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 = PText - dPText;
        }

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

            _pText = _heapStart;

            int size2 = FIXED_UNIT_SIZE * (_subAllocatorSize / 8 / FIXED_UNIT_SIZE * 7);
            int realSize2 = size2 / FIXED_UNIT_SIZE * UNIT_SIZE;
            int size1 = _subAllocatorSize - size2;
            int 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()
        {
            return _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()
        {
            StringBuilder 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);
        }
    }
}
