using System;
using System.Numerics;

namespace Ryujinx.Graphics.Texture.Astc
{
    internal struct IntegerEncoded
    {
        internal const int StructSize = 8;
        private static readonly IntegerEncoded[] Encodings;

        public enum EIntegerEncoding : byte
        {
            JustBits,
            Quint,
            Trit
        }

        EIntegerEncoding _encoding;
        public byte NumberBits { get; private set; }
        public byte TritValue { get; private set; }
        public byte QuintValue { get; private set; }
        public int BitValue { get; private set; }

        static IntegerEncoded()
        {
            Encodings = new IntegerEncoded[0x100];

            for (int i = 0; i < Encodings.Length; i++)
            {
                Encodings[i] = CreateEncodingCalc(i);
            }
        }

        public IntegerEncoded(EIntegerEncoding encoding, int numBits)
        {
            _encoding = encoding;
            NumberBits = (byte)numBits;
            BitValue = 0;
            TritValue = 0;
            QuintValue = 0;
        }

        public bool MatchesEncoding(IntegerEncoded other)
        {
            return _encoding == other._encoding && NumberBits == other.NumberBits;
        }

        public EIntegerEncoding GetEncoding()
        {
            return _encoding;
        }

        public int GetBitLength(int numberVals)
        {
            int totalBits = NumberBits * numberVals;
            if (_encoding == EIntegerEncoding.Trit)
            {
                totalBits += (numberVals * 8 + 4) / 5;
            }
            else if (_encoding == EIntegerEncoding.Quint)
            {
                totalBits += (numberVals * 7 + 2) / 3;
            }
            return totalBits;
        }

        public static IntegerEncoded CreateEncoding(int maxVal)
        {
            return Encodings[maxVal];
        }

        private static IntegerEncoded CreateEncodingCalc(int maxVal)
        {
            while (maxVal > 0)
            {
                int check = maxVal + 1;

                // Is maxVal a power of two?
                if ((check & (check - 1)) == 0)
                {
                    return new IntegerEncoded(EIntegerEncoding.JustBits, BitOperations.PopCount((uint)maxVal));
                }

                // Is maxVal of the type 3*2^n - 1?
                if ((check % 3 == 0) && ((check / 3) & ((check / 3) - 1)) == 0)
                {
                    return new IntegerEncoded(EIntegerEncoding.Trit, BitOperations.PopCount((uint)(check / 3 - 1)));
                }

                // Is maxVal of the type 5*2^n - 1?
                if ((check % 5 == 0) && ((check / 5) & ((check / 5) - 1)) == 0)
                {
                    return new IntegerEncoded(EIntegerEncoding.Quint, BitOperations.PopCount((uint)(check / 5 - 1)));
                }

                // Apparently it can't be represented with a bounded integer sequence...
                // just iterate.
                maxVal--;
            }

            return new IntegerEncoded(EIntegerEncoding.JustBits, 0);
        }

        public static void DecodeTritBlock(
            ref BitStream128 bitStream,
            ref IntegerSequence listIntegerEncoded,
            int numberBitsPerValue)
        {
            // Implement the algorithm in section C.2.12
            Span<int> m = stackalloc int[5];

            m[0] = bitStream.ReadBits(numberBitsPerValue);
            int encoded = bitStream.ReadBits(2);
            m[1] = bitStream.ReadBits(numberBitsPerValue);
            encoded |= bitStream.ReadBits(2) << 2;
            m[2] = bitStream.ReadBits(numberBitsPerValue);
            encoded |= bitStream.ReadBits(1) << 4;
            m[3] = bitStream.ReadBits(numberBitsPerValue);
            encoded |= bitStream.ReadBits(2) << 5;
            m[4] = bitStream.ReadBits(numberBitsPerValue);
            encoded |= bitStream.ReadBits(1) << 7;

            ReadOnlySpan<byte> encodings = GetTritEncoding(encoded);

            IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Trit, numberBitsPerValue);

            for (int i = 0; i < 5; i++)
            {
                intEncoded.BitValue = m[i];
                intEncoded.TritValue = encodings[i];

                listIntegerEncoded.Add(ref intEncoded);
            }
        }

        public static void DecodeQuintBlock(
            ref BitStream128 bitStream,
            ref IntegerSequence listIntegerEncoded,
            int numberBitsPerValue)
        {
            ReadOnlySpan<byte> interleavedBits = new byte[] { 3, 2, 2 };

            // Implement the algorithm in section C.2.12
            Span<int> m = stackalloc int[3];
            ulong encoded = 0;
            int encodedBitsRead = 0;

            for (int i = 0; i < m.Length; i++)
            {
                m[i] = bitStream.ReadBits(numberBitsPerValue);

                uint encodedBits = (uint)bitStream.ReadBits(interleavedBits[i]);

                encoded |= encodedBits << encodedBitsRead;
                encodedBitsRead += interleavedBits[i];
            }

            ReadOnlySpan<byte> encodings = GetQuintEncoding((int)encoded);

            for (int i = 0; i < 3; i++)
            {
                IntegerEncoded intEncoded = new IntegerEncoded(EIntegerEncoding.Quint, numberBitsPerValue)
                {
                    BitValue = m[i],
                    QuintValue = encodings[i]
                };

                listIntegerEncoded.Add(ref intEncoded);
            }
        }

