import { assert } from "../../../../../core/assert.js";
import { PI_HALF } from "../../../../../core/math/PI_HALF.js";
import { CellMatcherAny } from "../../../../rules/CellMatcherAny.js";
import { CellSupplier } from "./CellSupplier.js";

/**
 *
 * @param {{score:number}} a
 * @param {{score:number}} b
 * @returns {number}
 */
function byScore(a, b) {
    return a.score - b.score;
}

/**
 *
 * @param {{index:number}} a
 * @param {{index:number}} b
 * @returns {number}
 */
function byIndex(a, b) {
    return a.index - b.index;
}

export class CellSupplierBestN extends CellSupplier {

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

    /**
     *
     * @type {number}
     */
    cursor = 0;

    /**
     *
     * @type {{x:number, y:number, rotation:number}[]}
     */
    elements = [];

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

    /**
     *
     * @type {number}
     */
    limit = 0;

    /**
     *
     * @type {boolean}
     */
    allow_rotation = false;

    /**
     * @param {CellFilter} fitness
     * @param {CellMatcher} [predicate]
     * @param {number} limit
     * @param {boolean} allow_rotation
     */
    static from({
                    fitness,
                    predicate = CellMatcherAny.INSTANCE,
                    limit = 1,
                    allow_rotation = false
                }) {

        assert.isNumber(limit, 'limit');
        assert.isNonNegativeInteger(limit, 'limit');
        assert.equal(fitness.isCellFilter, true, 'fitness.isCellFilter !== true');
        assert.equal(predicate.isCellMatcher, true, 'predicate.isCellMatcher !== true');
        assert.isBoolean(allow_rotation, 'allow_rotation');

        const r = new CellSupplierBestN();

        r.fitness = fitness;
        r.limit = limit;
        r.predicate = predicate;
        r.allow_rotation = allow_rotation;

        return r;
    }

    initialize(grid, seed) {
        super.initialize(grid, seed);

        const predicate = this.predicate;
        predicate.initialize(grid, seed);
        this.fitness.ensureInitialized(grid, seed);

        const fitness_scratch = [];

        this.cursor = 0;

        // build list
        const h = grid.height;
        const w = grid.width;

        let rotation = 0;
        for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
                const cell_index = y * w + x;

                if (predicate.match(grid, x, y, 0)) {
                    rotation = 0;
                } else if (this.allow_rotation) {
                    if (predicate.match(grid, x, y, PI_HALF)) {
                        rotation = PI_HALF;
                    } else if (predicate.match(grid, x, y, PI_HALF * 2)) {
                        rotation = PI_HALF * 2;
                    } else if (predicate.match(grid, x, y, PI_HALF * 3)) {
                        rotation = PI_HALF * 3;
                    } else {
                        // no rotated matches
                        continue
                    }

                } else {
                    continue;
                }

                const fitness = this.fitness.execute(grid, x, y, rotation);

                if (fitness_scratch.length < this.limit) {
                } else if (fitness_scratch[0].score < fitness) {
                    // evict lower fitness candidate
                    fitness_scratch.shift();
                } else {
                    continue;
                }

                fitness_scratch.push({
                    score: fitness,
                    index: cell_index,
                    x,
                    y,
                    rotation: rotation
                });

                fitness_scratch.sort(byScore);

            }
        }

        // sort elements by index for faster access
        fitness_scratch.sort(byIndex);

        // copy out the list
        const found_cells = fitness_scratch.length;

        this.elements = [];
        for (let i = 0; i < found_cells; i++) {
            const s = fitness_scratch[i];

            this.elements[i] = {
                x: s.x,
                y: s.y,
                rotation: s.rotation
            };
        }

    }

    next() {
        const cursor = this.cursor;
        const elements = this.elements;

        if (cursor >= elements.length) {
            return undefined;
        }

        const r = elements[cursor];

        this.cursor++;

        return r;
    }
}
