import { assert } from "../../../../../core/assert.js";
import { BitSet } from "../../../../../core/binary/BitSet.js";
import { CellSupplier } from "./CellSupplier.js";

/**
 *
 * @type {Uint32Array}
 */
const scratch_neighbours = new Uint32Array(4);

/**
 * Supply cells that trace from source cells up to maximum value cells via hill climbing.
 * Useful for tracing paths between features, such as path between a shipyard and the nearest water source
 */
export class CellSupplierPathUpHill extends CellSupplier {

        /**
         *
         * @type {CellMatcher|null}
         */
        source = null;

        /**
         *
         * @type {CellFilter|null}
         */
        value = null;

        /**
         *
         * @type {BitSet}
         * @private
         */
        __open_set = new BitSet();
        /**
         *
         * @type {BitSet}
         * @private
         */
        __visited_set = new BitSet();

        /**
         *
         * @type { Float32Array|null}
         * @private
         */
        __cached_values = null;

        /**
         *
         * @type {GridData|null}
         * @private
         */
        __grid = null;

    /**
     *
     * @param {CellFilter} value
     * @param {CellMatcher} source
     */
    static from(value, source) {
        assert.equal(value.isCellFilter, true, 'value.isCellFilter !== true');
        assert.equal(source.isCellMatcher, true, 'source.isCellMatcher !== true');

        const r = new CellSupplierPathUpHill();

        r.value = value;
        r.source = source;

        return r;
    }

    initialize(grid, seed) {
        this.__grid = grid;

        this.source.initialize(grid, seed);
        this.value.ensureInitialized(grid, seed);

        const grid_size = grid.width * grid.height;

        this.__open_set.preventShrink();
        this.__open_set.reset();
        this.__open_set.setCapacity(grid_size);

        this.__visited_set.preventShrink();
        this.__visited_set.reset();
        this.__visited_set.setCapacity(grid_size);

        this.__cached_values = new Float32Array(grid_size);

        // identify source cells
        for (let y = 0; y < grid.height; y++) {
            for (let x = 0; x < grid.width; x++) {

                if (this.source.match(grid, x, y, 0)) {

                    const index = y * grid.width + x;
                    this.__open_set.set(index, true);
                    this.__visited_set.set(index, true);

                    this.__cached_values[index] = this.value.execute(grid, x, y, 0);
                }

            }
        }
    }

    next() {
        const grid_width = this.__grid.width;
        const current_index = this.__open_set.nextSetBit(0);

        if (current_index === -1) {
            // no more
            return undefined;
        }

        this.__open_set.clear(current_index);

        const current_value = this.__cached_values[current_index];

        const current_x = current_index % grid_width;
        const current_y = (current_index / grid_width) | 0;


        // update neighbours of the cell
        let neighbour_count = 0;

        if (current_y > 0) {
            // up
            scratch_neighbours[neighbour_count] = current_index - grid_width;
            neighbour_count++;
        }

        if (current_x > 0) {
            // left
            scratch_neighbours[neighbour_count] = current_index - 1;
            neighbour_count++;
        }

        if (current_x < grid_width - 1) {
            // right
            scratch_neighbours[neighbour_count] = current_index + 1;
            neighbour_count++;
        }

        if (current_y < this.__grid.height - 1) {
            // up
            scratch_neighbours[neighbour_count] = current_index + grid_width;
            neighbour_count++;
        }

        for (let i = 0; i < neighbour_count; i++) {
            const neighbour = scratch_neighbours[i];

            if (this.__visited_set.get(neighbour) || this.__open_set.get(neighbour)) {
                //already visited
                continue;
            }
            const n_x = neighbour % grid_width;
            const n_y = (neighbour - n_x) / grid_width;

            // pick smallest
            const n_v = this.value.execute(this.__grid, n_x, n_y, 0);

            if (n_v < current_value) {
                // invalid
                continue;
            }

            this.__open_set.set(neighbour, true);
            this.__visited_set.set(neighbour, true);

            this.__cached_values[neighbour] = n_v;
        }


        return {
            x: current_x,
            y: current_y,
            rotation: 0
        };
    }

}
