import { assert } from "../../../../core/assert.js";
import { combine_hash } from "../../../../core/collection/array/combine_hash.js";
import { murmur3_32 } from "../../../../core/math/hash/murmur3_32.js";
import { computeHashFloat } from "../../../../core/primitives/numbers/computeHashFloat.js";
import { computeStringHash } from "../../../../core/primitives/strings/computeStringHash.js";
import { computeHtmlCanvasElementHash } from "../../../graphics/canvas/computeHtmlCanvasElementHash.js";
import { isImageBitmap } from "../../../graphics/texture/isImageBitmap.js";
import { computeImageBitmapHash } from "./computeImageBitmapHash.js";

/**
 *
 * @param {Vector2} v
 * @return {number}
 */
function vector2Hash(v) {
    const x = computeHashFloat(v.x);
    const y = computeHashFloat(v.y);
    return ((x << 5) - x) + y;
}

/**
 *
 * @type {WeakMap<object, number>}
 */
const data_cache = new WeakMap();

/**
 *
 * @param {ImageBitmap|HTMLImageElement|{data,width:number,height:number}} image
 * @return {number}
 */
export function computeImageDataHash(image) {
    const cached_value = data_cache.get(image);

    if (cached_value !== undefined) {
        return cached_value;
    }

    let result = 0;

    if (isImageBitmap(image)) {

        result = computeImageBitmapHash(image);

    } else if (image instanceof HTMLImageElement) {

        result = computeStringHash(image.src);

    } else if (image instanceof HTMLCanvasElement) {

        result = computeHtmlCanvasElementHash(image);

    }

    let width = 0;
    let height = 0;

    if (typeof image.width === "number") {
        width = image.width;
    }

    if (typeof image.height === "number") {
        height = image.height;
    }

    result = result ^ (((width & 0xFFFF) << 16) | (height & 0xFFFF));

    // cache value
    data_cache.set(image, result);

    return result;
}

/**
 *
 * @param {Texture} texture
 * @return {number}
 */
function computeSpecificHash(texture) {
    let dataHash = 0;
    let i;

    if (texture.image !== null && texture.image !== undefined) {

        const image = texture.image;

        dataHash = computeImageDataHash(image);

    }

    const mipmaps = texture.mipmaps;

    if (Array.isArray(mipmaps)) {
        const nMipmaps = mipmaps.length;

        for (i = 0; i < nMipmaps; i++) {
            const mipmap = mipmaps[i];

            const data = mipmap.data;
            const dataSize = data.length;

            if (dataSize > 1024) {
                continue;
            }

            dataHash = ((dataHash << 5) - dataHash) + murmur3_32(data, 1337);

            break;
        }

    }

    return dataHash;
}

/**
 *
 * @param {Texture|THREE.Texture} t
 * @returns {number}
 */
export function computeTextureHash(t) {
    if (t === null) {
        return 0;
    }

    if (t === undefined) {
        return 1;
    }

    // special methods
    if (typeof t.hash === "function") {
        const hash = t.hash(); // must produce an integer value

        assert.isInteger(hash, 'hash');

        return hash;
    }

    // revert to generic check

    const specificHash = computeSpecificHash(t);

    return combine_hash(
        t.mapping,
        t.wrapS,
        t.wrapT,
        t.magFilter,
        t.minFilter,
        t.anisotropy,
        t.format,
        t.type,
        vector2Hash(t.offset),
        vector2Hash(t.repeat),
        vector2Hash(t.center),
        computeHashFloat(t.rotation),
        t.generateMipmaps ? 1 : 0,
        t.premultiplyAlpha ? 1 : 0,
        t.flipY ? 1 : 0,
        t.unpackAlignment,
        /*
         we are excluding encoding from hash, as this property has disappeared in later version of three.js
         This property is rarely going to have the deciding role for the hash anyway. This notice is left for historical purposes only
         */
        // t.encoding,
        specificHash
    );
}
