/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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 org.apache.pdfbox.jbig2.segments;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.pdfbox.jbig2.Bitmap;
import org.apache.pdfbox.jbig2.Region;
import org.apache.pdfbox.jbig2.SegmentHeader;
import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticDecoder;
import org.apache.pdfbox.jbig2.decoder.arithmetic.ArithmeticIntegerDecoder;
import org.apache.pdfbox.jbig2.decoder.arithmetic.CX;
import org.apache.pdfbox.jbig2.decoder.huffman.EncodedTable;
import org.apache.pdfbox.jbig2.decoder.huffman.FixedSizeTable;
import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable;
import org.apache.pdfbox.jbig2.decoder.huffman.HuffmanTable.Code;
import org.apache.pdfbox.jbig2.decoder.huffman.StandardTables;
import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
import org.apache.pdfbox.jbig2.image.Bitmaps;
import org.apache.pdfbox.jbig2.io.SubInputStream;
import org.apache.pdfbox.jbig2.util.CombinationOperator;

/**
 * This class represented the segment type "Text region", 7.4.3, page 56.
 */
public class TextRegion implements Region
{
    private SubInputStream subInputStream;

    /** Region segment information field, 7.4.1 */
    private RegionSegmentInformation regionInfo;

    /** Text region segment flags, 7.4.3.1.1 */
    private short sbrTemplate;
    private short sbdsOffset; /* 6.4.8 */
    private short defaultPixel;
    private CombinationOperator combinationOperator;
    private short isTransposed;
    private short referenceCorner;
    private short logSBStrips;
    private boolean useRefinement;
    private boolean isHuffmanEncoded;

    /** Text region segment Huffman flags, 7.4.3.1.2 */
    private short sbHuffRSize;
    private short sbHuffRDY;
    private short sbHuffRDX;
    private short sbHuffRDHeight;
    private short sbHuffRDWidth;
    private short sbHuffDT;
    private short sbHuffDS;
    private short sbHuffFS;

    /** Text region refinement AT flags, 7.4.3.1.3 */
    private short sbrATX[];
    private short sbrATY[];

    /** Number of symbol instances, 7.4.3.1.4 */
    private long amountOfSymbolInstances;

    /** Further parameters */
    private long currentS;
    private int sbStrips;
    private int amountOfSymbols;

    private Bitmap regionBitmap;
    private ArrayList<Bitmap> symbols = new ArrayList<Bitmap>();

    private ArithmeticDecoder arithmeticDecoder;
    private ArithmeticIntegerDecoder integerDecoder;
    private GenericRefinementRegion genericRefinementRegion;

    private CX cxIADT;
    private CX cxIAFS;
    private CX cxIADS;
    private CX cxIAIT;
    private CX cxIARI;
    private CX cxIARDW;
    private CX cxIARDH;
    private CX cxIAID;
    private CX cxIARDX;
    private CX cxIARDY;
    private CX cx;

    /** codeTable including a code to each symbol used in that region */
    private int symbolCodeLength;
    private FixedSizeTable symbolCodeTable;
    private SegmentHeader segmentHeader;

    /** User-supplied tables * */
    private HuffmanTable fsTable;
    private HuffmanTable dsTable;
    private HuffmanTable table;
    private HuffmanTable rdwTable;
    private HuffmanTable rdhTable;
    private HuffmanTable rdxTable;
    private HuffmanTable rdyTable;
    private HuffmanTable rSizeTable;

    public TextRegion()
    {
    }

    public TextRegion(SubInputStream subInputStream, SegmentHeader segmentHeader)
    {
        this.subInputStream = subInputStream;
        this.regionInfo = new RegionSegmentInformation(subInputStream);
        this.segmentHeader = segmentHeader;
    }

    private void parseHeader()
            throws IOException, InvalidHeaderValueException, IntegerMaxValueException
    {

        regionInfo.parseHeader();

        readRegionFlags();

        if (isHuffmanEncoded)
        {
            readHuffmanFlags();
        }

        readUseRefinement();

        readAmountOfSymbolInstances();

        /* 7.4.3.1.7 */
        getSymbols();

        computeSymbolCodeLength();

        this.checkInput();
    }

