from typing import *
from aocpy import BaseChallenge


Coordinate = Tuple[int, int]
Forest = Dict[Coordinate, int]


def parse(instr: str) -> Tuple[Forest, Coordinate]:
    res: Forest = {}

    for x, line in enumerate(instr.strip().splitlines()):
        for y, char in enumerate(line):
            res[(x, y)] = int(char)

    return res, (x, y)


def are_adjacents_lower(
    forest: Forest,
    initial_pos: Coordinate,
    next_pos: Callable[[Coordinate], Coordinate],
) -> bool:
    height = forest[initial_pos]
    pos = next_pos(initial_pos)
    while pos in forest:
        if forest[pos] >= height:
            return False
        pos = next_pos(pos)
    return True


def get_viewing_distance(
    forest: Forest,
    initial_pos: Coordinate,
    next_pos: Callable[[Coordinate], Coordinate],
) -> int:
    viewing_dist = 0

    height = forest[initial_pos]
    pos = next_pos(initial_pos)
    while pos in forest:
        viewing_dist += 1
        if forest[pos] >= height:
            break
        pos = next_pos(pos)

    return viewing_dist


class Challenge(BaseChallenge):
    @staticmethod
    def one(instr: str) -> int:
        forest, (max_x, max_y) = parse(instr)

        count = 0
        for tree_pos in forest:
            x, y = tree_pos
            if x == 0 or y == 0 or x == max_x or y == max_y:
                count += 1
                continue

            # left -x
            if are_adjacents_lower(forest, tree_pos, lambda x: (x[0] - 1, x[1])):
                count += 1
                continue
            # right +x
            if are_adjacents_lower(forest, tree_pos, lambda x: (x[0] + 1, x[1])):
                count += 1
                continue
            # up -y
            if are_adjacents_lower(forest, tree_pos, lambda x: (x[0], x[1] - 1)):
                count += 1
                continue
            # down +y
            if are_adjacents_lower(forest, tree_pos, lambda x: (x[0], x[1] + 1)):
                count += 1
                continue

        return count

    @staticmethod
    def two(instr: str) -> int:
        forest, _ = parse(instr)

        max_senic = 0
        for tree_pos in forest:
            view_left = get_viewing_distance(
                forest, tree_pos, lambda x: (x[0] - 1, x[1])
            )
            view_right = get_viewing_distance(
                forest, tree_pos, lambda x: (x[0] + 1, x[1])
            )
            view_up = get_viewing_distance(forest, tree_pos, lambda x: (x[0], x[1] - 1))
            view_down = get_viewing_distance(
                forest, tree_pos, lambda x: (x[0], x[1] + 1)
            )

            senic = view_left * view_right * view_up * view_down
            if senic > max_senic:
                max_senic = senic

        return max_senic
