import Rule from './rule.js';
import GridData from '../grid.js';
import { ConfigType } from '../config.js';
import { Color, Comparison, State, } from '../primitives.js';
import { array } from '../dataHelper.js';
import LetterSymbol from '../symbols/letterSymbol.js';
import GridConnections from '../gridConnections.js';
class SymbolsPerRegionRule extends Rule {
    /**
     * **Exactly &lt;count&gt; symbols per &lt;color&gt; area**
     *
     * @param color - Color of the region affected by the rule
     * @param count - Number of symbols to have in each region
     * @param comparison - Comparison to use when checking the number of symbols
     */
    constructor(color, count, comparison = Comparison.Equal) {
        super();
        Object.defineProperty(this, "color", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: color
        });
        Object.defineProperty(this, "count", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: count
        });
        Object.defineProperty(this, "comparison", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: comparison
        });
        this.color = color;
        this.count = count;
        this.comparison = comparison;
    }
    get id() {
        return `symbols_per_region`;
    }
    get explanation() {
        switch (this.comparison) {
            case Comparison.AtLeast:
                return `At least ${this.count} symbol${this.count === 1 ? '' : 's'} per ${this.color} area`;
            case Comparison.AtMost:
                return `At most ${this.count} symbol${this.count === 1 ? '' : 's'} per ${this.color} area`;
            default:
                return `Exactly ${this.count} symbol${this.count === 1 ? '' : 's'} per ${this.color} area`;
        }
    }
    get configs() {
        return SymbolsPerRegionRule.CONFIGS;
    }
    createExampleGrid() {
        if (this.count > SymbolsPerRegionRule.SYMBOL_POSITIONS.length ||
            this.comparison !== Comparison.Equal) {
            let description = '';
            switch (this.comparison) {
                case Comparison.AtLeast:
                    description = `≥${this.count}X`;
                    break;
                case Comparison.AtMost:
                    description = `≤${this.count}X`;
                    break;
                default:
                    description = `${this.count}X`;
                    break;
            }
            const symbol = new LetterSymbol(1.5, 1.5, description);
            return SymbolsPerRegionRule.EXAMPLE_GRIDS[this.color]
                .addSymbol(symbol)
                .withConnections(GridConnections.create(['.....', '.aa..', '.aa..', '.....']));
        }
        const symbols = [];
        for (let i = 0; i < this.count; i++) {
            const { x, y } = SymbolsPerRegionRule.SYMBOL_POSITIONS[i];
            symbols.push(new LetterSymbol(x, y, 'X'));
        }
        return SymbolsPerRegionRule.EXAMPLE_GRIDS[this.color].withSymbols(symbols);
    }
    get searchVariants() {
        return SymbolsPerRegionRule.SEARCH_VARIANTS;
    }
    validateGrid(grid) {
        const visited = array(grid.width, grid.height, (i, j) => !(grid.getTile(i, j).exists && grid.getTile(i, j).color === this.color));
        let complete = true;
        while (true) {
            const seed = grid.find((_tile, x, y) => !visited[y][x]);
            if (!seed)
                break;
            const completed = [];
            const gray = [];
            let nbSymbolsIn = 0;
            grid.iterateArea({ x: seed.x, y: seed.y }, tile => tile.color === this.color, (_, x, y) => {
                completed.push({ x, y });
                visited[y][x] = true;
                nbSymbolsIn += SymbolsPerRegionRule.countAllSymbolsOfPosition(grid, x, y);
            });
            if (this.comparison !== Comparison.AtLeast && nbSymbolsIn > this.count) {
                return { state: State.Error, positions: completed };
            }
            let nbSymbolsOut = 0;
            if (this.color === Color.Gray) {
                gray.push(...completed);
                nbSymbolsOut = nbSymbolsIn;
            }
            else {
                grid.iterateArea({ x: seed.x, y: seed.y }, tile => tile.color === Color.Gray || tile.color === this.color, (_, x, y) => {
                    gray.push({ x, y });
                    nbSymbolsOut += SymbolsPerRegionRule.countAllSymbolsOfPosition(grid, x, y);
                });
            }
            if (this.comparison !== Comparison.AtMost && nbSymbolsOut < this.count) {
                return { state: State.Error, positions: gray };
            }
            if (gray.length !== completed.length) {
                complete = false;
            }
        }
        return complete ? { state: State.Satisfied } : { state: State.Incomplete };
    }
    copyWith({ count, color, comparison, }) {
        return new SymbolsPerRegionRule(color ?? this.color, count ?? this.count, comparison ?? this.comparison);
    }
    withColor(color) {
        return this.copyWith({ color });
    }
    withCount(count) {
        return this.copyWith({ count });
    }
    withComparison(comparison) {
        return this.copyWith({ comparison });
    }
    static countAllSymbolsOfPosition(grid, x, y) {
        let count = 0;
        for (const symbolKind of grid.symbols.values()) {
            if (symbolKind.some(symbol => Math.floor(symbol.x) === x && Math.floor(symbol.y) === y)) {
                count++;
            }
        }
        return count;
    }
}
Object.defineProperty(SymbolsPerRegionRule, "SYMBOL_POSITIONS", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: [
        { x: 1, y: 1 },
        { x: 2, y: 2 },
        { x: 2, y: 1 },
        { x: 3, y: 1 },
        { x: 1, y: 2 },
    ]
});
Object.defineProperty(SymbolsPerRegionRule, "CONFIGS", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: Object.freeze([
        {
            type: ConfigType.Number,
            default: 1,
            field: 'count',
            description: 'Count',
            configurable: true,
        },
        {
            type: ConfigType.Color,
            default: Color.Light,
            field: 'color',
            description: 'Color',
            configurable: true,
            allowGray: true,
        },
        {
            type: ConfigType.Comparison,
            default: Comparison.Equal,
            field: 'comparison',
            description: 'Comparison',
            configurable: true,
        },
    ])
});
Object.defineProperty(SymbolsPerRegionRule, "EXAMPLE_GRIDS", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: {
        [Color.Dark]: GridData.create(['wwwww', 'wbbbw', 'wbbww', 'wwwww']),
        [Color.Light]: GridData.create(['bbbbb', 'bwwwb', 'bwwbb', 'bbbbb']),
        [Color.Gray]: GridData.create(['bwbwb', 'wnnnw', 'bnnwb', 'wbwbw']),
    }
});
Object.defineProperty(SymbolsPerRegionRule, "SEARCH_VARIANTS", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: [
        new SymbolsPerRegionRule(Color.Light, 1).searchVariant(),
        new SymbolsPerRegionRule(Color.Dark, 1).searchVariant(),
        new SymbolsPerRegionRule(Color.Light, 1, Comparison.AtLeast).searchVariant(),
        new SymbolsPerRegionRule(Color.Dark, 1, Comparison.AtLeast).searchVariant(),
        new SymbolsPerRegionRule(Color.Light, 1, Comparison.AtMost).searchVariant(),
        new SymbolsPerRegionRule(Color.Dark, 1, Comparison.AtMost).searchVariant(),
    ]
});
export default SymbolsPerRegionRule;
export const instance = new SymbolsPerRegionRule(Color.Dark, 1);
