/**
 * 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.decoder.arithmetic;

import java.io.IOException;

import javax.imageio.stream.ImageInputStream;

/**
 * This class represents the arithmetic decoder, described in ISO/IEC 14492:2001 in E.3
 */
public class ArithmeticDecoder
{

    private static final int QE[][] = { { 0x5601, 1, 1, 1 }, { 0x3401, 2, 6, 0 },
            { 0x1801, 3, 9, 0 }, { 0x0AC1, 4, 12, 0 }, { 0x0521, 5, 29, 0 }, { 0x0221, 38, 33, 0 },
            { 0x5601, 7, 6, 1 }, { 0x5401, 8, 14, 0 }, { 0x4801, 9, 14, 0 }, { 0x3801, 10, 14, 0 },
            { 0x3001, 11, 17, 0 }, { 0x2401, 12, 18, 0 }, { 0x1C01, 13, 20, 0 },
            { 0x1601, 29, 21, 0 }, { 0x5601, 15, 14, 1 }, { 0x5401, 16, 14, 0 },
            { 0x5101, 17, 15, 0 }, { 0x4801, 18, 16, 0 }, { 0x3801, 19, 17, 0 },
            { 0x3401, 20, 18, 0 }, { 0x3001, 21, 19, 0 }, { 0x2801, 22, 19, 0 },
            { 0x2401, 23, 20, 0 }, { 0x2201, 24, 21, 0 }, { 0x1C01, 25, 22, 0 },
            { 0x1801, 26, 23, 0 }, { 0x1601, 27, 24, 0 }, { 0x1401, 28, 25, 0 },
            { 0x1201, 29, 26, 0 }, { 0x1101, 30, 27, 0 }, { 0x0AC1, 31, 28, 0 },
            { 0x09C1, 32, 29, 0 }, { 0x08A1, 33, 30, 0 }, { 0x0521, 34, 31, 0 },
            { 0x0441, 35, 32, 0 }, { 0x02A1, 36, 33, 0 }, { 0x0221, 37, 34, 0 },
            { 0x0141, 38, 35, 0 }, { 0x0111, 39, 36, 0 }, { 0x0085, 40, 37, 0 },
            { 0x0049, 41, 38, 0 }, { 0x0025, 42, 39, 0 }, { 0x0015, 43, 40, 0 },
            { 0x0009, 44, 41, 0 }, { 0x0005, 45, 42, 0 }, { 0x0001, 45, 43, 0 },
            { 0x5601, 46, 46, 0 } };

    private int a;
    private long c;
    private int ct;

    private int b;

    private long streamPos0;

    private final ImageInputStream iis;

    public ArithmeticDecoder(ImageInputStream iis) throws IOException
    {
        this.iis = iis;
        init();
    }

    private void init() throws IOException
    {
        this.streamPos0 = iis.getStreamPosition();
        b = this.iis.read();

        c = b << 16;

        byteIn();

        c <<= 7;
        ct -= 7;
        a = 0x8000;
    }

    public int decode(CX cx) throws IOException
    {
        int d;
        final int qeValue = QE[cx.cx()][0];
        final int icx = cx.cx();

        a -= qeValue;

        if ((c >> 16) < qeValue)
        {
            d = lpsExchange(cx, icx, qeValue);
            renormalize();
        }
        else
        {
            c -= (qeValue << 16);
            if ((a & 0x8000) == 0)
            {
                d = mpsExchange(cx, icx);
                renormalize();
            }
            else
            {
                return cx.mps();
            }
        }

        return d;
    }

    private void byteIn() throws IOException
    {
        if (iis.getStreamPosition() > streamPos0)
        {
            iis.seek(iis.getStreamPosition() - 1);
        }

        b = iis.read();

        if (b == 0xFF)
        {
            final int b1 = iis.read();
            if (b1 > 0x8f)
            {
                c += 0xff00;
                ct = 8;
                iis.seek(iis.getStreamPosition() - 2);
            }
            else
            {
                c += b1 << 9;
                ct = 7;
            }
        }
        else
        {
            b = iis.read();
            c += b << 8;
            ct = 8;
        }

        c &= 0xffffffffL;
    }

    private void renormalize() throws IOException
    {
        do
        {
            if (ct == 0)
            {
                byteIn();
            }

            a <<= 1;
            c <<= 1;
            ct--;

        } while ((a & 0x8000) == 0);

        c &= 0xffffffffL;
    }

    private int mpsExchange(CX cx, int icx)
    {
        final int mps = cx.mps();

        if (a < QE[icx][0])
        {

            if (QE[icx][3] == 1)
            {
                cx.toggleMps();
            }

            cx.setCx(QE[icx][2]);
            return 1 - mps;
        }
        else
        {
            cx.setCx(QE[icx][1]);
            return mps;
        }
    }

    private int lpsExchange(CX cx, int icx, int qeValue)
    {
        final int mps = cx.mps();

        if (a < qeValue)
        {
            cx.setCx(QE[icx][1]);
            a = qeValue;

            return mps;
        }
        else
        {
            if (QE[icx][3] == 1)
            {
                cx.toggleMps();
            }

            cx.setCx(QE[icx][2]);
            a = qeValue;
            return 1 - mps;
        }
    }

    int getA()
    {
        return a;
    }

    long getC()
    {
        return c;
    }
}
