import { array_quick_sort_by_comparator } from "../../../../core/collection/array/array_quick_sort_by_comparator.js";
import { compose_tile_address } from "./tile/compose_tile_address.js";
import { finger_print_to_tile_address } from "./tile/finger_print_to_tile_address.js";
import { tile_address_to_finger_print } from "./tile/tile_address_to_finger_print.js";


export class VirtualTextureUsage {

    #counts_intrinsic = new Uint32Array(0);
    /**
     * Stores indices of tiles with usage > 0
     * @type {Uint32Array}
     */
    #occupancy = new Uint32Array(0);
    /**
     * Points at the end of the occupancy list
     * @type {number}
     */
    #occupancy_cursor = 0;

    get counts() {
        return this.#counts_intrinsic;
    }

    #max_mip_level = 0;


    set max_mip_level(v) {
        this.#max_mip_level = v;

        this.#ensureCapacity();
    }

    get max_mip_level() {
        return this.#max_mip_level;
    }

    /**
     *
     * @return {Uint32Array}
     */
    get occupancy() {
        return this.#occupancy;
    }

    /**
     *
     * @return {number}
     */
    get occupancy_count() {
        return this.#occupancy_cursor;
    }

    /**
     *
     */
    #ensureCapacity() {

        const size = compose_tile_address(this.#max_mip_level + 1, 0, 0);

        if (this.#counts_intrinsic.length >= size) {
            // already large enough
            return;
        }

        this.#counts_intrinsic = new Uint32Array(size);
        this.#occupancy = new Uint32Array(size);
    }

    /**
     *
     * @param {number} fingerprint
     * @returns {number}
     */
    getCountByFingerprint(fingerprint) {
        const index = finger_print_to_tile_address(fingerprint);

        return this.#counts_intrinsic[index];
    }

    /**
     *
     * @param {number} mip
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    getCountBy(mip, x, y) {
        const address = compose_tile_address(mip, x, y);

        return this.#counts_intrinsic[address];
    }

    clear() {
        this.#counts_intrinsic.fill(0);
        this.#occupancy_cursor = 0;
    }

    /**
     * Given existing usage, award same usage to all tiles up the chain all the way to the root
     * This helps avoid popping and ensures that when there's not enough space - at least the higher level LOD tiles are available
     * @param {number} bias added to the ancestor tiles to make them more likely to be loaded
     */
    promoteAncestors(bias = 1) {

        // traverse mip pyramid in reverse, so we can push counts to parents one level at a time
        const max_mip = this.#max_mip_level;
        for (let mip = max_mip; mip > 0; mip--) {

            const mip_resolution = 1 << mip;

            for (let y = 0; y < mip_resolution; y++) {
                for (let x = 0; x < mip_resolution; x++) {

                    const count = this.getCountBy(mip, x, y);

                    if (count <= 0) {
                        continue;
                    }

                    // get higher-level lod
                    const parent_level = mip - 1;
                    const parent_x = x >>> 1;
                    const parent_y = y >>> 1;

                    const parent_index = compose_tile_address(parent_level, parent_x, parent_y);

                    const parent_count = this.#counts_intrinsic[parent_index];

                    this.#counts_intrinsic[parent_index] = parent_count + count + bias;

                    if (parent_count === 0) {
                        this.#append_to_occupancy(parent_index);
                    }

                }
            }

        }
    }

    /**
     *
     * @param {number} address
     */
    #append_to_occupancy(address) {
        this.#occupancy[this.#occupancy_cursor] = address;
        this.#occupancy_cursor++;
    }

    sortOccupancyByLOD() {
        /**
         *
         * @param {number} a
         * @param {number} b
         * @return {number}
         */
        const compare = (a, b) => {

            const fingerprint_a = tile_address_to_finger_print(a);
            const fingerprint_b = tile_address_to_finger_print(b);

            // read MIP levels
            const mip_a = (fingerprint_a >> 24) & 0xFF;
            const mip_b = (fingerprint_b >> 24) & 0xFF;

            const mip_delta = mip_a - mip_b;

            if (mip_delta !== 0) {
                return mip_delta;
            }

            const intrinsic = this.#counts_intrinsic;

            const count_a = intrinsic[a];
            const count_b = intrinsic[b];

            return count_b - count_a;

        };

        array_quick_sort_by_comparator(this.#occupancy, compare, null, 0, this.#occupancy_cursor - 1);

    }


    /**
     *
     * @param {Uint8Array} data usage texture data
     */
    fromTexture(data) {
        this.clear();

        const data_size = data.length;

        this.#ensureCapacity();

        const counts = this.#counts_intrinsic;

        for (let offset = 0; offset < data_size; offset += 4) {

            if (data[offset + 3] === 0) {
                // no data
                continue;
            }

            const mip_level = data[offset];

            // we create a mask to make sure we don't end up with invalid tile position values
            const max_tile_value = (1 << mip_level) - 1;

            const tile_x = data[offset + 1] & max_tile_value;
            const tile_y = data[offset + 2] & max_tile_value;

            const tile_address = compose_tile_address(mip_level, tile_x, tile_y);

            const previous_count = counts[tile_address];

            if (previous_count === 0) {
                // first occurrence, mark occupancy
                this.#append_to_occupancy(tile_address);
            }

            counts[tile_address] = previous_count + 1;
        }

    }
}