/**
 * 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 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.CX;
import org.apache.pdfbox.jbig2.decoder.mmr.MMRDecompressor;
import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
import org.apache.pdfbox.jbig2.io.SubInputStream;
import org.apache.pdfbox.jbig2.util.log.Logger;
import org.apache.pdfbox.jbig2.util.log.LoggerFactory;

/**
 * This class represents a generic region segment.<br>
 * Parsing is done as described in 7.4.5.<br>
 * Decoding procedure is done as described in 6.2.5.7 and 7.4.6.4.
 */
public class GenericRegion implements Region
{

    private final Logger log = LoggerFactory.getLogger(GenericRegion.class);

    private SubInputStream subInputStream;
    private long dataHeaderOffset;
    private long dataHeaderLength;
    private long dataOffset;
    private long dataLength;

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

    /** Generic region segment flags, 7.4.6.2 */
    private boolean useExtTemplates;
    private boolean isTPGDon;
    private byte gbTemplate;
    private boolean isMMREncoded;

    /** Generic region segment AT flags, 7.4.6.3 */
    private short[] gbAtX;
    private short[] gbAtY;
    private boolean[] gbAtOverride;

    /**
     * If true, AT pixels are not on their nominal location and have to be overridden
     */
    private boolean override;

    /** Decoded data as pixel values (use row stride/width to wrap line) */
    private Bitmap regionBitmap;

    private ArithmeticDecoder arithDecoder;
    private CX cx;

    private MMRDecompressor mmrDecompressor;

    public GenericRegion()
    {
    }

    public GenericRegion(final SubInputStream subInputStream)
    {
        this.subInputStream = subInputStream;
        this.regionInfo = new RegionSegmentInformation(subInputStream);
    }

    private void parseHeader() throws IOException, InvalidHeaderValueException
    {
        regionInfo.parseHeader();

        /* Bit 5-7 */
        subInputStream.readBits(3); // Dirty read...

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

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

        /* Bit 1-2 */
        gbTemplate = (byte) (subInputStream.readBits(2) & 0xf);

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

        if (!isMMREncoded)
        {
            final int amountOfGbAt;
            if (gbTemplate == 0)
            {
                if (useExtTemplates)
                {
                    amountOfGbAt = 12;
                }
                else
                {
                    amountOfGbAt = 4;
                }
            }
            else
            {
                amountOfGbAt = 1;
            }

            readGbAtPixels(amountOfGbAt);
        }

        /* Segment data structure */
        computeSegmentDataStructure();

        this.checkInput();
    }

    private void readGbAtPixels(final int amountOfGbAt) throws IOException
    {
        gbAtX = new short[amountOfGbAt];
        gbAtY = new short[amountOfGbAt];

        for (int i = 0; i < amountOfGbAt; i++)
        {
            gbAtX[i] = subInputStream.readByte();
            gbAtY[i] = subInputStream.readByte();
        }
    }

    private void computeSegmentDataStructure() throws IOException
    {
        dataOffset = subInputStream.getStreamPosition();
        dataHeaderLength = dataOffset - dataHeaderOffset;
        dataLength = subInputStream.length() - dataHeaderLength;
    }

    private void checkInput() throws InvalidHeaderValueException
    {
        if (isMMREncoded)
        {
            if (gbTemplate != 0)
            {
                log.info("gbTemplate should contain the value 0");
            }
        }
    }

