import { max2 } from "../../../../core/math/max2.js";
import Mesh from "./Mesh.js";
import Task from "../../../../core/process/task/Task.js";
import { TaskSignal } from "../../../../core/process/task/TaskSignal.js";
import { assert } from "../../../../core/assert.js";
import { assetTypeByPath } from "./assetTypeByPath.js";
import { collectIteratorValueToArray } from "../../../../core/collection/collectIteratorValueToArray.js";
import { clamp01 } from "../../../../core/math/clamp01.js";

/**
 * Produces a task that will wait for all existing Meshes of a dataset to be loaded, will fail upon timeout being reached
 * @param {EntityComponentDataset} ecd Dataset
 * @param {Engine} engine
 * @param {number} timeout in milliseconds, measured in wall-clock-time, from initialization of the task
 * @return {Task}
 */
export function createTaskWaitForMeshesToLoad(ecd, engine, timeout) {
    assert.isNumber(timeout, 'timeout');
    assert.notNaN(timeout, 'timeout');

    /**
     *
     * @type {AssetManager}
     */
    const am = engine.assetManager;

    /**
     *
     * @type {string[]}
     */
    const remaining = [];
    /**
     *
     * @type {Set<string>}
     */
    const seen = new Set();

    /**
     *
     * @param {Mesh} m
     */
    function collectMesh(m) {
        if (seen.has(m.url)) {
            // already seen, ignore
            return;
        }

        seen.add(m.url);
    }


    let cursor = 0;

    let startTime = 0;
    let currentTime = 0;

    const estimatedDuration = timeout / 1000;

    const task = new Task({
        name: 'Waiting for meshes to load',
        initializer() {
            remaining.splice(0, remaining.length);
            seen.clear();

            ecd.traverseComponents(Mesh, collectMesh);

            collectIteratorValueToArray(remaining, seen.values());

            cursor = 0;

            startTime = performance.now();
            currentTime = startTime;
        },

        estimatedDuration,

        computeProgress() {
            const n = remaining.length;
            if (n === 0) {
                return 1;
            }


            const progress = cursor / n;

            if (progress >= 1) {
                return 1;
            }

            if (timeout === 0 || !Number.isFinite(timeout)) {
                return progress;
            }


            const relativeExpiredTime = (currentTime - startTime) / timeout;

            const p = max2(progress, relativeExpiredTime);

            return clamp01(p);
        },
        cycleFunction() {
            currentTime = performance.now();

            if (cursor >= remaining.length) {
                return TaskSignal.EndSuccess;
            }

            const path = remaining[cursor];

            if (path === null) {
                // No path
                cursor++;
                return TaskSignal.Continue;
            }

            const type = assetTypeByPath(path);

            if (type === null) {
                // Failed to figure out asset type
                cursor++;
                return TaskSignal.Continue;
            }

            const asset = am.tryGet(path, type);

            const is_mesh_loaded = asset !== null;

            if (is_mesh_loaded) {

                // console.log(`${cursor}/${remaining.length} loaded : ${path}`);

                cursor++;
                return TaskSignal.Continue;
            }

            // check if asset failed to load
            const failed_to_load = am.isFailed(path, type);

            if (failed_to_load) {
                // skip and continue
                console.log(`Skipped asset failure '${path}'`);
                cursor++;
                return TaskSignal.Continue;
            }

            // console.log(`\t!${cursor}/${remaining.length} not loaded : ${path}`);

            if ((currentTime - startTime) >= timeout) {
                return TaskSignal.EndFailure;
            } else {
                return TaskSignal.Yield;
            }

        }
    });

    return task
}