        public static void DecodeIntegerSequence(
            ref IntegerSequence decodeIntegerSequence,
            ref BitStream128 bitStream,
            int maxRange,
            int numberValues)
        {
            // Determine encoding parameters
            IntegerEncoded intEncoded = CreateEncoding(maxRange);

            // Start decoding
            int numberValuesDecoded = 0;
            while (numberValuesDecoded < numberValues)
            {
                switch (intEncoded.GetEncoding())
                {
                    case EIntegerEncoding.Quint:
                    {
                        DecodeQuintBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits);
                        numberValuesDecoded += 3;

                        break;
                    }

                    case EIntegerEncoding.Trit:
                    {
                        DecodeTritBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits);
                        numberValuesDecoded += 5;

                        break;
                    }

                    case EIntegerEncoding.JustBits:
                    {
                        intEncoded.BitValue = bitStream.ReadBits(intEncoded.NumberBits);
                        decodeIntegerSequence.Add(ref intEncoded);
                        numberValuesDecoded++;

                        break;
                    }
                }
            }
        }

        private static ReadOnlySpan<byte> GetTritEncoding(int index)
        {
            return TritEncodings.Slice(index * 5, 5);
        }

        private static ReadOnlySpan<byte> GetQuintEncoding(int index)
        {
            return QuintEncodings.Slice(index * 3, 3);
        }

        private static ReadOnlySpan<byte> TritEncodings => new byte[]
        {
            0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
            0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0,
            2, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 2, 0, 0, 0,
            1, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 2, 0, 0,
            0, 2, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0, 0,
            2, 0, 2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
            2, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 1, 0, 0,
            1, 1, 1, 0, 0, 2, 1, 1, 0, 0, 1, 1, 2, 0, 0,
            0, 2, 1, 0, 0, 1, 2, 1, 0, 0, 2, 2, 1, 0, 0,
            2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 2, 2,
            2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 1, 0,
            1, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 2, 1, 0,
            0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0,
            1, 0, 2, 1, 0, 0, 2, 0, 1, 0, 1, 2, 0, 1, 0,
            2, 2, 0, 1, 0, 2, 0, 2, 1, 0, 0, 2, 2, 1, 0,
            1, 2, 2, 1, 0, 2, 2, 2, 1, 0, 2, 0, 2, 1, 0,
            0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 2, 0, 1, 1, 0,
            0, 1, 2, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0,
            2, 1, 1, 1, 0, 1, 1, 2, 1, 0, 0, 2, 1, 1, 0,
            1, 2, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 2, 1, 0,
            0, 1, 0, 2, 2, 1, 1, 0, 2, 2, 2, 1, 0, 2, 2,
            1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 0, 0, 2, 0,
            2, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 0, 2, 0,
            1, 1, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 2, 0,
            0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 2, 0, 2, 0,
            2, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 2, 2, 0,
            2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 1, 2, 0,
            1, 0, 1, 2, 0, 2, 0, 1, 2, 0, 0, 1, 2, 2, 0,
            0, 1, 1, 2, 0, 1, 1, 1, 2, 0, 2, 1, 1, 2, 0,
            1, 1, 2, 2, 0, 0, 2, 1, 2, 0, 1, 2, 1, 2, 0,
            2, 2, 1, 2, 0, 2, 1, 2, 2, 0, 0, 2, 0, 2, 2,
            1, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2,
            0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2,
            0, 0, 2, 0, 2, 0, 1, 0, 0, 2, 1, 1, 0, 0, 2,
            2, 1, 0, 0, 2, 1, 0, 2, 0, 2, 0, 2, 0, 0, 2,
            1, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 0, 2, 0, 2,
            0, 2, 2, 0, 2, 1, 2, 2, 0, 2, 2, 2, 2, 0, 2,
            2, 0, 2, 0, 2, 0, 0, 1, 0, 2, 1, 0, 1, 0, 2,
            2, 0, 1, 0, 2, 0, 1, 2, 0, 2, 0, 1, 1, 0, 2,
            1, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 1, 2, 0, 2,
            0, 2, 1, 0, 2, 1, 2, 1, 0, 2, 2, 2, 1, 0, 2,
            2, 1, 2, 0, 2, 0, 2, 2, 2, 2, 1, 2, 2, 2, 2,
            2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1,
            1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 2, 0, 1,
            0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1,
            1, 0, 2, 0, 1, 0, 2, 0, 0, 1, 1, 2, 0, 0, 1,
            2, 2, 0, 0, 1, 2, 0, 2, 0, 1, 0, 2, 2, 0, 1,
            1, 2, 2, 0, 1, 2, 2, 2, 0, 1, 2, 0, 2, 0, 1,
            0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 2, 0, 1, 0, 1,
            0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1,
            2, 1, 1, 0, 1, 1, 1, 2, 0, 1, 0, 2, 1, 0, 1,
            1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 2, 1, 2, 0, 1,
            0, 0, 1, 2, 2, 1, 0, 1, 2, 2, 2, 0, 1, 2, 2,
            0, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1,
            2, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 1, 0, 1, 1,
            1, 1, 0, 1, 1, 2, 1, 0, 1, 1, 1, 0, 2, 1, 1,
            0, 2, 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 1,
            2, 0, 2, 1, 1, 0, 2, 2, 1, 1, 1, 2, 2, 1, 1,
            2, 2, 2, 1, 1, 2, 0, 2, 1, 1, 0, 0, 1, 1, 1,
            1, 0, 1, 1, 1, 2, 0, 1, 1, 1, 0, 1, 2, 1, 1,
            0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1,
            1, 1, 2, 1, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 1,
            2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 0, 1, 1, 2, 2,
            1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2,
            0, 0, 0, 2, 1, 1, 0, 0, 2, 1, 2, 0, 0, 2, 1,
            0, 0, 2, 2, 1, 0, 1, 0, 2, 1, 1, 1, 0, 2, 1,
            2, 1, 0, 2, 1, 1, 0, 2, 2, 1, 0, 2, 0, 2, 1,
            1, 2, 0, 2, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1,
            0, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 1,
            2, 0, 2, 2, 1, 0, 0, 1, 2, 1, 1, 0, 1, 2, 1,
            2, 0, 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 1, 2, 1,
            1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1,
            0, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1,
            2, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, 2, 1, 2, 2,
            2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 1, 2,
            1, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 0, 2, 1, 2,
            0, 1, 0, 1, 2, 1, 1, 0, 1, 2, 2, 1, 0, 1, 2,
            1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 2,
            2, 2, 0, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, 2,
            1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0, 2, 1, 2,
            0, 0, 1, 1, 2, 1, 0, 1, 1, 2, 2, 0, 1, 1, 2,
            0, 1, 2, 1, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 2,
            2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 0, 2, 1, 1, 2,
            1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 2,
            0, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
            2, 1, 2, 2, 2
        };

        private static ReadOnlySpan<byte> QuintEncodings => new byte[]
        {
            0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0,
            0, 4, 0, 4, 4, 0, 4, 4, 4, 0, 1, 0, 1, 1, 0,
            2, 1, 0, 3, 1, 0, 4, 1, 0, 1, 4, 0, 4, 4, 1,
            4, 4, 4, 0, 2, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0,
            4, 2, 0, 2, 4, 0, 4, 4, 2, 4, 4, 4, 0, 3, 0,
            1, 3, 0, 2, 3, 0, 3, 3, 0, 4, 3, 0, 3, 4, 0,
            4, 4, 3, 4, 4, 4, 0, 0, 1, 1, 0, 1, 2, 0, 1,
            3, 0, 1, 4, 0, 1, 0, 4, 1, 4, 0, 4, 0, 4, 4,
            0, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 4, 1, 1,
            1, 4, 1, 4, 1, 4, 1, 4, 4, 0, 2, 1, 1, 2, 1,
            2, 2, 1, 3, 2, 1, 4, 2, 1, 2, 4, 1, 4, 2, 4,
            2, 4, 4, 0, 3, 1, 1, 3, 1, 2, 3, 1, 3, 3, 1,
            4, 3, 1, 3, 4, 1, 4, 3, 4, 3, 4, 4, 0, 0, 2,
            1, 0, 2, 2, 0, 2, 3, 0, 2, 4, 0, 2, 0, 4, 2,
            2, 0, 4, 3, 0, 4, 0, 1, 2, 1, 1, 2, 2, 1, 2,
            3, 1, 2, 4, 1, 2, 1, 4, 2, 2, 1, 4, 3, 1, 4,
            0, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 2, 4, 2, 2,
            2, 4, 2, 2, 2, 4, 3, 2, 4, 0, 3, 2, 1, 3, 2,
            2, 3, 2, 3, 3, 2, 4, 3, 2, 3, 4, 2, 2, 3, 4,
            3, 3, 4, 0, 0, 3, 1, 0, 3, 2, 0, 3, 3, 0, 3,
            4, 0, 3, 0, 4, 3, 0, 0, 4, 1, 0, 4, 0, 1, 3,
            1, 1, 3, 2, 1, 3, 3, 1, 3, 4, 1, 3, 1, 4, 3,
            0, 1, 4, 1, 1, 4, 0, 2, 3, 1, 2, 3, 2, 2, 3,
            3, 2, 3, 4, 2, 3, 2, 4, 3, 0, 2, 4, 1, 2, 4,
            0, 3, 3, 1, 3, 3, 2, 3, 3, 3, 3, 3, 4, 3, 3,
            3, 4, 3, 0, 3, 4, 1, 3, 4
        };
    }
}