package graph.iterative;

import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class NumberOfIslands {

    private static final char LAND  = '1';
    private static final char WATER = '0';
    private static final char FLOOD = 'X';

    private record Coordinate(int row, int col) {

        public static final List<Coordinate> deltas = List.of(
                Coordinate.of(0, 1),
                Coordinate.of(0, -1),
                Coordinate.of(1, 0),
                Coordinate.of(-1, 0)
        );

        public static Coordinate of(int row, int col) {
            return new Coordinate(row, col);
        }

        public Coordinate add(Coordinate other) {
            return new Coordinate(other.row + row, other.col + col);
        }
    }

    public int numIslands(char[][] grid) {

        int rows = grid.length, cols = grid[0].length;
        int numIslands = 0;

        for (int row = 0; row < rows; row++)
            for (int col = 0; col < cols; col++)
                if (grid[row][col] == LAND) {
                    flood(grid, Coordinate.of(row, col));
                    numIslands++;
                }

        return numIslands;
    }

    private void flood(char[][] grid, Coordinate start) {

        int rows = grid.length, cols = grid[0].length;
        Queue<Coordinate> queue = new ArrayDeque<>(List.of(start));

        Predicate<Coordinate> isValidNeigh = c -> c.row >= 0 && c.row < rows && c.col >= 0 && c.col < cols;
        Predicate<Coordinate> isLand       = c -> grid[c.row][c.col] == LAND;
        Consumer<Coordinate> flood         = c -> grid[c.row][c.col] = FLOOD;

        flood.accept(start);
        while (!queue.isEmpty()) {
            var node = queue.remove();
            Coordinate.deltas.stream()
                    .map(node::add)
                    .filter(isValidNeigh.and(isLand))
                    .forEach(flood.andThen(queue::add));
        }

    }
}