    private void readRegionFlags() throws IOException
    {
        /* Bit 15 */
        sbrTemplate = (short) subInputStream.readBit();

        /* Bit 10-14 */
        sbdsOffset = (short) (subInputStream.readBits(5));
        if (sbdsOffset > 0x0f)
        {
            sbdsOffset -= 0x20;
        }

        /* Bit 9 */
        defaultPixel = (short) subInputStream.readBit();

        /* Bit 7-8 */
        combinationOperator = CombinationOperator
                .translateOperatorCodeToEnum((short) (subInputStream.readBits(2) & 0x3));

        /* Bit 6 */
        isTransposed = (short) subInputStream.readBit();

        /* Bit 4-5 */
        referenceCorner = (short) (subInputStream.readBits(2) & 0x3);

        /* Bit 2-3 */
        logSBStrips = (short) (subInputStream.readBits(2) & 0x3);
        sbStrips = (1 << logSBStrips);

        /* Bit 1 */
        if (subInputStream.readBit() == 1)
        {
            useRefinement = true;
        }

        /* Bit 0 */
        if (subInputStream.readBit() == 1)
        {
            isHuffmanEncoded = true;
        }
    }

    private void readHuffmanFlags() throws IOException
    {
        /* Bit 15 */
        subInputStream.readBit(); // Dirty read...

        /* Bit 14 */
        sbHuffRSize = (short) subInputStream.readBit();

        /* Bit 12-13 */
        sbHuffRDY = (short) (subInputStream.readBits(2) & 0xf);

        /* Bit 10-11 */
        sbHuffRDX = (short) (subInputStream.readBits(2) & 0xf);

        /* Bit 8-9 */
        sbHuffRDHeight = (short) (subInputStream.readBits(2) & 0xf);

        /* Bit 6-7 */
        sbHuffRDWidth = (short) (subInputStream.readBits(2) & 0xf);

        /* Bit 4-5 */
        sbHuffDT = (short) (subInputStream.readBits(2) & 0xf);

        /* Bit 2-3 */
        sbHuffDS = (short) (subInputStream.readBits(2) & 0xf);

        /* Bit 0-1 */
        sbHuffFS = (short) (subInputStream.readBits(2) & 0xf);
    }

    private void readUseRefinement() throws IOException
    {
        if (useRefinement && sbrTemplate == 0)
        {
            sbrATX = new short[2];
            sbrATY = new short[2];

            /* Byte 0 */
            sbrATX[0] = subInputStream.readByte();

            /* Byte 1 */
            sbrATY[0] = subInputStream.readByte();

            /* Byte 2 */
            sbrATX[1] = subInputStream.readByte();

            /* Byte 3 */
            sbrATY[1] = subInputStream.readByte();
        }
    }

    private void readAmountOfSymbolInstances() throws IOException
    {
        amountOfSymbolInstances = subInputStream.readBits(32) & 0xffffffff;

        // sanity check: don't decode more than one symbol per pixel
        long pixels = (long) regionInfo.getBitmapWidth() * (long) regionInfo.getBitmapHeight();
        if (pixels < amountOfSymbolInstances)
        {
            amountOfSymbolInstances = pixels;
        }
    }

    private void getSymbols()
            throws IOException, IntegerMaxValueException, InvalidHeaderValueException
    {
        if (segmentHeader.getRtSegments() != null)
        {
            initSymbols();
        }
    }

    private void computeSymbolCodeLength() throws IOException
    {
        if (isHuffmanEncoded)
        {
            symbolIDCodeLengths();
        }
        else
        {
            symbolCodeLength = (int) Math.ceil((Math.log(amountOfSymbols) / Math.log(2)));
        }
    }

    private void checkInput() throws InvalidHeaderValueException
    {

        if (!useRefinement)
        {
            if (sbrTemplate != 0)
            {
                sbrTemplate = 0;
            }
        }

        if (sbHuffFS == 2 || sbHuffRDWidth == 2 || sbHuffRDHeight == 2 || sbHuffRDX == 2
                || sbHuffRDY == 2)
        {
            throw new InvalidHeaderValueException(
                    "Huffman flag value of text region segment is not permitted");
        }

        if (!useRefinement)
        {
            if (sbHuffRSize != 0)
            {
                sbHuffRSize = 0;
            }
            if (sbHuffRDY != 0)
            {
                sbHuffRDY = 0;
            }
            if (sbHuffRDX != 0)
            {
                sbHuffRDX = 0;
            }
            if (sbHuffRDWidth != 0)
            {
                sbHuffRDWidth = 0;
            }
            if (sbHuffRDHeight != 0)
            {
                sbHuffRDHeight = 0;
            }
        }
    }

