import Foundation

let input = loadData(day: 24)
let scanner = Scanner(string: input)
scanner.charactersToBeSkipped = nil

enum Direction: String, CaseIterable {
    case east = "e"
    case southEast = "se"
    case southWest = "sw"
    case west = "w"
    case northWest = "nw"
    case northEast = "ne"
}

extension Scanner {
    func readDirection() -> Direction? {
        for dir in Direction.allCases {
            if string(dir.rawValue) {
                return dir
            }
        }
        return nil
    }
}

struct Coord: Hashable {
    var south: Int
    var east: Int

    static let zero = Coord(south: 0, east: 0)

    mutating func move(_ direction: Direction) {
        switch direction {
        case .east:
            east += 1
        case .southEast:
            south += 1

        case  .southWest:
            south += 1
            east -= 1

        case .west:
            east -= 1

        case .northWest:
            south -= 1

        case .northEast:
            south -= 1
            east += 1
        }
    }

    func neighbor(_ direction: Direction) -> Coord {
        var result = self
        result.move(direction)
        return result
    }

    var neighbors: [Coord] {
        Direction.allCases.map(neighbor)
    }
}

func readCoordinate() -> Coord {
    var current = Coord.zero
    while let direction = scanner.readDirection() {
        current.move(direction)
    }
    precondition(scanner.string("\n"))
    return current
}

var floor: [Coord: Bool] = [:]
var northWestCorner = Coord.zero
var southEastCorner = Coord.zero


func expandRange(toInclude coord: Coord) {
    northWestCorner.east = min(northWestCorner.east, coord.east)
    northWestCorner.south = min(northWestCorner.south, coord.south)

    southEastCorner.east = max(southEastCorner.east, coord.east)
    southEastCorner.south = max(southEastCorner.south, coord.south)
}

while !scanner.isAtEnd {
    let coord = readCoordinate()
    expandRange(toInclude: coord)

    floor[coord, default: false].toggle()
}

print("part 1:", floor.values.lazy.filter { $0 }.count)

func step() -> [Coord: Bool] {
    var result = floor

    for east in (northWestCorner.east - 1)...(southEastCorner.east + 1) {
        for south in (northWestCorner.south - 1)...(southEastCorner.south + 1) {
            let coord = Coord(south: south, east: east)

            let isBlack = floor[coord, default: false]

            let blackNeighbors = coord.neighbors
                .lazy
                .map { floor[$0, default: false] }
                .filter { $0 }
                .count

            if isBlack && (blackNeighbors == 0 || blackNeighbors > 2) {
                result[coord] = false
            } else if !isBlack && blackNeighbors == 2 {
                result[coord] = true
                expandRange(toInclude: coord)
            }
        }
    }

    return result
}

for _ in 0..<100 {
    floor = step()
}

print("part 2:", floor.values.lazy.filter { $0 }.count)