    /**
     * The procedure is described in 6.2.5.7, page 17.
     * 
     * @return The decoded {@link Bitmap} of this region.
     */
    public Bitmap getRegionBitmap() throws IOException
    {
        if (null == regionBitmap)
        {

            if (isMMREncoded)
            {

                /*
                 * MMR DECODER CALL
                 */
                if (null == mmrDecompressor)
                {
                    mmrDecompressor = new MMRDecompressor(regionInfo.getBitmapWidth(),
                            regionInfo.getBitmapHeight(),
                            new SubInputStream(subInputStream, dataOffset, dataLength));
                }

                /* 6.2.6 */
                regionBitmap = mmrDecompressor.uncompress();

            }
            else
            {

                /*
                 * ARITHMETIC DECODER PROCEDURE for generic region segments
                 */

                updateOverrideFlags();

                /* 6.2.5.7 - 1) */
                int ltp = 0;

                if (arithDecoder == null)
                {
                    arithDecoder = new ArithmeticDecoder(subInputStream);
                }
                if (cx == null)
                {
                    cx = new CX(65536, 1);
                }

                /* 6.2.5.7 - 2) */
                regionBitmap = new Bitmap(regionInfo.getBitmapWidth(),
                        regionInfo.getBitmapHeight());

                final int paddedWidth = (regionBitmap.getWidth() + 7) & -8;

                /* 6.2.5.7 - 3 */
                for (int line = 0; line < regionBitmap.getHeight(); line++)
                {

                    /* 6.2.5.7 - 3 b) */
                    if (isTPGDon)
                    {
                        ltp ^= decodeSLTP();
                    }

                    /* 6.2.5.7 - 3 c) */
                    if (ltp == 1)
                    {
                        if (line > 0)
                        {
                            copyLineAbove(line);
                        }
                    }
                    else
                    {
                        /* 3 d) */
                        // NOT USED ATM - If corresponding pixel of SKIP bitmap is 0, set
                        // current pixel to 0. Something like that:
                        // if (useSkip) {
                        // for (int i = 1; i < rowstride; i++) {
                        // if (skip[pixel] == 1) {
                        // gbReg[pixel] = 0;
                        // }
                        // pixel++;
                        // }
                        // } else {
                        decodeLine(line, regionBitmap.getWidth(), regionBitmap.getRowStride(),
                                paddedWidth);
                        // }
                    }
                }
            }
        }

        // if (JBIG2ImageReader.DEBUG)
        // if (header != null && header.getSegmentNr() == 3)
        // new Testbild(gbReg.getByteArray(), (int) gbReg.getWidth(), (int) gbReg.getHeight(),
        // gbReg.getRowStride());

        /* 4 */
        return regionBitmap;
    }

    private int decodeSLTP() throws IOException
    {
        switch (gbTemplate)
        {
        case 0:
            cx.setIndex(0x9b25);
            break;
        case 1:
            cx.setIndex(0x795);
            break;
        case 2:
            cx.setIndex(0xe5);
            break;
        case 3:
            cx.setIndex(0x195);
            break;
        }
        return arithDecoder.decode(cx);
    }