    public Bitmap getRegionBitmap()
            throws IOException, IntegerMaxValueException, InvalidHeaderValueException
    {

        if (!isHuffmanEncoded)
        {
            setCodingStatistics();
        }

        createRegionBitmap();
        decodeSymbolInstances();

        /* 4) */
        return regionBitmap;
    }

    private void setCodingStatistics() throws IOException
    {
        if (cxIADT == null)
            cxIADT = new CX(512, 1);

        if (cxIAFS == null)
            cxIAFS = new CX(512, 1);

        if (cxIADS == null)
            cxIADS = new CX(512, 1);

        if (cxIAIT == null)
            cxIAIT = new CX(512, 1);

        if (cxIARI == null)
            cxIARI = new CX(512, 1);

        if (cxIARDW == null)
            cxIARDW = new CX(512, 1);

        if (cxIARDH == null)
            cxIARDH = new CX(512, 1);

        if (cxIAID == null)
            cxIAID = new CX(1 << symbolCodeLength, 1);

        if (cxIARDX == null)
            cxIARDX = new CX(512, 1);

        if (cxIARDY == null)
            cxIARDY = new CX(512, 1);

        if (arithmeticDecoder == null)
            arithmeticDecoder = new ArithmeticDecoder(subInputStream);

        if (integerDecoder == null)
            integerDecoder = new ArithmeticIntegerDecoder(arithmeticDecoder);
    }

    private void createRegionBitmap()
    {

        /* 6.4.5 */
        final int width = regionInfo.getBitmapWidth();
        final int height = regionInfo.getBitmapHeight();
        regionBitmap = new Bitmap(width, height);

        /* 1) */
        if (defaultPixel != 0)
        {
            regionBitmap.fillBitmap((byte) 0xff);
        }
    }

    private final long decodeStripT() throws IOException, InvalidHeaderValueException
    {

        long stripT = 0;
        /* 2) */
        if (isHuffmanEncoded)
        {
            /* 6.4.6 */
            if (sbHuffDT == 3)
            {
                // TODO test user-specified table
                if (table == null)
                {
                    int dtNr = 0;

                    if (sbHuffFS == 3)
                    {
                        dtNr++;
                    }

                    if (sbHuffDS == 3)
                    {
                        dtNr++;
                    }

                    table = getUserTable(dtNr);
                }
                stripT = table.decode(subInputStream);
            }
            else
            {
                stripT = StandardTables.getTable(11 + sbHuffDT).decode(subInputStream);
            }
        }
        else
        {
            stripT = integerDecoder.decode(cxIADT);
        }

        return stripT * -(sbStrips);
    }

    private void decodeSymbolInstances()
            throws IOException, InvalidHeaderValueException, IntegerMaxValueException
    {

        long stripT = decodeStripT();

        /* Last two sentences of 6.4.5 2) */
        long firstS = 0;
        long instanceCounter = 0;

        /* 6.4.5 3 a) */
        while (instanceCounter < amountOfSymbolInstances)
        {

            final long dT = decodeDT();
            stripT += dT;
            long dfS = 0;

            /* 3 c) symbol instances in the strip */
            boolean first = true;
            currentS = 0;

            // do until OOB
            for (;;)
            {
                /* 3 c) i) - first symbol instance in the strip */
                if (first)
                {
                    /* 6.4.7 */
                    dfS = decodeDfS();
                    firstS += dfS;
                    currentS = firstS;
                    first = false;
                    /* 3 c) ii) - the remaining symbol instances in the strip */
                }
                else
                {
                    /* 6.4.8 */
                    final long idS = decodeIdS();

                    /*
                     * If result is OOB, then all the symbol instances in this strip have been decoded; proceed to step
                     * 3 d) respectively 3 b). Also exit, if the expected number of instances have been decoded.
                     * 
                     * The latter exit condition guards against pathological cases where a strip's S never contains OOB
                     * and thus never terminates as illustrated in
                     * https://bugs.chromium.org/p/chromium/issues/detail?id=450971 case pdfium-loop2.pdf.
                     */
                    if (idS == Long.MAX_VALUE || instanceCounter >= amountOfSymbolInstances)
                        break;

                    currentS += (idS + sbdsOffset);
                }

                /* 3 c) iii) */
                final long currentT = decodeCurrentT();
                final long t = stripT + currentT;

                /* 3 c) iv) */
                final long id = decodeID();

                /* 3 c) v) */
                final long r = decodeRI();
                /* 6.4.11 */
                final Bitmap ib = decodeIb(r, id);

                /* vi) */
                blit(ib, t);

                instanceCounter++;
            }
        }
    }

