// IDCT constants (20.12 fixed point format)
const dctCos1 = 4017;   // cos(pi/16)
const dctSin1 = 799;   // sin(pi/16)
const dctCos3 = 3406;   // cos(3*pi/16)
const dctSin3 = 2276;   // sin(3*pi/16)
const dctCos6 = 1567;   // cos(6*pi/16)
const dctSin6 = 3784;   // sin(6*pi/16)
const dctSqrt2 = 5793;   // sqrt(2)
const dctSqrt1d2 = 2896;  // sqrt(2) / 2

/**
 * Used internally for transformation
 * @type {Int32Array}
 */
const p = new Int32Array(64);

/**
 * Decode 8x8 block quantized DCT coefficients and inverse DCT transform.
 * This is a fixed-point implementation of the IDCT algorithm.
 *
 * This IDCT algorithm is taken from:
 *    Y. A. Reznik, A. T. Hinds, L. Yu, Z. Ni, and C-X. Zhang,
 *    "Efficient fixed-point approximations of the 8x8 inverse discrete
 *    cosine transform" (invited paper), Proc. SPIE Vol. 6696, Sep. 24, 2007.
 *
 * which is based on:
 *   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
 *   "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
 *   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
 *   988-991.
 *
 * The stage numbers mentioned in the comments refer to Figure 1 in the Loeffler paper.
 *
 * @param {Int32Array} qt quantization table
 * @param {Int32Array} zz zigzag-ordered coefficients
 * @param {Uint8Array|Uint8ClampedArray} dataOut
 */
export function idct8x8_fixed(
    qt,
    zz,
    dataOut,
) {

    // TODO check https://github.com/ONLYOFFICE/core/blob/f6c835668482d70e59202394434314c8863fc1cf/PdfFile/lib/xpdf/Stream.cc#L3582

    let v0, v1, v2, v3, v4, v5, v6, v7, t;
    let i;

    // dequant
    for (i = 0; i < 64; i++) {
        p[i] = zz[i] * qt[i];
    }

    // inverse DCT on rows
    for (i = 0; i < 8; ++i) {
        const row = 8 * i;

        // check for all-zero AC coefficients
        if (p[1 + row] === 0
            && p[2 + row] === 0
            && p[3 + row] === 0
            && p[4 + row] === 0
            && p[5 + row] === 0
            && p[6 + row] === 0
            && p[7 + row] === 0
        ) {

            t = (dctSqrt2 * p[0 + row] + 512) >> 10;

            p[0 + row] = t;
            p[1 + row] = t;
            p[2 + row] = t;
            p[3 + row] = t;
            p[4 + row] = t;
            p[5 + row] = t;
            p[6 + row] = t;
            p[7 + row] = t;

            continue;
        }

        // stage 4
        v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
        v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
        v2 = p[2 + row];
        v3 = p[6 + row];
        v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
        v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
        v5 = p[3 + row] << 4;
        v6 = p[5 + row] << 4;

        // stage 3
        t = (v0 - v1 + 1) >> 1;
        v0 = (v0 + v1 + 1) >> 1;
        v1 = t;
        t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
        v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
        v3 = t;
        t = (v4 - v6 + 1) >> 1;
        v4 = (v4 + v6 + 1) >> 1;
        v6 = t;
        t = (v7 + v5 + 1) >> 1;
        v5 = (v7 - v5 + 1) >> 1;
        v7 = t;

        // stage 2
        t = (v0 - v3 + 1) >> 1;
        v0 = (v0 + v3 + 1) >> 1;
        v3 = t;
        t = (v1 - v2 + 1) >> 1;
        v1 = (v1 + v2 + 1) >> 1;
        v2 = t;
        t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
        v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
        v7 = t;
        t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
        v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
        v6 = t;

        // stage 1
        p[0 + row] = v0 + v7;
        p[7 + row] = v0 - v7;
        p[1 + row] = v1 + v6;
        p[6 + row] = v1 - v6;
        p[2 + row] = v2 + v5;
        p[5 + row] = v2 - v5;
        p[3 + row] = v3 + v4;
        p[4 + row] = v3 - v4;
    }

    // inverse DCT on columns
    for (i = 0; i < 8; ++i) {
        const col = i;

        // check for all-zero AC coefficients
        if (p[1 * 8 + col] === 0
            && p[2 * 8 + col] === 0
            && p[3 * 8 + col] === 0
            && p[4 * 8 + col] === 0
            && p[5 * 8 + col] === 0
            && p[6 * 8 + col] === 0
            && p[7 * 8 + col] === 0
        ) {

            t = (dctSqrt2 * p[i + 0] + 8192) >> 14;

            p[0 * 8 + col] = t;
            p[1 * 8 + col] = t;
            p[2 * 8 + col] = t;
            p[3 * 8 + col] = t;
            p[4 * 8 + col] = t;
            p[5 * 8 + col] = t;
            p[6 * 8 + col] = t;
            p[7 * 8 + col] = t;
            continue;
        }

        // stage 4
        v0 = (dctSqrt2 * p[0 * 8 + col] + 2048) >> 12;
        v1 = (dctSqrt2 * p[4 * 8 + col] + 2048) >> 12;
        v2 = p[2 * 8 + col];
        v3 = p[6 * 8 + col];
        v4 = (dctSqrt1d2 * (p[1 * 8 + col] - p[7 * 8 + col]) + 2048) >> 12;
        v7 = (dctSqrt1d2 * (p[1 * 8 + col] + p[7 * 8 + col]) + 2048) >> 12;
        v5 = p[3 * 8 + col];
        v6 = p[5 * 8 + col];

        // stage 3
        t = (v0 - v1 + 1) >> 1;
        v0 = (v0 + v1 + 1) >> 1;
        v1 = t;
        t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
        v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
        v3 = t;
        t = (v4 - v6 + 1) >> 1;
        v4 = (v4 + v6 + 1) >> 1;
        v6 = t;
        t = (v7 + v5 + 1) >> 1;
        v5 = (v7 - v5 + 1) >> 1;
        v7 = t;

        // stage 2
        t = (v0 - v3 + 1) >> 1;
        v0 = (v0 + v3 + 1) >> 1;
        v3 = t;
        t = (v1 - v2 + 1) >> 1;
        v1 = (v1 + v2 + 1) >> 1;
        v2 = t;
        t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
        v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
        v7 = t;
        t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
        v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
        v6 = t;

        // stage 1
        p[0 * 8 + col] = v0 + v7;
        p[7 * 8 + col] = v0 - v7;
        p[1 * 8 + col] = v1 + v6;
        p[6 * 8 + col] = v1 - v6;
        p[2 * 8 + col] = v2 + v5;
        p[5 * 8 + col] = v2 - v5;
        p[3 * 8 + col] = v3 + v4;
        p[4 * 8 + col] = v3 - v4;
    }

    // convert to 8-bit integers
    for (i = 0; i < 64; ++i) {
        const q = p[i];

        // clip to 8-bit range
        dataOut[i] = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4;
    }
}