    private void decodeLine(final int lineNumber, final int width, final int rowStride,
            final int paddedWidth) throws IOException
    {
        final int byteIndex = regionBitmap.getByteIndex(0, lineNumber);
        final int idx = byteIndex - rowStride;

        switch (gbTemplate)
        {
        case 0:
            if (!useExtTemplates)
            {
                decodeTemplate0a(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
            }
            else
            {
                decodeTemplate0b(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
            }
            break;
        case 1:
            decodeTemplate1(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
            break;
        case 2:
            decodeTemplate2(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
            break;
        case 3:
            decodeTemplate3(lineNumber, width, rowStride, paddedWidth, byteIndex, idx);
            break;
        }
    }

    /**
     * Each pixel gets the value from the corresponding pixel of the row above. Line 0 cannot get copied values (source
     * will be -1, doesn't exist).
     * 
     * @param lineNumber - Coordinate of the row that should be set.
     */
    private void copyLineAbove(final int lineNumber)
    {
        int targetByteIndex = lineNumber * regionBitmap.getRowStride();
        int sourceByteIndex = targetByteIndex - regionBitmap.getRowStride();

        for (int i = 0; i < regionBitmap.getRowStride(); i++)
        {
            // Get the byte that should be copied and put it into Bitmap
            regionBitmap.setByte(targetByteIndex++, regionBitmap.getByte(sourceByteIndex++));
        }
    }

    private void decodeTemplate0a(final int lineNumber, final int width, final int rowStride,
            final int paddedWidth, int byteIndex, int idx) throws IOException
    {
        int context;
        int overriddenContext = 0;

        int line1 = 0;
        int line2 = 0;

        if (lineNumber >= 1)
        {
            line1 = regionBitmap.getByteAsInteger(idx);
        }

        if (lineNumber >= 2)
        {
            line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 6;
        }

        context = (line1 & 0xf0) | (line2 & 0x3800);

        int nextByte;
        for (int x = 0; x < paddedWidth; x = nextByte)
        {
            /* 6.2.5.7 3d */
            byte result = 0;
            nextByte = x + 8;
            final int minorWidth = width - x > 8 ? 8 : width - x;

            if (lineNumber > 0)
            {
                line1 = (line1 << 8)
                        | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
            }

            if (lineNumber > 1)
            {
                line2 = (line2 << 8) | (nextByte < width
                        ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 6 : 0);
            }

            for (int minorX = 0; minorX < minorWidth; minorX++)
            {
                final int toShift = 7 - minorX;
                if (override)
                {
                    overriddenContext = overrideAtTemplate0a(context, (x + minorX), lineNumber,
                            result, minorX, toShift);
                    cx.setIndex(overriddenContext);
                }
                else
                {
                    cx.setIndex(context);
                }

                int bit = arithDecoder.decode(cx);

                result |= bit << toShift;

                context = ((context & 0x7bf7) << 1) | bit | ((line1 >> toShift) & 0x10)
                        | ((line2 >> toShift) & 0x800);
            }

            regionBitmap.setByte(byteIndex++, result);
            idx++;
        }
    }

    private void decodeTemplate0b(final int lineNumber, final int width, final int rowStride,
            final int paddedWidth, int byteIndex, int idx) throws IOException
    {
        int context;
        int overriddenContext = 0;

        int line1 = 0;
        int line2 = 0;

        if (lineNumber >= 1)
        {
            line1 = regionBitmap.getByteAsInteger(idx);
        }

        if (lineNumber >= 2)
        {
            line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 6;
        }

        context = (line1 & 0xf0) | (line2 & 0x3800);

        int nextByte;
        for (int x = 0; x < paddedWidth; x = nextByte)
        {
            /* 6.2.5.7 3d */
            byte result = 0;
            nextByte = x + 8;
            final int minorWidth = width - x > 8 ? 8 : width - x;

            if (lineNumber > 0)
            {
                line1 = (line1 << 8)
                        | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
            }

            if (lineNumber > 1)
            {
                line2 = (line2 << 8) | (nextByte < width
                        ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 6 : 0);
            }

            for (int minorX = 0; minorX < minorWidth; minorX++)
            {
                final int toShift = 7 - minorX;
                if (override)
                {
                    overriddenContext = overrideAtTemplate0b(context, (x + minorX), lineNumber,
                            result, minorX, toShift);
                    cx.setIndex(overriddenContext);
                }
                else
                {
                    cx.setIndex(context);
                }

                final int bit = arithDecoder.decode(cx);

                result |= bit << toShift;

                context = ((context & 0x7bf7) << 1) | bit | ((line1 >> toShift) & 0x10)
                        | ((line2 >> toShift) & 0x800);
            }

            regionBitmap.setByte(byteIndex++, result);
            idx++;
        }
    }

    private void decodeTemplate1(final int lineNumber, int width, final int rowStride,
            final int paddedWidth, int byteIndex, int idx) throws IOException
    {
        int context;
        int overriddenContext;

        int line1 = 0;
        int line2 = 0;

        if (lineNumber >= 1)
        {
            line1 = regionBitmap.getByteAsInteger(idx);
        }

        if (lineNumber >= 2)
        {
            line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 5;
        }

        context = ((line1 >> 1) & 0x1f8) | ((line2 >> 1) & 0x1e00);

        int nextByte;
        for (int x = 0; x < paddedWidth; x = nextByte)
        {
            /* 6.2.5.7 3d */
            byte result = 0;
            nextByte = x + 8;
            final int minorWidth = width - x > 8 ? 8 : width - x;

            if (lineNumber >= 1)
            {
                line1 = (line1 << 8)
                        | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
            }

            if (lineNumber >= 2)
            {
                line2 = (line2 << 8) | (nextByte < width
                        ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 5 : 0);
            }

            for (int minorX = 0; minorX < minorWidth; minorX++)
            {
                if (override)
                {
                    overriddenContext = overrideAtTemplate1(context, x + minorX, lineNumber, result,
                            minorX);
                    cx.setIndex(overriddenContext);
                }
                else
                {
                    cx.setIndex(context);
                }

                final int bit = arithDecoder.decode(cx);

                result |= bit << 7 - minorX;

                final int toShift = 8 - minorX;
                context = ((context & 0xefb) << 1) | bit | ((line1 >> toShift) & 0x8)
                        | ((line2 >> toShift) & 0x200);
            }

            regionBitmap.setByte(byteIndex++, result);
            idx++;
        }
    }

    private void decodeTemplate2(final int lineNumber, final int width, final int rowStride,
            final int paddedWidth, int byteIndex, int idx) throws IOException
    {
        int context;
        int overriddenContext;

        int line1 = 0;
        int line2 = 0;

        if (lineNumber >= 1)
        {
            line1 = regionBitmap.getByteAsInteger(idx);
        }

        if (lineNumber >= 2)
        {
            line2 = regionBitmap.getByteAsInteger(idx - rowStride) << 4;
        }

        context = ((line1 >> 3) & 0x7c) | ((line2 >> 3) & 0x380);

        int nextByte;
        for (int x = 0; x < paddedWidth; x = nextByte)
        {
            /* 6.2.5.7 3d */
            byte result = 0;
            nextByte = x + 8;
            final int minorWidth = width - x > 8 ? 8 : width - x;

            if (lineNumber >= 1)
            {
                line1 = (line1 << 8)
                        | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
            }

            if (lineNumber >= 2)
            {
                line2 = (line2 << 8) | (nextByte < width
                        ? regionBitmap.getByteAsInteger(idx - rowStride + 1) << 4 : 0);
            }

            for (int minorX = 0; minorX < minorWidth; minorX++)
            {

                if (override)
                {
                    overriddenContext = overrideAtTemplate2(context, x + minorX, lineNumber, result,
                            minorX);
                    cx.setIndex(overriddenContext);
                }
                else
                {
                    cx.setIndex(context);
                }

                final int bit = arithDecoder.decode(cx);

                result |= bit << (7 - minorX);

                final int toShift = 10 - minorX;
                context = ((context & 0x1bd) << 1) | bit | ((line1 >> toShift) & 0x4)
                        | ((line2 >> toShift) & 0x80);
            }

            regionBitmap.setByte(byteIndex++, result);
            idx++;
        }
    }

    private void decodeTemplate3(final int lineNumber, final int width, final int rowStride,
            final int paddedWidth, int byteIndex, int idx) throws IOException
    {
        int context;
        int overriddenContext;

        int line1 = 0;

        if (lineNumber >= 1)
        {
            line1 = regionBitmap.getByteAsInteger(idx);
        }

        context = (line1 >> 1) & 0x70;

        int nextByte;
        for (int x = 0; x < paddedWidth; x = nextByte)
        {
            /* 6.2.5.7 3d */
            byte result = 0;
            nextByte = x + 8;
            final int minorWidth = width - x > 8 ? 8 : width - x;

            if (lineNumber >= 1)
            {
                line1 = (line1 << 8)
                        | (nextByte < width ? regionBitmap.getByteAsInteger(idx + 1) : 0);
            }

            for (int minorX = 0; minorX < minorWidth; minorX++)
            {

                if (override)
                {
                    overriddenContext = overrideAtTemplate3(context, x + minorX, lineNumber, result,
                            minorX);
                    cx.setIndex(overriddenContext);
                }
                else
                {
                    cx.setIndex(context);
                }

                final int bit = arithDecoder.decode(cx);

                result |= bit << (7 - minorX);
                context = ((context & 0x1f7) << 1) | bit | ((line1 >> (8 - minorX)) & 0x010);
            }

            regionBitmap.setByte(byteIndex++, result);
            idx++;
        }
    }

    private void updateOverrideFlags()
    {
        if (gbAtX == null || gbAtY == null)
        {
            log.info("AT pixels not set");
            return;
        }

        if (gbAtX.length != gbAtY.length)
        {
            log.info("AT pixel inconsistent, amount of x pixels: " + gbAtX.length
                    + ", amount of y pixels:" + gbAtY.length);
            return;
        }

        gbAtOverride = new boolean[gbAtX.length];

        switch (gbTemplate)
        {
        case 0:
            if (!useExtTemplates)
            {
                if (gbAtX[0] != 3 || gbAtY[0] != -1)
                    setOverrideFlag(0);

                if (gbAtX[1] != -3 || gbAtY[1] != -1)
                    setOverrideFlag(1);

                if (gbAtX[2] != 2 || gbAtY[2] != -2)
                    setOverrideFlag(2);

                if (gbAtX[3] != -2 || gbAtY[3] != -2)
                    setOverrideFlag(3);

            }
            else
            {
                if (gbAtX[0] != -2 || gbAtY[0] != 0)
                    setOverrideFlag(0);

                if (gbAtX[1] != 0 || gbAtY[1] != -2)
                    setOverrideFlag(1);

                if (gbAtX[2] != -2 || gbAtY[2] != -1)
                    setOverrideFlag(2);

                if (gbAtX[3] != -1 || gbAtY[3] != -2)
                    setOverrideFlag(3);

                if (gbAtX[4] != 1 || gbAtY[4] != -2)
                    setOverrideFlag(4);

                if (gbAtX[5] != 2 || gbAtY[5] != -1)
                    setOverrideFlag(5);

                if (gbAtX[6] != -3 || gbAtY[6] != 0)
                    setOverrideFlag(6);

                if (gbAtX[7] != -4 || gbAtY[7] != 0)
                    setOverrideFlag(7);

                if (gbAtX[8] != 2 || gbAtY[8] != -2)
                    setOverrideFlag(8);

                if (gbAtX[9] != 3 || gbAtY[9] != -1)
                    setOverrideFlag(9);

                if (gbAtX[10] != -2 || gbAtY[10] != -2)
                    setOverrideFlag(10);

                if (gbAtX[11] != -3 || gbAtY[11] != -1)
                    setOverrideFlag(11);
            }
            break;
        case 1:
            if (gbAtX[0] != 3 || gbAtY[0] != -1)
                setOverrideFlag(0);
            break;
        case 2:
            if (gbAtX[0] != 2 || gbAtY[0] != -1)
                setOverrideFlag(0);
            break;
        case 3:
            if (gbAtX[0] != 2 || gbAtY[0] != -1)
                setOverrideFlag(0);
            break;
        }

    }

    private void setOverrideFlag(final int index)
    {
        gbAtOverride[index] = true;
        override = true;
    }

    private int overrideAtTemplate0a(int context, final int x, final int y, final int result,
            final int minorX, final int toShift) throws IOException
    {
        if (gbAtOverride[0])
        {
            context &= 0xffef;
            if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
                context |= (result >> (toShift - gbAtX[0]) & 0x1) << 4;
            else
                context |= getPixel(x + gbAtX[0], y + gbAtY[0]) << 4;
        }

        if (gbAtOverride[1])
        {
            context &= 0xfbff;
            if (gbAtY[1] == 0 && gbAtX[1] >= -minorX)
                context |= (result >> (toShift - gbAtX[1]) & 0x1) << 10;
            else
                context |= getPixel(x + gbAtX[1], y + gbAtY[1]) << 10;
        }

        if (gbAtOverride[2])
        {
            context &= 0xf7ff;
            if (gbAtY[2] == 0 && gbAtX[2] >= -minorX)
                context |= (result >> (toShift - gbAtX[2]) & 0x1) << 11;
            else
                context |= getPixel(x + gbAtX[2], y + gbAtY[2]) << 11;
        }

        if (gbAtOverride[3])
        {
            context &= 0x7fff;
            if (gbAtY[3] == 0 && gbAtX[3] >= -minorX)
                context |= (result >> (toShift - gbAtX[3]) & 0x1) << 15;
            else
                context |= getPixel(x + gbAtX[3], y + gbAtY[3]) << 15;
        }
        return context;
    }

    private int overrideAtTemplate0b(int context, final int x, final int y, final int result,
            final int minorX, final int toShift) throws IOException
    {
        if (gbAtOverride[0])
        {
            context &= 0xfffd;
            if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
                context |= (result >> (toShift - gbAtX[0]) & 0x1) << 1;
            else
                context |= getPixel(x + gbAtX[0], y + gbAtY[0]) << 1;
        }

        if (gbAtOverride[1])
        {
            context &= 0xdfff;
            if (gbAtY[1] == 0 && gbAtX[1] >= -minorX)
                context |= (result >> (toShift - gbAtX[1]) & 0x1) << 13;
            else
                context |= getPixel(x + gbAtX[1], y + gbAtY[1]) << 13;
        }
        if (gbAtOverride[2])
        {
            context &= 0xfdff;
            if (gbAtY[2] == 0 && gbAtX[2] >= -minorX)
                context |= (result >> (toShift - gbAtX[2]) & 0x1) << 9;
            else
                context |= getPixel(x + gbAtX[2], y + gbAtY[2]) << 9;
        }
        if (gbAtOverride[3])
        {
            context &= 0xbfff;
            if (gbAtY[3] == 0 && gbAtX[3] >= -minorX)
                context |= (result >> (toShift - gbAtX[3]) & 0x1) << 14;
            else
                context |= getPixel(x + gbAtX[3], y + gbAtY[3]) << 14;
        }
        if (gbAtOverride[4])
        {
            context &= 0xefff;
            if (gbAtY[4] == 0 && gbAtX[4] >= -minorX)
                context |= (result >> (toShift - gbAtX[4]) & 0x1) << 12;
            else
                context |= getPixel(x + gbAtX[4], y + gbAtY[4]) << 12;
        }
        if (gbAtOverride[5])
        {
            context &= 0xffdf;
            if (gbAtY[5] == 0 && gbAtX[5] >= -minorX)
                context |= (result >> (toShift - gbAtX[5]) & 0x1) << 5;
            else
                context |= getPixel(x + gbAtX[5], y + gbAtY[5]) << 5;
        }
        if (gbAtOverride[6])
        {
            context &= 0xfffb;
            if (gbAtY[6] == 0 && gbAtX[6] >= -minorX)
                context |= (result >> (toShift - gbAtX[6]) & 0x1) << 2;
            else
                context |= getPixel(x + gbAtX[6], y + gbAtY[6]) << 2;
        }
        if (gbAtOverride[7])
        {
            context &= 0xfff7;
            if (gbAtY[7] == 0 && gbAtX[7] >= -minorX)
                context |= (result >> (toShift - gbAtX[7]) & 0x1) << 3;
            else
                context |= getPixel(x + gbAtX[7], y + gbAtY[7]) << 3;
        }
        if (gbAtOverride[8])
        {
            context &= 0xf7ff;
            if (gbAtY[8] == 0 && gbAtX[8] >= -minorX)
                context |= (result >> (toShift - gbAtX[8]) & 0x1) << 11;
            else
                context |= getPixel(x + gbAtX[8], y + gbAtY[8]) << 11;
        }
        if (gbAtOverride[9])
        {
            context &= 0xffef;
            if (gbAtY[9] == 0 && gbAtX[9] >= -minorX)
                context |= (result >> (toShift - gbAtX[9]) & 0x1) << 4;
            else
                context |= getPixel(x + gbAtX[9], y + gbAtY[9]) << 4;
        }
        if (gbAtOverride[10])
        {
            context &= 0x7fff;
            if (gbAtY[10] == 0 && gbAtX[10] >= -minorX)
                context |= (result >> (toShift - gbAtX[10]) & 0x1) << 15;
            else
                context |= getPixel(x + gbAtX[10], y + gbAtY[10]) << 15;
        }
        if (gbAtOverride[11])
        {
            context &= 0xfdff;
            if (gbAtY[11] == 0 && gbAtX[11] >= -minorX)
                context |= (result >> (toShift - gbAtX[11]) & 0x1) << 10;
            else
                context |= getPixel(x + gbAtX[11], y + gbAtY[11]) << 10;
        }

        return context;
    }

    private int overrideAtTemplate1(int context, final int x, final int y, final int result,
            final int minorX) throws IOException
    {
        context &= 0x1ff7;
        if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
            return (context | (result >> (7 - (minorX + gbAtX[0])) & 0x1) << 3);
        else
            return (context | getPixel(x + gbAtX[0], y + gbAtY[0]) << 3);
    }

    private int overrideAtTemplate2(int context, final int x, final int y, final int result,
            final int minorX) throws IOException
    {
        context &= 0x3fb;
        if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
            return (context | (result >> (7 - (minorX + gbAtX[0])) & 0x1) << 2);
        else
            return (context | getPixel(x + gbAtX[0], y + gbAtY[0]) << 2);
    }

    private int overrideAtTemplate3(int context, final int x, final int y, final int result,
            final int minorX) throws IOException
    {
        context &= 0x3ef;
        if (gbAtY[0] == 0 && gbAtX[0] >= -minorX)
            return (context | (result >> (7 - (minorX + gbAtX[0])) & 0x1) << 4);
        else
            return (context | getPixel(x + gbAtX[0], y + gbAtY[0]) << 4);
    }

    private byte getPixel(final int x, final int y) throws IOException
    {
        if (x < 0 || x >= regionBitmap.getWidth())
            return 0;

        if (y < 0 || y >= regionBitmap.getHeight())
            return 0;

        return regionBitmap.getPixel(x, y);
    }

    /**
     * Used by {@link SymbolDictionary}.
     * 
     * @param isMMREncoded the data is MMR encoded
     * @param dataOffset the offset
     * @param dataLength the length of the data
     * @param gbh bitmap height
     * @param gbw bitmap width
     */
    protected void setParameters(final boolean isMMREncoded, final long dataOffset,
            final long dataLength, final int gbh, final int gbw)
    {
        this.isMMREncoded = isMMREncoded;
        this.dataOffset = dataOffset;
        this.dataLength = dataLength;
        this.regionInfo.setBitmapHeight(gbh);
        this.regionInfo.setBitmapWidth(gbw);

        this.mmrDecompressor = null;
        resetBitmap();
    }

    /**
     * @param isMMREncoded the data is MMR encoded
     * @param sdTemplate sd template
     * @param isTPGDon is TPGDon
     * @param useSkip use skip
     * @param sdATX x values gbA pixels
     * @param sdATY y values gbA pixels
     * @param symWidth bitmap width
     * @param hcHeight bitmap height
     * @param cx context for the arithmetic decoder
     * @param arithmeticDecoder the arithmetic decode to be used
     * 
     * Used by {@link SymbolDictionary}.
     */
    protected void setParameters(final boolean isMMREncoded, final byte sdTemplate,
            final boolean isTPGDon, final boolean useSkip, final short[] sdATX, final short[] sdATY,
            final int symWidth, final int hcHeight, final CX cx,
            final ArithmeticDecoder arithmeticDecoder)
    {
        this.isMMREncoded = isMMREncoded;
        this.gbTemplate = sdTemplate;
        this.isTPGDon = isTPGDon;
        this.gbAtX = sdATX;
        this.gbAtY = sdATY;
        this.regionInfo.setBitmapWidth(symWidth);
        this.regionInfo.setBitmapHeight(hcHeight);
        if (null != cx)
            this.cx = cx;
        if (null != arithmeticDecoder)
            this.arithDecoder = arithmeticDecoder;

        this.mmrDecompressor = null;
        resetBitmap();
    }

    /**
     * Used by {@link PatternDictionary} and {@link HalftoneRegion}.
     * 
     * @param isMMREncoded the data is MMR encoded
     * @param dataOffset the offset
     * @param dataLength the length of the data
     * @param gbh bitmap height
     * @param gbw bitmap width
     * @param gbTemplate gb template
     * @param isTPGDon is TPGDon
     * @param useSkip use skip
     * @param gbAtX x values of gbA pixels
     * @param gbAtY y values of gbA pixels
     * 
     */
    protected void setParameters(final boolean isMMREncoded, final long dataOffset,
            final long dataLength, final int gbh, final int gbw, final byte gbTemplate,
            final boolean isTPGDon, final boolean useSkip, final short[] gbAtX, final short[] gbAtY)
    {
        this.dataOffset = dataOffset;
        this.dataLength = dataLength;

        this.regionInfo = new RegionSegmentInformation();
        this.regionInfo.setBitmapHeight(gbh);
        this.regionInfo.setBitmapWidth(gbw);
        this.gbTemplate = gbTemplate;

        this.isMMREncoded = isMMREncoded;
        this.isTPGDon = isTPGDon;
        this.gbAtX = gbAtX;
        this.gbAtY = gbAtY;
    }

    /**
     * Simply sets the memory-critical bitmap of this region to {@code null}.
     */
    protected void resetBitmap()
    {
        this.regionBitmap = null;
    }

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

    public RegionSegmentInformation getRegionInfo()
    {
        return regionInfo;
    }

    protected boolean useExtTemplates()
    {
        return useExtTemplates;
    }

    protected boolean isTPGDon()
    {
        return isTPGDon;
    }

    protected byte getGbTemplate()
    {
        return gbTemplate;
    }

    protected boolean isMMREncoded()
    {
        return isMMREncoded;
    }

    protected short[] getGbAtX()
    {
        return gbAtX;
    }

    protected short[] getGbAtY()
    {
        return gbAtY;
    }
}