    private final long decodeDT() throws IOException
    {
        /* 3) b) */
        /* 6.4.6 */
        long dT;
        if (isHuffmanEncoded)
        {
            if (sbHuffDT == 3)
            {
                dT = table.decode(subInputStream);
            }
            else
            {
                dT = StandardTables.getTable(11 + sbHuffDT).decode(subInputStream);
            }
        }
        else
        {
            dT = integerDecoder.decode(cxIADT);
        }

        return (dT * sbStrips);
    }

    private final long decodeDfS() throws IOException, InvalidHeaderValueException
    {
        if (isHuffmanEncoded)
        {
            if (sbHuffFS == 3)
            {
                if (fsTable == null)
                {
                    fsTable = getUserTable(0);
                }
                return fsTable.decode(subInputStream);
            }
            else
            {
                return StandardTables.getTable(6 + sbHuffFS).decode(subInputStream);
            }
        }
        else
        {
            return integerDecoder.decode(cxIAFS);
        }
    }

    private final long decodeIdS() throws IOException, InvalidHeaderValueException
    {
        if (isHuffmanEncoded)
        {
            if (sbHuffDS == 3)
            {
                // TODO test user-specified table
                if (dsTable == null)
                {
                    int dsNr = 0;
                    if (sbHuffFS == 3)
                    {
                        dsNr++;
                    }

                    dsTable = getUserTable(dsNr);
                }
                return dsTable.decode(subInputStream);

            }
            else
            {
                return StandardTables.getTable(8 + sbHuffDS).decode(subInputStream);
            }
        }
        else
        {
            return integerDecoder.decode(cxIADS);
        }
    }

    private final long decodeCurrentT() throws IOException
    {
        if (sbStrips != 1)
        {
            if (isHuffmanEncoded)
            {
                return subInputStream.readBits(logSBStrips);
            }
            else
            {
                return integerDecoder.decode(cxIAIT);
            }
        }

        return 0;
    }

    private final long decodeID() throws IOException
    {
        if (isHuffmanEncoded)
        {
            if (symbolCodeTable == null)
            {
                return subInputStream.readBits(symbolCodeLength);
            }

            return symbolCodeTable.decode(subInputStream);
        }
        else
        {
            return integerDecoder.decodeIAID(cxIAID, symbolCodeLength);
        }
    }

    private final long decodeRI() throws IOException
    {
        if (useRefinement)
        {
            if (isHuffmanEncoded)
            {
                return subInputStream.readBit();
            }
            else
            {
                return integerDecoder.decode(cxIARI);
            }
        }
        return 0;
    }

    private final Bitmap decodeIb(long r, long id)
            throws IOException, InvalidHeaderValueException, IntegerMaxValueException
    {
        Bitmap ib;

        if (r == 0)
        {
            ib = symbols.get((int) id);
        }
        else
        {
            /* 1) - 4) */
            final long rdw = decodeRdw();
            final long rdh = decodeRdh();
            final long rdx = decodeRdx();
            final long rdy = decodeRdy();

            /* 5) */
            /* long symInRefSize = 0; */
            if (isHuffmanEncoded)
            {
                /* symInRefSize = */decodeSymInRefSize();
                subInputStream.skipBits();
            }

            /* 6) */
            final Bitmap ibo = symbols.get((int) id);
            final int wo = ibo.getWidth();
            final int ho = ibo.getHeight();

            final int genericRegionReferenceDX = (int) ((rdw >> 1) + rdx);
            final int genericRegionReferenceDY = (int) ((rdh >> 1) + rdy);

            if (genericRefinementRegion == null)
            {
                genericRefinementRegion = new GenericRefinementRegion(subInputStream);
            }

            genericRefinementRegion.setParameters(cx, arithmeticDecoder, sbrTemplate,
                    (int) (wo + rdw), (int) (ho + rdh), ibo, genericRegionReferenceDX,
                    genericRegionReferenceDY, false, sbrATX, sbrATY);

            ib = genericRefinementRegion.getRegionBitmap();

            /* 7 */
            if (isHuffmanEncoded)
            {
                subInputStream.skipBits();
            }
        }
        return ib;
    }

