//This File includes code from:
//Copyright(c) 2018 Mark Heath, Andrew Ward & Contributors
//Licensed under the MIT License (see LICENSE (Nlayer).txt)
using System;

namespace NLayer.Decoder
{
    abstract class LayerIIDecoderBase : LayerDecoderBase
    {
        protected const int SSLIMIT = 12;

        static protected bool GetCRC(MpegFrame frame, int[] rateTable, int[][] allocLookupTable, bool readScfsiBits, ref uint crc)
        {
            var scfsiBits = 0;

            var subbandCount = rateTable.Length;
            var jsbound = subbandCount;
            if (frame.ChannelMode == MpegChannelMode.JointStereo)
            {
                jsbound = frame.ChannelModeExtension * 4 + 4;
            }

            var channels = frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2;
            var sb = 0;
            for (; sb < jsbound; sb++)
            {
                var bits = allocLookupTable[rateTable[sb]][0];
                for (int ch = 0; ch < channels; ch++)
                {
                    var alloc = frame.ReadBits(bits);
                    if (alloc > 0) scfsiBits += 2;

                    MpegFrame.UpdateCRC(alloc, bits, ref crc);
                }
            }

            for (; sb < subbandCount; sb++)
            {
                var bits = allocLookupTable[rateTable[sb]][0];

                var alloc = frame.ReadBits(bits);
                if (alloc > 0) scfsiBits += channels * 2;

                MpegFrame.UpdateCRC(alloc, bits, ref crc);
            }

            if (readScfsiBits)
            {
                while (scfsiBits >= 2)
                {
                    MpegFrame.UpdateCRC(frame.ReadBits(2), 2, ref crc);
                    scfsiBits -= 2;
                }
            }

            return true;
        }

        #region Lookup Tables

        static readonly float[] _groupedC = { 0, 0, 1.33333333333f, 1.60000000000f, 1.77777777777f };

        static readonly float[] _groupedD = { 0, 0, -0.5f, -0.5f, -0.5f };

        static readonly float[] _C = {
                                         0.00000000000f,
                                         0.00000000000f, 1.33333333333f, 1.14285714286f, 1.06666666666f, 1.03225806452f, 1.01587301587f, 1.00787401575f, 1.00392156863f,
                                         1.00195694716f, 1.00097751711f, 1.00048851979f, 1.00024420024f, 1.00012208522f, 1.00006103888f, 1.00003051851f, 1.00001525902f
                                     };

        static readonly float[] _D = {
                                         0.00000000000f - 0f,
                                         0.00000000000f - 0f, 0.50000000000f - 1f, 0.25000000000f - 1f, 0.12500000000f - 1f, 0.062500000000f - 1f, 0.03125000000f - 1f, 0.01562500000f - 1f, 0.00781250000f - 1f,
                                         0.00390625000f - 1f, 0.00195312500f - 1f, 0.00097656250f - 1f, 0.00048828125f - 1f, 0.000244140630f - 1f, 0.00012207031f - 1f, 0.00006103516f - 1f, 0.00003051758f - 1f
                                     };

        static readonly float[] _denormalMultiplier = {
                                                          2.00000000000000f, 1.58740105196820f, 1.25992104989487f, 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, 0.39685026299205f,
                                                          0.31498026247372f, 0.25000000000000f, 0.19842513149602f, 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, 0.06250000000000f,
                                                          0.04960628287401f, 0.03937253280921f, 0.03125000000000f, 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, 0.00984313320230f,
                                                          0.00781250000000f, 0.00620078535925f, 0.00492156660115f, 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, 0.00155019633981f,
                                                          0.00123039165029f, 0.00097656250000f, 0.00077509816991f, 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, 0.00024414062500f,
                                                          0.00019377454248f, 0.00015379895629f, 0.00012207031250f, 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, 0.00003844973907f,
                                                          0.00003051757813f, 0.00002422181781f, 0.00001922486954f, 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, 0.00000605545445f,
                                                          0.00000480621738f, 0.00000381469727f, 0.00000302772723f, 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, 0.00000095367432f
                                                      };

        #endregion

