/* Copyright 2012 Mozilla Foundation
 *
 * Licensed 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.
 */

'use strict';

(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define('pdfjs/core/arithmetic_decoder', ['exports'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports);
    } else {
        factory((root.pdfjsCoreArithmeticDecoder = {}));
    }
}(this, function(exports) {

    /* This class implements the QM Coder decoding as defined in
     *   JPEG 2000 Part I Final Committee Draft Version 1.0
     *   Annex C.3 Arithmetic decoding procedure
     * available at http://www.jpeg.org/public/fcd15444-1.pdf
     *
     * The arithmetic decoder is used in conjunction with context models to decode
     * JPEG2000 and JBIG2 streams.
     */
    var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
        // Table C-2
        var QeTable = [
            {qe : 0x5601, nmps : 1, nlps : 1, switchFlag : 1},
            {qe : 0x3401, nmps : 2, nlps : 6, switchFlag : 0},
            {qe : 0x1801, nmps : 3, nlps : 9, switchFlag : 0},
            {qe : 0x0AC1, nmps : 4, nlps : 12, switchFlag : 0},
            {qe : 0x0521, nmps : 5, nlps : 29, switchFlag : 0},
            {qe : 0x0221, nmps : 38, nlps : 33, switchFlag : 0},
            {qe : 0x5601, nmps : 7, nlps : 6, switchFlag : 1},
            {qe : 0x5401, nmps : 8, nlps : 14, switchFlag : 0},
            {qe : 0x4801, nmps : 9, nlps : 14, switchFlag : 0},
            {qe : 0x3801, nmps : 10, nlps : 14, switchFlag : 0},
            {qe : 0x3001, nmps : 11, nlps : 17, switchFlag : 0},
            {qe : 0x2401, nmps : 12, nlps : 18, switchFlag : 0},
            {qe : 0x1C01, nmps : 13, nlps : 20, switchFlag : 0},
            {qe : 0x1601, nmps : 29, nlps : 21, switchFlag : 0},
            {qe : 0x5601, nmps : 15, nlps : 14, switchFlag : 1},
            {qe : 0x5401, nmps : 16, nlps : 14, switchFlag : 0},
            {qe : 0x5101, nmps : 17, nlps : 15, switchFlag : 0},
            {qe : 0x4801, nmps : 18, nlps : 16, switchFlag : 0},
            {qe : 0x3801, nmps : 19, nlps : 17, switchFlag : 0},
            {qe : 0x3401, nmps : 20, nlps : 18, switchFlag : 0},
            {qe : 0x3001, nmps : 21, nlps : 19, switchFlag : 0},
            {qe : 0x2801, nmps : 22, nlps : 19, switchFlag : 0},
            {qe : 0x2401, nmps : 23, nlps : 20, switchFlag : 0},
            {qe : 0x2201, nmps : 24, nlps : 21, switchFlag : 0},
            {qe : 0x1C01, nmps : 25, nlps : 22, switchFlag : 0},
            {qe : 0x1801, nmps : 26, nlps : 23, switchFlag : 0},
            {qe : 0x1601, nmps : 27, nlps : 24, switchFlag : 0},
            {qe : 0x1401, nmps : 28, nlps : 25, switchFlag : 0},
            {qe : 0x1201, nmps : 29, nlps : 26, switchFlag : 0},
            {qe : 0x1101, nmps : 30, nlps : 27, switchFlag : 0},
            {qe : 0x0AC1, nmps : 31, nlps : 28, switchFlag : 0},
            {qe : 0x09C1, nmps : 32, nlps : 29, switchFlag : 0},
            {qe : 0x08A1, nmps : 33, nlps : 30, switchFlag : 0},
            {qe : 0x0521, nmps : 34, nlps : 31, switchFlag : 0},
            {qe : 0x0441, nmps : 35, nlps : 32, switchFlag : 0},
            {qe : 0x02A1, nmps : 36, nlps : 33, switchFlag : 0},
            {qe : 0x0221, nmps : 37, nlps : 34, switchFlag : 0},
            {qe : 0x0141, nmps : 38, nlps : 35, switchFlag : 0},
            {qe : 0x0111, nmps : 39, nlps : 36, switchFlag : 0},
            {qe : 0x0085, nmps : 40, nlps : 37, switchFlag : 0},
            {qe : 0x0049, nmps : 41, nlps : 38, switchFlag : 0},
            {qe : 0x0025, nmps : 42, nlps : 39, switchFlag : 0},
            {qe : 0x0015, nmps : 43, nlps : 40, switchFlag : 0},
            {qe : 0x0009, nmps : 44, nlps : 41, switchFlag : 0},
            {qe : 0x0005, nmps : 45, nlps : 42, switchFlag : 0},
            {qe : 0x0001, nmps : 45, nlps : 43, switchFlag : 0},
            {qe : 0x5601, nmps : 46, nlps : 46, switchFlag : 0}
        ];

        // C.3.5 Initialisation of the decoder (INITDEC)
        function ArithmeticDecoder(data, start, end) {
            this.data = data;
            this.bp = start;
            this.dataEnd = end;

            this.chigh = data[start];
            this.clow = 0;

            this.byteIn();

            this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
            this.clow = (this.clow << 7) & 0xFFFF;
            this.ct -= 7;
            this.a = 0x8000;
        }

        ArithmeticDecoder.prototype = {
            // C.3.4 Compressed data input (BYTEIN)
            byteIn  : function ArithmeticDecoder_byteIn() {
                var data = this.data;
                var bp = this.bp;
                if (data[bp] === 0xFF) {
                    var b1 = data[bp + 1];
                    if (b1 > 0x8F) {
                        this.clow += 0xFF00;
                        this.ct = 8;
                    } else {
                        bp++;
                        this.clow += (data[bp] << 9);
                        this.ct = 7;
                        this.bp = bp;
                    }
                } else {
                    bp++;
                    this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
                    this.ct = 8;
                    this.bp = bp;
                }
                if (this.clow > 0xFFFF) {
                    this.chigh += (this.clow >> 16);
                    this.clow &= 0xFFFF;
                }
            },
            // C.3.2 Decoding a decision (DECODE)
            readBit : function ArithmeticDecoder_readBit(contexts, pos) {
                // contexts are packed into 1 byte:
                // highest 7 bits carry cx.index, lowest bit carries cx.mps
                var cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1;
                var qeTableIcx = QeTable[cx_index];
                var qeIcx = qeTableIcx.qe;
                var d;
                var a = this.a - qeIcx;

                if (this.chigh < qeIcx) {
                    // exchangeLps
                    if (a < qeIcx) {
                        a = qeIcx;
                        d = cx_mps;
                        cx_index = qeTableIcx.nmps;
                    } else {
                        a = qeIcx;
                        d = 1 ^ cx_mps;
                        if (qeTableIcx.switchFlag === 1) {
                            cx_mps = d;
                        }
                        cx_index = qeTableIcx.nlps;
                    }
                } else {
                    this.chigh -= qeIcx;
                    if ((a & 0x8000) !== 0) {
                        this.a = a;
                        return cx_mps;
                    }
                    // exchangeMps
                    if (a < qeIcx) {
                        d = 1 ^ cx_mps;
                        if (qeTableIcx.switchFlag === 1) {
                            cx_mps = d;
                        }
                        cx_index = qeTableIcx.nlps;
                    } else {
                        d = cx_mps;
                        cx_index = qeTableIcx.nmps;
                    }
                }
                // C.3.3 renormD;
                do {
                    if (this.ct === 0) {
                        this.byteIn();
                    }

                    a <<= 1;
                    this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
                    this.clow = (this.clow << 1) & 0xFFFF;
                    this.ct--;
                } while ((a & 0x8000) === 0);
                this.a = a;

                contexts[pos] = cx_index << 1 | cx_mps;
                return d;
            }
        };

        return ArithmeticDecoder;
    })();

    exports.ArithmeticDecoder = ArithmeticDecoder;
}));