    private final long decodeRdw() throws IOException, InvalidHeaderValueException
    {
        if (isHuffmanEncoded)
        {
            if (sbHuffRDWidth == 3)
            {
                // TODO test user-specified table
                if (rdwTable == null)
                {
                    int rdwNr = 0;
                    if (sbHuffFS == 3)
                    {
                        rdwNr++;
                    }

                    if (sbHuffDS == 3)
                    {
                        rdwNr++;
                    }

                    if (sbHuffDT == 3)
                    {
                        rdwNr++;
                    }

                    rdwTable = getUserTable(rdwNr);
                }
                return rdwTable.decode(subInputStream);

            }
            else
            {
                return StandardTables.getTable(14 + sbHuffRDWidth).decode(subInputStream);
            }
        }
        else
        {
            return integerDecoder.decode(cxIARDW);
        }
    }

    private final long decodeRdh() throws IOException, InvalidHeaderValueException
    {
        if (isHuffmanEncoded)
        {
            if (sbHuffRDHeight == 3)
            {
                if (rdhTable == null)
                {
                    int rdhNr = 0;

                    if (sbHuffFS == 3)
                    {
                        rdhNr++;
                    }

                    if (sbHuffDS == 3)
                    {
                        rdhNr++;
                    }

                    if (sbHuffDT == 3)
                    {
                        rdhNr++;
                    }

                    if (sbHuffRDWidth == 3)
                    {
                        rdhNr++;
                    }

                    rdhTable = getUserTable(rdhNr);
                }
                return rdhTable.decode(subInputStream);
            }
            else
            {
                return StandardTables.getTable(14 + sbHuffRDHeight).decode(subInputStream);
            }
        }
        else
        {
            return integerDecoder.decode(cxIARDH);
        }
    }

    private final long decodeRdx() throws IOException, InvalidHeaderValueException
    {
        if (isHuffmanEncoded)
        {
            if (sbHuffRDX == 3)
            {
                if (rdxTable == null)
                {
                    int rdxNr = 0;
                    if (sbHuffFS == 3)
                    {
                        rdxNr++;
                    }

                    if (sbHuffDS == 3)
                    {
                        rdxNr++;
                    }

                    if (sbHuffDT == 3)
                    {
                        rdxNr++;
                    }

                    if (sbHuffRDWidth == 3)
                    {
                        rdxNr++;
                    }

                    if (sbHuffRDHeight == 3)
                    {
                        rdxNr++;
                    }

                    rdxTable = getUserTable(rdxNr);
                }
                return rdxTable.decode(subInputStream);
            }
            else
            {
                return StandardTables.getTable(14 + sbHuffRDX).decode(subInputStream);
            }
        }
        else
        {
            return integerDecoder.decode(cxIARDX);
        }
    }

    private final long decodeRdy() throws IOException, InvalidHeaderValueException
    {
        if (isHuffmanEncoded)
        {
            if (sbHuffRDY == 3)
            {
                if (rdyTable == null)
                {
                    int rdyNr = 0;
                    if (sbHuffFS == 3)
                    {
                        rdyNr++;
                    }

                    if (sbHuffDS == 3)
                    {
                        rdyNr++;
                    }

                    if (sbHuffDT == 3)
                    {
                        rdyNr++;
                    }

                    if (sbHuffRDWidth == 3)
                    {
                        rdyNr++;
                    }

                    if (sbHuffRDHeight == 3)
                    {
                        rdyNr++;
                    }

                    if (sbHuffRDX == 3)
                    {
                        rdyNr++;
                    }

                    rdyTable = getUserTable(rdyNr);
                }
                return rdyTable.decode(subInputStream);
            }
            else
            {
                return StandardTables.getTable(14 + sbHuffRDY).decode(subInputStream);
            }
        }
        else
        {
            return integerDecoder.decode(cxIARDY);
        }
    }