        int _channels, _jsbound, _granuleCount;
        int[][] _allocLookupTable, _scfsi, _samples;
        int[][][] _scalefac;
        float[] _polyPhaseBuf;

        int[][] _allocation;

        protected LayerIIDecoderBase(int[][] allocLookupTable, int granuleCount)
            : base()
        {
            _allocLookupTable = allocLookupTable;
            _granuleCount = granuleCount;

            _allocation = new int[][] { new int[SBLIMIT], new int[SBLIMIT] };
            _scfsi = new int[][] { new int[SBLIMIT], new int[SBLIMIT] };
            _samples = new int[][] { new int[SBLIMIT * SSLIMIT * _granuleCount], new int[SBLIMIT * SSLIMIT * _granuleCount] };

            _scalefac = new int[][][] { new int[3][], new int[3][] };
            for (int i = 0; i < 3; i++)
            {
                _scalefac[0][i] = new int[SBLIMIT];
                _scalefac[1][i] = new int[SBLIMIT];
            }

            _polyPhaseBuf = new float[SBLIMIT];
        }

        internal override int DecodeFrame(IMpegFrame frame, float[] ch0, float[] ch1)
        {
            InitFrame(frame);

            var rateTable = GetRateTable(frame);

            ReadAllocation(frame, rateTable);

            for (int i = 0; i < _scfsi[0].Length; i++)
            {
                _scfsi[0][i] = _allocation[0][i] != 0 ? 2 : -1;
                _scfsi[1][i] = _allocation[1][i] != 0 ? 2 : -1;
            }
            ReadScaleFactorSelection(frame, _scfsi, _channels);

            ReadScaleFactors(frame);

            ReadSamples(frame);

            return DecodeSamples(ch0, ch1);
        }

        void InitFrame(IMpegFrame frame)
        {
            switch (frame.ChannelMode)
            {
                case MpegChannelMode.Mono:
                    _channels = 1;
                    _jsbound = SBLIMIT;
                    break;
                case MpegChannelMode.JointStereo:
                    _channels = 2;
                    _jsbound = frame.ChannelModeExtension * 4 + 4;
                    break;
                default:
                    _channels = 2;
                    _jsbound = SBLIMIT;
                    break;
            }
        }

        abstract protected int[] GetRateTable(IMpegFrame frame);

        void ReadAllocation(IMpegFrame frame, int[] rateTable)
        {
            var _subBandCount = rateTable.Length;
            if (_jsbound > _subBandCount) _jsbound = _subBandCount;

            Array.Clear(_allocation[0], 0, SBLIMIT);
            Array.Clear(_allocation[1], 0, SBLIMIT);

            int sb = 0;
            for (; sb < _jsbound; sb++)
            {
                var table = _allocLookupTable[rateTable[sb]];
                var bits = table[0];
                for (int ch = 0; ch < _channels; ch++)
                {
                    _allocation[ch][sb] = table[frame.ReadBits(bits) + 1];
                }
            }
            for (; sb < _subBandCount; sb++)
            {
                var table = _allocLookupTable[rateTable[sb]];
                _allocation[0][sb] = _allocation[1][sb] = table[frame.ReadBits(table[0]) + 1];
            }
        }

        abstract protected void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels);

        void ReadScaleFactors(IMpegFrame frame)
        {
            for (int sb = 0; sb < SBLIMIT; sb++)
            {
                for (int ch = 0; ch < _channels; ch++)
                {
                    switch (_scfsi[ch][sb])
                    {
                        case 0:
                            _scalefac[ch][0][sb] = frame.ReadBits(6);
                            _scalefac[ch][1][sb] = frame.ReadBits(6);
                            _scalefac[ch][2][sb] = frame.ReadBits(6);
                            break;
                        case 1:
                            _scalefac[ch][0][sb] =
                            _scalefac[ch][1][sb] = frame.ReadBits(6);
                            _scalefac[ch][2][sb] = frame.ReadBits(6);
                            break;
                        case 2:
                            _scalefac[ch][0][sb] =
                            _scalefac[ch][1][sb] =
                            _scalefac[ch][2][sb] = frame.ReadBits(6);
                            break;
                        case 3:
                            _scalefac[ch][0][sb] = frame.ReadBits(6);
                            _scalefac[ch][1][sb] =
                            _scalefac[ch][2][sb] = frame.ReadBits(6);
                            break;
                        default:
                            _scalefac[ch][0][sb] = 63;
                            _scalefac[ch][1][sb] = 63;
                            _scalefac[ch][2][sb] = 63;
                            break;
                    }
                }
            }
        }

