import TopDownCameraControllerSystem from "../../../graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js";
import { Camera } from "../../../graphics/ecs/camera/Camera.js";
import { obtainTerrain } from "./obtainTerrain.js";
import { assert } from "../../../../core/assert.js";
import TerrainSystem from "../ecs/TerrainSystem.js";
import Task from "../../../../core/process/task/Task.js";
import { TaskSignal } from "../../../../core/process/task/TaskSignal.js";
import { max2 } from "../../../../core/math/max2.js";
import { clamp01 } from "../../../../core/math/clamp01.js";

/**
 *
 * @param {EntityManager} em
 * @param {EntityComponentDataset} ecd
 * @param {number} [timeout] in seconds
 * @returns {Task}
 */
export function loadVisibleTerrainTiles({
                                            em,
                                            ecd,
                                            timeout = 10
                                        }) {

    let totalTiles = 0;
    let loadedTiles = 0;
    let failedTiles = 0;

    let attempt = 0;

    let failureFlag = false;

    const timeout_milliseconds = timeout * 1000;

    let start_time = 0;

    function init() {

        start_time = Date.now();

        const attemptId = attempt;

        //force update by camera controller, to ensure correct focus is set
        em.getSystem(TopDownCameraControllerSystem).update(0);

        ecd.traverseComponents(Camera, function (c) {
            c.updateMatrices();
        });

        const tiles = [];

        const terrain = obtainTerrain(ecd);

        assert.notEqual(terrain, null, 'terrain not found');

        TerrainSystem.traverseVisibleTiles(ecd, terrain, function (tile) {
            tiles.push(tile);
        });

        totalTiles = tiles.length;
        const tileManager = terrain.__tiles;

        function handleObtainedTile(t) {
            if (attemptId !== attempt) {
                return;
            }

            loadedTiles++;
        }

        function handleFailedTile(t) {
            if (attemptId !== attempt) {
                return;
            }

            attempt++;

            failedTiles++;

            if (attempt < 10) {
                init();
            } else {
                failureFlag = true;
            }
        }

        if (totalTiles !== 0) {
            tiles.forEach(function (tile) {
                tileManager.obtain(tile.gridPosition.x, tile.gridPosition.y).then(handleObtainedTile, handleFailedTile);
            });
        }
    }

    const task = new Task({
        name: "Building visible terrain tiles",

        initializer: init,

        cycleFunction() {
            if (Date.now() > start_time + timeout_milliseconds) {
                // timed out
                return TaskSignal.EndFailure;
            }

            if (failureFlag) {
                return TaskSignal.EndFailure;
            }

            if (loadedTiles >= totalTiles) {
                return TaskSignal.EndSuccess;
            } else {
                return TaskSignal.Yield;
            }
        },

        computeProgress() {

            const loaded_fraction = totalTiles === 0 ? 0 : loadedTiles / totalTiles;

            const timeout_fraction = (Date.now() - start_time) / timeout_milliseconds;

            return clamp01(max2(loaded_fraction, timeout_fraction));
        }
    });

    return task;
}