    private final long decodeSymInRefSize() throws IOException, InvalidHeaderValueException
    {
        if (sbHuffRSize == 0)
        {
            return StandardTables.getTable(1).decode(subInputStream);
        }
        else
        {
            if (rSizeTable == null)
            {
                int rSizeNr = 0;

                if (sbHuffFS == 3)
                {
                    rSizeNr++;
                }

                if (sbHuffDS == 3)
                {
                    rSizeNr++;
                }

                if (sbHuffDT == 3)
                {
                    rSizeNr++;
                }

                if (sbHuffRDWidth == 3)
                {
                    rSizeNr++;
                }

                if (sbHuffRDHeight == 3)
                {
                    rSizeNr++;
                }

                if (sbHuffRDX == 3)
                {
                    rSizeNr++;
                }

                if (sbHuffRDY == 3)
                {
                    rSizeNr++;
                }

                rSizeTable = getUserTable(rSizeNr);
            }
            return rSizeTable.decode(subInputStream);
        }

    }

    private final void blit(Bitmap ib, long t)
    {
        if (isTransposed == 0 && (referenceCorner == 2 || referenceCorner == 3))
        {
            currentS += ib.getWidth() - 1;
        }
        else if (isTransposed == 1 && (referenceCorner == 0 || referenceCorner == 2))
        {
            currentS += ib.getHeight() - 1;
        }

        /* vii) */
        long s = currentS;

        /* viii) */
        if (isTransposed == 1)
        {
            final long swap = t;
            t = s;
            s = swap;
        }

        if (referenceCorner != 1)
        {
            if (referenceCorner == 0)
            {
                // BL
                t -= ib.getHeight() - 1;
            }
            else if (referenceCorner == 2)
            {
                // BR
                t -= ib.getHeight() - 1;
                s -= ib.getWidth() - 1;
            }
            else if (referenceCorner == 3)
            {
                // TR
                s -= ib.getWidth() - 1;
            }
        }

        Bitmaps.blit(ib, regionBitmap, (int) s, (int) t, combinationOperator);

        /* x) */
        if (isTransposed == 0 && (referenceCorner == 0 || referenceCorner == 1))
        {
            currentS += ib.getWidth() - 1;
        }

        if (isTransposed == 1 && (referenceCorner == 1 || referenceCorner == 3))
        {
            currentS += ib.getHeight() - 1;
        }

    }

    private void initSymbols()
            throws IOException, IntegerMaxValueException, InvalidHeaderValueException
    {
        for (final SegmentHeader segment : segmentHeader.getRtSegments())
        {
            if (segment.getSegmentType() == 0)
            {
                final SymbolDictionary sd = (SymbolDictionary) segment.getSegmentData();

                sd.cxIAID = cxIAID;
                symbols.addAll(sd.getDictionary());
            }
        }
        amountOfSymbols = symbols.size();
    }

    private HuffmanTable getUserTable(final int tablePosition)
            throws InvalidHeaderValueException, IOException
    {
        int tableCounter = 0;

        for (final SegmentHeader referredToSegmentHeader : segmentHeader.getRtSegments())
        {
            if (referredToSegmentHeader.getSegmentType() == 53)
            {
                if (tableCounter == tablePosition)
                {
                    final Table t = (Table) referredToSegmentHeader.getSegmentData();
                    return new EncodedTable(t);
                }
                else
                {
                    tableCounter++;
                }
            }
        }
        return null;
    }

