import Control.Lens
import qualified Data.HashSet as H
import Data.List
import Data.List.Split
import Linear.V2

type Cave = H.HashSet (V2 Int)

main :: IO ()
main = do
  input <- map (map (splitOn ",") . splitOn " -> ") . lines <$> readFile "input"
  let cave = H.fromList $ concatMap toV2 input
  let getMaxY = maximum . map (^. _y) . H.toList
  let f x = length (H.toList x \\ H.toList cave)
  let part1 = f $ day14 (getMaxY cave) True cave
  let part2 = f $ day14 (getMaxY cave) False cave
  putStrLn $ "Part 1: " ++ show part1 ++ "\nPart 2: " ++ show part2

day14 :: Int -> Bool -> Cave -> Cave
day14 maxY mode cave =
  if cave == newCave
    then cave
    else day14 maxY mode newCave
  where
    newCave = fallDown maxY mode (V2 500 0) cave

toV2 :: [[String]] -> [V2 Int]
toV2 ([x1, y1]:[x2, y2]:xs) =
  [V2 i j | i <- f [x1, x2], j <- f [y1, y2]] ++ toV2 ([x2, y2] : xs)
  where
    f = (\[x, y] -> [x .. y]) . sort . map read
toV2 _ = []

fallDown :: Int -> Bool -> V2 Int -> Cave -> Cave
fallDown maxY mode start cave
  | not mode && start ^. _y == maxY + 1 = H.insert start cave
  | mode && start ^. _y >= maxY = cave
  | f (V2 0 1) cave = fallDown maxY mode (start + V2 0 1) cave
  | f (V2 (-1) 1) cave = fallDown maxY mode (start + V2 (-1) 1) cave
  | f (V2 1 1) cave = fallDown maxY mode (start + V2 1 1) cave
  | otherwise = H.insert start cave
  where
    f x = not . H.member (start + x)
