export const dctZigZag = new Int32Array([
    0,
    1, 8,
    16, 9, 2,
    3, 10, 17, 24,
    32, 25, 18, 11, 4,
    5, 12, 19, 26, 33, 40,
    48, 41, 34, 27, 20, 13, 6,
    7, 14, 21, 28, 35, 42, 49, 56,
    57, 50, 43, 36, 29, 22, 15,
    23, 30, 37, 44, 51, 58,
    59, 52, 45, 38, 31,
    39, 46, 53, 60,
    61, 54, 47,
    55, 62,
    63
]);

/**
 *
 * @param {Uint8Array} data
 * @param {number} offset
 * @param {JpegFrame} frame
 * @param {JpegFrameComponent[]} components
 * @param {number} resetInterval
 * @param {number} spectralStart
 * @param {number} spectralEnd
 * @param {number} successivePrev
 * @param {number} successive
 * @param {boolean} [tolerantDecoding]
 * @return {number}
 */
export function decodeScan(
    data,
    offset,
    frame,
    components,
    resetInterval,
    spectralStart,
    spectralEnd,
    successivePrev,
    successive,
    tolerantDecoding = false
) {
    const mcusPerLine = frame.mcusPerLine;
    const progressive = frame.progressive;

    const startOffset = offset;
    let bitsData = 0, bitsCount = 0;

    /**
     *
     * @return {number}
     */
    function readBit() {

        if (bitsCount > 0) {
            bitsCount--;
            return (bitsData >> bitsCount) & 1;
        }

        bitsData = data[offset++];

        if (bitsData === 0xFF) {
            const nextByte = data[offset++];

            if (nextByte) {
                throw new Error("unexpected marker: " + ((bitsData << 8) | nextByte).toString(16));
            }
            // unstuff 0
        }

        bitsCount = 7;

        return bitsData >>> 7;
    }

    function decodeHuffman(tree) {
        let node = tree, bit;
        while ((bit = readBit()) !== null) {
            node = node[bit];
            if (typeof node === 'number')
                return node;
            if (typeof node !== 'object')
                throw new Error("invalid huffman sequence");
        }
        return null;
    }

    function receive(length) {
        let n = 0;
        while (length > 0) {
            const bit = readBit();
            if (bit === null) return;
            n = (n << 1) | bit;
            length--;
        }
        return n;
    }

    function receiveAndExtend(length) {
        const n = receive(length);
        if (n >= 1 << (length - 1))
            return n;
        return n + (-1 << length) + 1;
    }

    /**
     *
     * @param {JpegFrameComponent} component
     * @param {number[]} zz
     */
    function decodeBaseline(component, zz) {
        const t = decodeHuffman(component.huffmanTableDC);
        const diff = t === 0 ? 0 : receiveAndExtend(t);
        zz[0] = (component.pred += diff);
        let k = 1;
        while (k < 64) {
            const rs = decodeHuffman(component.huffmanTableAC);
            const s = rs & 15, r = rs >> 4;
            if (s === 0) {
                if (r < 15)
                    break;
                k += 16;
                continue;
            }
            k += r;
            const z = dctZigZag[k];
            zz[z] = receiveAndExtend(s);
            k++;
        }
    }

    function decodeDCFirst(component, zz) {
        const t = decodeHuffman(component.huffmanTableDC);
        const diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
        zz[0] = (component.pred += diff);
    }

    function decodeDCSuccessive(component, zz) {
        zz[0] |= readBit() << successive;
    }

    let eobrun = 0;

    function decodeACFirst(component, zz) {
        if (eobrun > 0) {
            eobrun--;
            return;
        }
        let k = spectralStart;
        while (k <= spectralEnd) {
            const rs = decodeHuffman(component.huffmanTableAC);
            const s = rs & 15, r = rs >> 4;
            if (s === 0) {
                if (r < 15) {
                    eobrun = receive(r) + (1 << r) - 1;
                    break;
                }
                k += 16;
                continue;
            }
            k += r;
            const z = dctZigZag[k];
            zz[z] = receiveAndExtend(s) * (1 << successive);
            k++;
        }
    }

    let successiveACState = 0, successiveACNextValue;

    function decodeACSuccessive(component, zz) {
        let k = spectralStart, r = 0;
        while (k <= spectralEnd) {
            const z = dctZigZag[k];
            const direction = zz[z] < 0 ? -1 : 1;
            switch (successiveACState) {
                case 0: // initial state
                    const rs = decodeHuffman(component.huffmanTableAC);
                    let s = rs & 15;
                    r = rs >> 4;

                    if (s === 0) {
                        if (r < 15) {
                            eobrun = receive(r) + (1 << r);
                            successiveACState = 4;
                        } else {
                            r = 16;
                            successiveACState = 1;
                        }
                    } else {
                        if (s !== 1)
                            throw new Error("invalid ACn encoding");
                        successiveACNextValue = receiveAndExtend(s);
                        successiveACState = r ? 2 : 3;
                    }
                    continue;
                case 1: // skipping r zero items
                case 2:
                    if (zz[z])
                        zz[z] += (readBit() << successive) * direction;
                    else {
                        r--;
                        if (r === 0)
                            successiveACState = successiveACState === 2 ? 3 : 0;
                    }
                    break;
                case 3: // set value for a zero item
                    if (zz[z])
                        zz[z] += (readBit() << successive) * direction;
                    else {
                        zz[z] = successiveACNextValue << successive;
                        successiveACState = 0;
                    }
                    break;
                case 4: // eob
                    if (zz[z])
                        zz[z] += (readBit() << successive) * direction;
                    break;
            }
            k++;
        }
        if (successiveACState === 4) {
            eobrun--;
            if (eobrun === 0)
                successiveACState = 0;
        }
    }

    /**
     *
     * @param {JpegFrameComponent} component
     * @param {function} decode
     * @param {number} mcu
     * @param {number} row
     * @param {number} col
     */
    function decodeMcu(component, decode, mcu, row, col) {
        const mcuRow = (mcu / mcusPerLine) | 0;
        const mcuCol = mcu % mcusPerLine;
        const blockRow = mcuRow * component.v + row;
        const blockCol = mcuCol * component.h + col;

        if (component.blocks[blockRow] === undefined && tolerantDecoding) {
            // If the block is missing and we're in tolerant mode, just skip it.
            return;
        }

        decode(component, component.blocks[blockRow][blockCol]);
    }

    /**
     *
     * @param {JpegFrameComponent} component
     * @param {function} decode
     * @param {number} mcu
     */
    function decodeBlock(component, decode, mcu) {
        const blockRow = (mcu / component.blocksPerLine) | 0;
        const blockCol = mcu % component.blocksPerLine;

        if (component.blocks[blockRow] === undefined && tolerantDecoding) {
            // If the block is missing and we're in tolerant mode, just skip it.
            return;
        }

        decode(component, component.blocks[blockRow][blockCol]);
    }

    const componentsLength = components.length;
    let component, i, j, k, n;
    let decodeFn;
    if (progressive) {
        if (spectralStart === 0)
            decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
        else
            decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
    } else {
        decodeFn = decodeBaseline;
    }

    let mcu = 0, marker;
    let mcuExpected;
    if (componentsLength === 1) {
        mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
    } else {
        mcuExpected = mcusPerLine * frame.mcusPerColumn;
    }
    if (!resetInterval) {
        resetInterval = mcuExpected;
    }

    let h, v;
    while (mcu < mcuExpected) {
        // reset interval stuff
        for (i = 0; i < componentsLength; i++)
            components[i].pred = 0;
        eobrun = 0;

        if (componentsLength === 1) {
            component = components[0];
            for (n = 0; n < resetInterval; n++) {
                decodeBlock(component, decodeFn, mcu);
                mcu++;
            }
        } else {
            for (n = 0; n < resetInterval; n++) {
                for (i = 0; i < componentsLength; i++) {
                    component = components[i];
                    h = component.h;
                    v = component.v;
                    for (j = 0; j < v; j++) {
                        for (k = 0; k < h; k++) {
                            decodeMcu(component, decodeFn, mcu, j, k);
                        }
                    }
                }
                mcu++;

                // If we've reached our expected MCU's, stop decoding
                if (mcu === mcuExpected) break;
            }
        }

        if (mcu === mcuExpected) {
            // Skip trailing bytes at the end of the scan - until we reach the next marker
            do {
                if (data[offset] === 0xFF) {
                    if (data[offset + 1] !== 0x00) {
                        break;
                    }
                }
                offset += 1;
            } while (offset < data.length - 2);
        }

        // find marker
        bitsCount = 0;
        marker = (data[offset] << 8) | data[offset + 1];
        if (marker < 0xFF00) {
            throw new Error("marker was not found");
        }

        if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
            offset += 2;
        } else
            break;
    }

    return offset - startOffset;
}