    private void symbolIDCodeLengths() throws IOException
    {
        /* 1) - 2) */
        final List<Code> runCodeTable = new ArrayList<Code>();

        for (int i = 0; i < 35; i++)
        {
            final int prefLen = (int) (subInputStream.readBits(4) & 0xf);
            if (prefLen > 0)
            {
                runCodeTable.add(new Code(prefLen, 0, i, false));
            }
        }

        HuffmanTable ht = new FixedSizeTable(runCodeTable);

        /* 3) - 5) */
        long previousCodeLength = 0;

        int counter = 0;
        final List<Code> sbSymCodes = new ArrayList<Code>();
        while (counter < amountOfSymbols)
        {
            final long code = ht.decode(subInputStream);
            if (code < 32)
            {
                if (code > 0)
                {
                    sbSymCodes.add(new Code((int) code, 0, counter, false));
                }

                previousCodeLength = code;
                counter++;
            }
            else
            {

                long runLength = 0;
                long currCodeLength = 0;
                if (code == 32)
                {
                    runLength = 3 + subInputStream.readBits(2);
                    if (counter > 0)
                    {
                        currCodeLength = previousCodeLength;
                    }
                }
                else if (code == 33)
                {
                    runLength = 3 + subInputStream.readBits(3);
                }
                else if (code == 34)
                {
                    runLength = 11 + subInputStream.readBits(7);
                }

                for (int j = 0; j < runLength; j++)
                {
                    if (currCodeLength > 0)
                    {
                        sbSymCodes.add(new Code((int) currCodeLength, 0, counter, false));
                    }
                    counter++;
                }
            }
        }

        /* 6) - Skip over remaining bits in the last Byte read */
        subInputStream.skipBits();

        /* 7) */
        symbolCodeTable = new FixedSizeTable(sbSymCodes);

    }

    public void init(SegmentHeader header, SubInputStream sis)
            throws InvalidHeaderValueException, IntegerMaxValueException, IOException
    {
        this.segmentHeader = header;
        this.subInputStream = sis;
        this.regionInfo = new RegionSegmentInformation(subInputStream);
        parseHeader();
    }

    protected void setContexts(CX cx, CX cxIADT, CX cxIAFS, CX cxIADS, CX cxIAIT, CX cxIAID,
            CX cxIARDW, CX cxIARDH, CX cxIARDX, CX cxIARDY)
    {
        this.cx = cx;

        this.cxIADT = cxIADT;
        this.cxIAFS = cxIAFS;
        this.cxIADS = cxIADS;
        this.cxIAIT = cxIAIT;

        this.cxIAID = cxIAID;

        this.cxIARDW = cxIARDW;
        this.cxIARDH = cxIARDH;
        this.cxIARDX = cxIARDX;
        this.cxIARDY = cxIARDY;
    }

    protected void setParameters(ArithmeticDecoder arithmeticDecoder,
            ArithmeticIntegerDecoder iDecoder, boolean isHuffmanEncoded, boolean sbRefine, int sbw,
            int sbh, long sbNumInstances, int sbStrips, int sbNumSyms, short sbDefaultPixel,
            short sbCombinationOperator, short transposed, short refCorner, short sbdsOffset,
            short sbHuffFS, short sbHuffDS, short sbHuffDT, short sbHuffRDWidth,
            short sbHuffRDHeight, short sbHuffRDX, short sbHuffRDY, short sbHuffRSize,
            short sbrTemplate, short sbrATX[], short sbrATY[], ArrayList<Bitmap> sbSyms,
            int sbSymCodeLen)
    {

        this.arithmeticDecoder = arithmeticDecoder;

        this.integerDecoder = iDecoder;

        this.isHuffmanEncoded = isHuffmanEncoded;
        this.useRefinement = sbRefine;

        this.regionInfo.setBitmapWidth(sbw);
        this.regionInfo.setBitmapHeight(sbh);

        this.amountOfSymbolInstances = sbNumInstances;
        this.sbStrips = sbStrips;
        this.amountOfSymbols = sbNumSyms;
        this.defaultPixel = sbDefaultPixel;
        this.combinationOperator = CombinationOperator
                .translateOperatorCodeToEnum(sbCombinationOperator);
        this.isTransposed = transposed;
        this.referenceCorner = refCorner;
        this.sbdsOffset = sbdsOffset;

        this.sbHuffFS = sbHuffFS;
        this.sbHuffDS = sbHuffDS;
        this.sbHuffDT = sbHuffDT;
        this.sbHuffRDWidth = sbHuffRDWidth;
        this.sbHuffRDHeight = sbHuffRDHeight;
        this.sbHuffRDX = sbHuffRDX;
        this.sbHuffRDY = sbHuffRDY;
        this.sbHuffRSize = sbHuffRSize;

        this.sbrTemplate = sbrTemplate;
        this.sbrATX = sbrATX;
        this.sbrATY = sbrATY;

        this.symbols = sbSyms;
        this.symbolCodeLength = sbSymCodeLen;
    }

    public RegionSegmentInformation getRegionInfo()
    {
        return regionInfo;
    }
}
