import { gl } from '../core/gl';
import { warpShader } from '../shaders/warp-shader';
import { simpleShader } from '../core/simple-shader';
import { getInverse } from '../math/matrix';
import { TFxCanvas, TMat2x2, TMat3x3, TMatDeep3x3 } from '../fx-canvas-types';

/**
 * Matrix Warp
 * @description           Transforms an image by a 2x2 or 3x3 matrix. The coordinates used in
 *                        the transformation are (x, y) for a 2x2 matrix or (x, y, 1) for a
 *                        3x3 matrix, where x and y are in units of pixels.
 * @param matrix          A 2x2 or 3x3 matrix represented as either a list or a list of lists.
 *                        For example, the 3x3 matrix [[2,0,0],[0,3,0],[0,0,1]] can also be
 *                        represented as [2,0,0,0,3,0,0,0,1] or just [2,0,0,3].
 * @param inverse         A boolean value that, when true, applies the inverse transformation
 *                        instead. (optional, defaults to false)
 * @param useTextureSpace A boolean value that, when true, uses texture-space coordinates
 *                        instead of screen-space coordinates. Texture-space coordinates range
 *                        from -1 to 1 instead of 0 to width - 1 or height - 1, and are easier
 *                        to use for simple operations like flipping and rotating.
 */
export type TFilterMatrixWarp = (
    this: TFxCanvas,
    matrix: TMat2x2 | TMat3x3 | TMatDeep3x3,
    inverse?: boolean,
    useTextureSpace?: boolean,
) => TFxCanvas;

export const matrixWarp: TFilterMatrixWarp = function (matrix, inverse, useTextureSpace) {
    gl.matrixWarp =
        gl.matrixWarp ||
        warpShader(
            '\
        uniform mat3 matrix;\
        uniform bool useTextureSpace;\
    ',
            '\
        if (useTextureSpace) coord = coord / texSize * 2.0 - 1.0;\
        vec3 warp = matrix * vec3(coord, 1.0);\
        coord = warp.xy / warp.z;\
        if (useTextureSpace) coord = (coord * 0.5 + 0.5) * texSize;\
    ',
        );

    // Flatten all members of matrix into one big list
    matrix = matrix.flat() as TMat2x2 | TMat3x3;

    // Extract a 3x3 matrix out of the arguments
    if (matrix.length == 4) {
        matrix = [matrix[0], matrix[1], 0, matrix[2], matrix[3], 0, 0, 0, 1];
    } else if (matrix.length != 9) {
        throw 'can only warp with 2x2 or 3x3 matrix';
    }

    simpleShader.call(this, gl.matrixWarp, {
        matrix: inverse ? getInverse(matrix) : matrix,
        texSize: [this.width, this.height],
        useTextureSpace: useTextureSpace ? 1 : 0,
    });

    return this;
};
