import { ConfigType } from '../config.js';
import GridData from '../grid.js';
import { Color, Direction, State } from '../primitives.js';
import Symbol from './symbol.js';
class DirectionLinkerSymbol extends Symbol {
    /**
     * **Darts count opposite color cells in that direction**
     *
     * @param x - The x-coordinate of the symbol.
     * @param y - The y-coordinate of the symbol.
     */
    constructor(x, y) {
        super(x, y);
        Object.defineProperty(this, "x", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: x
        });
        Object.defineProperty(this, "y", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: y
        });
        Object.defineProperty(this, "linkedDirections", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: {
                [Direction.Left]: Direction.Left,
                [Direction.Up]: Direction.Up,
                [Direction.Right]: Direction.Right,
                [Direction.Down]: Direction.Down,
            }
        });
    }
    changeDirections(linkedDirections) {
        this.linkedDirections = linkedDirections;
        return this;
    }
    get id() {
        return `linkedDirection`;
    }
    get explanation() {
        return `Direction pointed by this symbol should *behave* the same.`;
    }
    get configs() {
        return DirectionLinkerSymbol.CONFIGS;
    }
    createExampleGrid() {
        return DirectionLinkerSymbol.EXAMPLE_GRID;
    }
    getColor(c, grid) {
        if (!grid.isPositionValid(c.x, c.y) || !grid.getTile(c.x, c.y).exists) {
            return null;
        }
        return grid.getTile(c.x, c.y).color;
    }
    deltaCoordinate(c, direction) {
        return {
            x: c.x + DirectionLinkerSymbol.directionDeltas[direction].dx,
            y: c.y + DirectionLinkerSymbol.directionDeltas[direction].dy,
        };
    }
    validateSymbol(grid) {
        // A turtle is an object which have 2 coordinates
        // This turtle when activated will check the color of both the cells it is pointing to
        // If the color of one of the cell is gray, the turtle doesn't fire any other turtle
        // If one of the turtle's coordinate is out of the grid, consider it's color as opposite of the base color
        // If the color of both cells are different, return a State.Error
        // If the color of both are the same as the color of the turtle, the turtle fires another turtle in all the directions according to the linked directions except the one it came from and the directions where a turtle has already been fired
        // If the color of both are the opposite of the turtle, the turtle doesn't fire any other turtle
        // If no turtle remains, go to final state
        // Final state is State.Satisfied if no gray cell is found, State.Incomplete otherwise
        const baseColor = grid.getTile(Math.round(this.x), Math.round(this.y)).color;
        if (baseColor === Color.Gray) {
            return State.Incomplete;
        }
        const checkedCouples = this.getInitialCheckedCouples(this.x, this.y);
        const queue = checkedCouples.map(([p1, p2]) => [
            { ...p1 },
            { ...p2 },
        ]);
        let grayFound = false;
        while (queue.length > 0) {
            const turtle = queue.shift();
            const [c1, c2] = turtle;
            const color1 = this.getColor(c1, grid);
            const color2 = this.getColor(c2, grid);
            const colorSet = new Set([color1, color2]);
            if (colorSet.has(null) && !colorSet.has(baseColor)) {
                colorSet.delete(null);
            }
            if (colorSet.size === 2 && !colorSet.has(Color.Gray)) {
                return State.Error;
            }
            if (colorSet.has(Color.Gray)) {
                grayFound = true;
            }
            if (color1 === baseColor) {
                const directions = Object.keys(this.linkedDirections);
                for (const direction of directions) {
                    const newTurtle = [
                        this.deltaCoordinate(c1, direction),
                        this.deltaCoordinate(c2, this.linkedDirections[direction]),
                    ];
                    if (checkedCouples.some(([c1, c2]) => c1.x === newTurtle[0].x &&
                        c1.y === newTurtle[0].y &&
                        c2.x === newTurtle[1].x &&
                        c2.y === newTurtle[1].y) ||
                        (c1.x === newTurtle[1].x &&
                            c1.y === newTurtle[1].y &&
                            c2.x === newTurtle[0].x &&
                            c2.y === newTurtle[0].y)) {
                        continue;
                    }
                    checkedCouples.push([newTurtle[0], newTurtle[1]]);
                    queue.push(newTurtle);
                }
            }
        }
        return grayFound ? State.Incomplete : State.Satisfied;
    }
    copyWith({ x, y }) {
        return new DirectionLinkerSymbol(x ?? this.x, y ?? this.y);
    }
    getInitialCheckedCouples(x, y) {
        // 1x1
        if (x % 1 === 0 && y % 1 === 0) {
            return [
                [
                    { x, y },
                    { x, y },
                ],
            ];
        }
        // 1x2
        if (x % 1 === 0) {
            return [
                [
                    { x, y: y - 0.5 },
                    {
                        x,
                        y: y +
                            (this.linkedDirections[Direction.Up] === Direction.Up &&
                                this.linkedDirections[Direction.Down] === Direction.Down
                                ? -0.5
                                : 0.5),
                    },
                ],
            ];
        }
        // 2x1
        if (y % 1 === 0) {
            return [
                [
                    { x: x - 0.5, y },
                    {
                        x: x +
                            (this.linkedDirections[Direction.Left] === Direction.Left &&
                                this.linkedDirections[Direction.Right] === Direction.Right
                                ? -0.5
                                : 0.5),
                        y,
                    },
                ],
            ];
        }
        // 2x2
        if (this.linkedDirections[Direction.Left] === Direction.Left &&
            this.linkedDirections[Direction.Right] === Direction.Right) {
            return [
                [
                    { x: x - 0.5, y: y - 0.5 },
                    { x: x - 0.5, y: y + 0.5 },
                ],
                [
                    { x: x + 0.5, y: y - 0.5 },
                    { x: x + 0.5, y: y + 0.5 },
                ],
            ];
        }
        if (this.linkedDirections[Direction.Up] === Direction.Up &&
            this.linkedDirections[Direction.Down] === Direction.Down) {
            return [
                [
                    { x: x - 0.5, y: y - 0.5 },
                    { x: x + 0.5, y: y - 0.5 },
                ],
                [
                    { x: x - 0.5, y: y + 0.5 },
                    { x: x + 0.5, y: y + 0.5 },
                ],
            ];
        }
        else if ((this.linkedDirections[Direction.Up] === Direction.Left &&
            this.linkedDirections[Direction.Left] === Direction.Up) ||
            (this.linkedDirections[Direction.Up] === Direction.Right &&
                this.linkedDirections[Direction.Right] === Direction.Up)) {
            return [
                [
                    { x: x - 0.5, y: y - 0.5 },
                    { x: x - 0.5, y: y - 0.5 },
                ],
            ];
        }
        return [
            [
                { x: x - 0.5, y: y - 0.5 },
                { x: x + 0.5, y: y + 0.5 },
            ],
            [
                { x: x - 0.5, y: y + 0.5 },
                { x: x + 0.5, y: y - 0.5 },
            ],
        ];
    }
}
Object.defineProperty(DirectionLinkerSymbol, "CONFIGS", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: Object.freeze([
        {
            type: ConfigType.Number,
            default: 0,
            field: 'x',
            description: 'X',
            configurable: false,
        },
        {
            type: ConfigType.Number,
            default: 0,
            field: 'y',
            description: 'Y',
            configurable: false,
        },
    ])
});
Object.defineProperty(DirectionLinkerSymbol, "EXAMPLE_GRID", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: Object.freeze(GridData.create(['wwbbw', 'wwwww', 'wwwww', 'wwwww']))
});
Object.defineProperty(DirectionLinkerSymbol, "directionDeltas", {
    enumerable: true,
    configurable: true,
    writable: true,
    value: {
        [Direction.Up]: { dx: 0, dy: -1 },
        [Direction.Down]: { dx: 0, dy: 1 },
        [Direction.Left]: { dx: -1, dy: 0 },
        [Direction.Right]: { dx: 1, dy: 0 },
    }
});
export default DirectionLinkerSymbol;
export const instance = undefined;
