//
// Copyright (c) 2008-2011, Kenneth Bell
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//

//
// Based on "libbzip2", Copyright (C) 1996-2007 Julian R Seward.
//

namespace DiscUtils.Compression
{
    using System.IO;

    internal class BZip2BlockDecoder
    {
        private InverseBurrowsWheeler _inverseBurrowsWheeler;
        private uint _crc;

        public BZip2BlockDecoder(int blockSize)
        {
            _inverseBurrowsWheeler = new InverseBurrowsWheeler(blockSize);
        }

        public uint Crc
        {
            get { return _crc; }
        }

        public int Process(BitStream bitstream, byte[] outputBuffer, int outputBufferOffset)
        {
            _crc = 0;
            for (int i = 0; i < 4; ++i)
            {
                _crc = (_crc << 8) | bitstream.Read(8);
            }

            bool rand = bitstream.Read(1) != 0;
            int origPtr = (int)bitstream.Read(24);

            int thisBlockSize = ReadBuffer(bitstream, outputBuffer, outputBufferOffset);

            _inverseBurrowsWheeler.OriginalIndex = origPtr;
            _inverseBurrowsWheeler.Process(outputBuffer, outputBufferOffset, thisBlockSize, outputBuffer, outputBufferOffset);

            if (rand)
            {
                BZip2Randomizer randomizer = new BZip2Randomizer();
                randomizer.Process(outputBuffer, outputBufferOffset, thisBlockSize, outputBuffer, outputBufferOffset);
            }

            return thisBlockSize;
        }

        private static int ReadBuffer(BitStream bitstream, byte[] buffer, int offset)
        {
            // The MTF state
            int numInUse = 0;
            MoveToFront moveFrontTransform = new MoveToFront();
            bool[] inUseGroups = new bool[16];
            for (int i = 0; i < 16; ++i)
            {
                inUseGroups[i] = bitstream.Read(1) != 0;
            }

            for (int i = 0; i < 256; ++i)
            {
                if (inUseGroups[i / 16])
                {
                    if (bitstream.Read(1) != 0)
                    {
                        moveFrontTransform.Set(numInUse, (byte)i);
                        numInUse++;
                    }
                }
            }

            // Initialize 'virtual' Huffman tree from bitstream
            BZip2CombinedHuffmanTrees huffmanTree = new BZip2CombinedHuffmanTrees(bitstream, numInUse + 2);

            // Main loop reading data
            int readBytes = 0;
            while (true)
            {
                uint symbol = huffmanTree.NextSymbol();

                if (symbol < 2)
                {
                    // RLE, with length stored in a binary-style format
                    uint runLength = 0;
                    int bitShift = 0;
                    while (symbol < 2)
                    {
                        runLength += (symbol + 1) << bitShift;
                        bitShift++;

                        symbol = huffmanTree.NextSymbol();
                    }

                    byte b = moveFrontTransform.Head;
                    while (runLength > 0)
                    {
                        buffer[offset + readBytes] = b;
                        ++readBytes;
                        --runLength;
                    }
                }

                if (symbol <= numInUse)
                {
                    // Single byte
                    byte b = moveFrontTransform.GetAndMove((int)symbol - 1);
                    buffer[offset + readBytes] = b;
                    ++readBytes;
                }
                else if (symbol == numInUse + 1)
                {
                    // End of block marker
                    return readBytes;
                }
                else
                {
                    throw new InvalidDataException("Invalid symbol from Huffman table");
                }
            }
        }
    }
}
