/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.compression;

import static io.netty.handler.codec.compression.Bzip2Constants.*;

/**
 * A decoder for the Bzip2 Huffman coding stage.
 */
final class Bzip2HuffmanStageDecoder {
    /**
     * A reader that provides bit-level reads.
     */
    private final Bzip2BitReader reader;

    /**
     * The Huffman table number to use for each group of 50 symbols.
     */
    byte[] selectors;

    /**
     * The minimum code length for each Huffman table.
     */
    private final int[] minimumLengths;

    /**
     * An array of values for each Huffman table that must be subtracted from the numerical value of
     * a Huffman code of a given bit length to give its canonical code index.
     */
    private final int[][] codeBases;

    /**
     * An array of values for each Huffman table that gives the highest numerical value of a Huffman
     * code of a given bit length.
     */
    private final int[][] codeLimits;

    /**
     * A mapping for each Huffman table from canonical code index to output symbol.
     */
    private final int[][] codeSymbols;

    /**
     * The Huffman table for the current group.
     */
    private int currentTable;

    /**
     * The index of the current group within the selectors array.
     */
    private int groupIndex = -1;

    /**
     * The byte position within the current group. A new group is selected every 50 decoded bytes.
     */
    private int groupPosition = -1;

    /**
     * Total number of used Huffman tables in range 2..6.
     */
    final int totalTables;

    /**
     * The total number of codes (uniform for each table).
     */
    final int alphabetSize;

    /**
     * Table for Move To Front transformations.
     */
    final Bzip2MoveToFrontTable tableMTF = new Bzip2MoveToFrontTable();

    // For saving state if end of current ByteBuf was reached
    int currentSelector;

    /**
     * The Canonical Huffman code lengths for each table.
     */
    final byte[][] tableCodeLengths;

    // For saving state if end of current ByteBuf was reached
    int currentGroup;
    int currentLength = -1;
    int currentAlpha;
    boolean modifyLength;

    Bzip2HuffmanStageDecoder(final Bzip2BitReader reader, final int totalTables, final int alphabetSize) {
        this.reader = reader;
        this.totalTables = totalTables;
        this.alphabetSize = alphabetSize;

        minimumLengths = new int[totalTables];
        codeBases = new int[totalTables][HUFFMAN_DECODE_MAX_CODE_LENGTH + 2];
        codeLimits = new int[totalTables][HUFFMAN_DECODE_MAX_CODE_LENGTH + 1];
        codeSymbols = new int[totalTables][HUFFMAN_MAX_ALPHABET_SIZE];
        tableCodeLengths = new byte[totalTables][HUFFMAN_MAX_ALPHABET_SIZE];
    }

    /**
     * Constructs Huffman decoding tables from lists of Canonical Huffman code lengths.
     */
    void createHuffmanDecodingTables() {
        final int alphabetSize = this.alphabetSize;

        for (int table = 0; table < tableCodeLengths.length; table++) {
            final int[] tableBases = codeBases[table];
            final int[] tableLimits = codeLimits[table];
            final int[] tableSymbols = codeSymbols[table];
            final byte[] codeLengths = tableCodeLengths[table];

            int minimumLength = HUFFMAN_DECODE_MAX_CODE_LENGTH;
            int maximumLength = 0;

            // Find the minimum and maximum code length for the table
            for (int i = 0; i < alphabetSize; i++) {
                final byte currLength = codeLengths[i];
                maximumLength = Math.max(currLength, maximumLength);
                minimumLength = Math.min(currLength, minimumLength);
            }
            minimumLengths[table] = minimumLength;

            // Calculate the first output symbol for each code length
            for (int i = 0; i < alphabetSize; i++) {
                tableBases[codeLengths[i] + 1]++;
            }
            for (int i = 1, b = tableBases[0]; i < HUFFMAN_DECODE_MAX_CODE_LENGTH + 2; i++) {
                b += tableBases[i];
                tableBases[i] = b;
            }

            // Calculate the first and last Huffman code for each code length (codes at a given
            // length are sequential in value)
            for (int i = minimumLength, code = 0; i <= maximumLength; i++) {
                int base = code;
                code += tableBases[i + 1] - tableBases[i];
                tableBases[i] = base - tableBases[i];
                tableLimits[i] = code - 1;
                code <<= 1;
            }

            // Populate the mapping from canonical code index to output symbol
            for (int bitLength = minimumLength, codeIndex = 0; bitLength <= maximumLength; bitLength++) {
                for (int symbol = 0; symbol < alphabetSize; symbol++) {
                    if (codeLengths[symbol] == bitLength) {
                        tableSymbols[codeIndex++] = symbol;
                    }
                }
            }
        }

        currentTable = selectors[0];
    }

    /**
     * Decodes and returns the next symbol.
     * @return The decoded symbol
     */
    int nextSymbol() {
        // Move to next group selector if required
        if (++groupPosition % HUFFMAN_GROUP_RUN_LENGTH == 0) {
            groupIndex++;
            if (groupIndex == selectors.length) {
                throw new DecompressionException("error decoding block");
            }
            currentTable = selectors[groupIndex] & 0xff;
        }

        final Bzip2BitReader reader = this.reader;
        final int currentTable = this.currentTable;
        final int[] tableLimits = codeLimits[currentTable];
        final int[] tableBases = codeBases[currentTable];
        final int[] tableSymbols = codeSymbols[currentTable];
        int codeLength = minimumLengths[currentTable];

        // Starting with the minimum bit length for the table, read additional bits one at a time
        // until a complete code is recognised
        int codeBits = reader.readBits(codeLength);
        for (; codeLength <= HUFFMAN_DECODE_MAX_CODE_LENGTH; codeLength++) {
            if (codeBits <= tableLimits[codeLength]) {
                // Convert the code to a symbol index and return
                return tableSymbols[codeBits - tableBases[codeLength]];
            }
            codeBits = codeBits << 1 | reader.readBits(1);
        }

        throw new DecompressionException("a valid code was not recognised");
    }
}