        void ReadSamples(IMpegFrame frame)
        {
            for (int ss = 0, idx = 0; ss < SSLIMIT; ss++, idx += SBLIMIT * (_granuleCount - 1))
            {
                for (int sb = 0; sb < SBLIMIT; sb++, idx++)
                {
                    for (int ch = 0; ch < _channels; ch++)
                    {
                        if (ch == 0 || sb < _jsbound)
                        {
                            var alloc = _allocation[ch][sb];
                            if (alloc != 0)
                            {
                                if (alloc < 0)
                                {
                                    var val = frame.ReadBits(-alloc);
                                    var levels = (1 << (-alloc / 2 + -alloc % 2 - 1)) + 1;

                                    _samples[ch][idx] = val % levels;
                                    val /= levels;
                                    _samples[ch][idx + SBLIMIT] = val % levels;
                                    _samples[ch][idx + SBLIMIT * 2] = val / levels;
                                }
                                else
                                {
                                    for (int gr = 0; gr < _granuleCount; gr++)
                                    {
                                        _samples[ch][idx + SBLIMIT * gr] = frame.ReadBits(alloc);
                                    }
                                }
                            }
                            else
                            {
                                for (int gr = 0; gr < _granuleCount; gr++)
                                {
                                    _samples[ch][idx + SBLIMIT * gr] = 0;
                                }
                            }
                        }
                        else
                        {
                            for (int gr = 0; gr < _granuleCount; gr++)
                            {
                                _samples[1][idx + SBLIMIT * gr] = _samples[0][idx + SBLIMIT * gr];
                            }
                        }
                    }
                }
            }
        }

        int DecodeSamples(float[] ch0, float[] ch1)
        {
            var chanBufs = new float[2][];
            var startChannel = 0;
            var endChannel = _channels - 1;
            if (_channels == 1 || StereoMode == StereoMode.LeftOnly)
            {
                chanBufs[0] = ch0;
                endChannel = 0;
            }
            else if (StereoMode == StereoMode.RightOnly)
            {
                chanBufs[1] = ch0;  
                startChannel = 1;
            }
            else    
            {
                chanBufs[0] = ch0;
                chanBufs[1] = ch1;
            }

            int idx = 0;
            for (int ch = startChannel; ch <= endChannel; ch++)
            {
                idx = 0;
                for (int gr = 0; gr < _granuleCount; gr++)
                {
                    for (int ss = 0; ss < SSLIMIT; ss++)
                    {
                        for (int sb = 0; sb < SBLIMIT; sb++, idx++)
                        {
                            var alloc = _allocation[ch][sb];
                            if (alloc != 0)
                            {
                                float[] c, d;
                                if (alloc < 0)
                                {
                                    alloc = -alloc / 2 + -alloc % 2 - 1;
                                    c = _groupedC;
                                    d = _groupedD;
                                }
                                else
                                {
                                    c = _C;
                                    d = _D;
                                }

                                _polyPhaseBuf[sb] = c[alloc] * ((_samples[ch][idx] << (16 - alloc)) / 32768f + d[alloc]) * _denormalMultiplier[_scalefac[ch][gr][sb]];
                            }
                            else
                            {
                                _polyPhaseBuf[sb] = 0f;
                            }
                        }

                        base.InversePolyPhase(ch, _polyPhaseBuf);
                        Array.Copy(_polyPhaseBuf, 0, chanBufs[ch], idx - SBLIMIT, SBLIMIT);
                    }
                }
            }

            if (_channels == 2 && StereoMode == NLayer.StereoMode.DownmixToMono)
            {
                for (int i = 0; i < idx; i++)
                {
                    ch0[i] = (ch0[i] + ch1[i]) / 2;
                }
            }

            return idx;
        }
    }
}
