import { fastArrayEquals } from "../../../../core/collection/array/fastArrayEquals.js";
import { isArrayLike } from "../../../../core/collection/array/isArrayLike.js";
import { computeImageCanvasEquality } from "../../../graphics/canvas/computeImageCanvasEquality.js";
import { isImageBitmap } from "../../../graphics/texture/isImageBitmap.js";
import { computeImageBitmapEquality } from "./computeImageBitmapEquality.js";


/**
 * Compare two three.js textures for equality
 * @param {THREE.Texture} a
 * @param {THREE.Texture} b
 * @returns {boolean}
 */
export function computeTextureEquality(a, b) {
    if (a === b) {
        return true;
    }

    if (
        a === null
        || b === null
        || a === undefined
        || b === undefined
    ) {
        return false;
    }

    // special methods
    if (typeof a.equals === "function" && typeof b.equals === "function") {
        // can check for equality using custom methods
        return a.equals(b);
    }

    // revert to generic check

    if (
        a.mapping !== b.mapping
        || a.wrapS !== b.wrapS
        || a.wrapT !== b.wrapT
        || a.magFilter !== b.magFilter
        || a.minFilter !== b.minFilter
        || a.anisotropy !== b.anisotropy
        || a.format !== b.format
        || a.type !== b.type
        || !a.offset.equals(b.offset)
        || !a.repeat.equals(b.repeat)
        || !a.center.equals(b.center)
        || a.rotation !== b.rotation
        || a.generateMipmaps !== b.generateMipmaps
        || a.premultiplyAlpha !== b.premultiplyAlpha
        || a.flipY !== b.flipY
        || a.unpackAlignment !== b.unpackAlignment
        || a.encoding !== b.encoding
        || !textureImagesEqual(a.image, b.image)
    ) {
        return false;
    }


    if (a.isCompressedTexture && b.isCompressedTexture) {
        const aMipmaps = a.mipmaps;
        const bMipmaps = b.mipmaps;

        if (!textureMipChainEqual(aMipmaps, bMipmaps)) {
            return false;
        }

    }

    //TODO implement support for other texture types

    // checks up until now were for rejection, so if we got until this point - textures must be equal
    return true;
}

/**
 *
 * @param {Image|ImageBitmap|HTMLCanvasElement|[]|{width:number, height:number}} a
 * @param {Image|ImageBitmap|HTMLCanvasElement|[]|{width:number, height:number}} b
 * @returns {boolean}
 */
export function textureImagesEqual(a, b) {
    if (a === b) {
        return true;
    }

    if (a instanceof Image && b instanceof Image) {
        //both are images
        if (a.src === b.src) {
            //same source
            return true;
        } else {
            // different source, assume images are different too
            return false;
        }
    }

    if (
        (typeof a.width === "number" && a.width !== b.width)
        || (typeof a.height === "number" && a.height !== b.height)
    ) {
        // dimensions are present, but are inconsistent
        return false;
    }

    if (isImageBitmap(a) && isImageBitmap(b)) {

        return computeImageBitmapEquality(a, b);

    }

    if (a instanceof HTMLCanvasElement && b instanceof HTMLCanvasElement) {
        return computeImageCanvasEquality(a, b);
    }

    if (Array.isArray(a) && Array.isArray(b)) {
        if (a.length !== b.length) {
            return false;
        }

        for (let i = 0; i < a.length; i++) {
            const ai = a[i];
            const bi = b[i];

            if (ai.format !== bi.format) {
                return false;
            }

            const aMipmaps = ai.mipmaps;
            const bMipmaps = bi.mipmaps;

            //TODO should we ignore mipmaps if they are absent?
            if (!textureMipChainEqual(aMipmaps, bMipmaps)) {
                return false;
            }
        }

        return true;
    }

    const a_data = a.data;
    const b_data = b.data;

    if (a_data === null && b_data === null) {
        // special case, empty texture
        return true;
    }

    if (isArrayLike(a_data) && isArrayLike(b_data)) {
        return textureMipmapEqual(a, b);
    }

    // assume different
    return false;
}

/**
 *
 * @param {Array} a
 * @param {Array} b
 * @returns {boolean}
 */
function textureMipChainEqual(a, b) {
    if (a === undefined) {
        if (b === undefined) {
            return true;
        } else {
            return false;
        }
    } else if (b === undefined) {
        return false;
    }

    const a_length = a.length;

    if (a_length !== b.length) {
        // different mip chain length
        return false;
    }

    for (let j = a_length - 1; j >= 0; j--) { //traverse chain top to bottom, starting from smallest mip for faster rejection

        const aMipmap = a[j];
        const bMipmap = b[j];

        if (!textureMipmapEqual(aMipmap, bMipmap)) {
            return false;
        }

    }

    return true;
}


/**
 *
 * @param {{data:Uint8Array, width:number, height:number}} a
 * @param {{data:Uint8Array, width:number, height:number}} b
 * @returns {boolean}
 */
function textureMipmapEqual(a, b) {
    if (a.width !== b.width) {
        return false;
    }

    if (a.height !== b.width) {
        return false;
    }

    const aData = a.data;
    const bData = b.data;


    if (aData === bData) {
        // special case
        return true;
    }

    return fastArrayEquals(aData, bData);
}
