module Day14

import Prelude
import Node
import Aoc
import Parser
import SortedMap

data Robot : U where
  MkBot : Point → Point → Robot

pint : Parser Int
pint = do
  sign <- pure (0 - 1) <* match '-' <|> pure 1
  num <- number
  pure $ sign * num

ppoint : Parser Point
ppoint = _,_ <$> pint <* match ',' <*> pint

probot : Parser Robot
probot = do
  token "p="
  p <- ppoint
  ws
  token "v="
  v <- ppoint
  ws
  many (match '\n')
  pure $ MkBot p v

-- TODO make Mod typeclass or something
infixl 7 _%_
pfunc _%_ : Int → Int → Int := `(x,y) => x % y`

pmod : Int → Int → Int
pmod a b =
  if a < 0 then (a + b * ((0 - a / b) + 1)) % b else a % b

move : Int → Int → Int → Robot → Point
move w h time (MkBot (x,y) (dx,dy)) =
  -- no HMul yet
  -- let (x',y') = trace "to" $ (x,y) + (time * dx, time * dy) in
  let x' = pmod (x + time * dx) w
      y' = pmod (y + time * dy) h
   in (x', y')

count : ∀ a. (a → Bool) → List a → Int
count {a} f xs = go xs 0
  where
    go : List a → Int → Int
    go Nil acc = acc
    go (x :: xs) acc = if f x then go xs (acc + 1) else go xs acc

draw' : Int → Int → SortedMap Point Int → IO Unit
draw' w h pts = go 0
  where
    mkLine : Int → Int → SnocList Char → String
    mkLine y x acc = if x == w then pack (acc <>> Nil)
      else case lookupMap (x,y) pts of
        Nothing => mkLine y (x + 1) (acc :< '.')
        _       => mkLine y (x + 1) (acc :< '*')

    go : Int → IO Unit
    go y = if y == h then pure MkUnit else do
      putStrLn $ mkLine y 0 Lin
      go (y + 1)

draw : Int → Int → List Point → IO Unit
draw w h pts =
  let m = foldMap _+_ EmptyMap $ map (flip _,_ 1) pts in draw' w h m


part2 : Int → Int → List Robot → List Point × Int
part2 w h robots = go 1
  where
    heuristic : Point → Bool
    heuristic (x,y) = if x < w / 2 then 100 < 2 * x + y else 2 * (x - 50) < y
    -- heuristic (x,y) = x == w / 2
    go : Int → List Point × Int
    go t =
      let pts = map (move w h t) robots
          cnt = count heuristic pts
      in if cnt > 20 then (pts, t)
      else go (t + 1)

run : String → Int → Int → IO Unit
run fn w h = do
  putStrLn fn
  text <- readFile fn
  let (Right (robots, Nil)) = some probot $ unpack text
    | Left msg => putStrLn $ "Parse Error " ++ msg
    | Right (robots, rest) => putStrLn $ "stuck at¬" ++ pack rest
  let result = map (move w h 100) robots
  -- printLn result
  let q1 = count quad1 result
  let q2 = count quad2 result
  let q3 = count quad3 result
  let q4 = count quad4 result
  printLn (q1,q2,q3,q4)
  let p1 = q1 * q2 * q3 * q4
  putStrLn $ "part1 " ++ show p1
  printLn $ count (\ x => True) robots
  let scores = collect robots 0 Nil
  let stuff = qsort (\ a b => snd a < snd b) $ collect robots 0 Nil
  -- printLn stuff
  dump robots stuff 1
  where
    dump : List Robot → List (Int × Int) → Int → IO Unit
    dump robots Nil _ = pure MkUnit
    dump robots (((t,cnt) :: xs)) 0 = pure MkUnit
    dump robots (((t,cnt) :: xs)) gas = do
      printLn t
      draw w h $ map (move w h t) robots
      printLn ""
      dump robots xs (gas - 1)

    quad1 : Point → Bool
    quad1 (x,y) = x < w / 2 && y < h / 2

    quad2 : Point → Bool
    quad2 (x,y) = x > w / 2 && y < h / 2

    quad3 : Point → Bool
    quad3 (x,y) = x > w / 2 && y > h / 2

    quad4 : Point → Bool
    quad4 (x,y) = x < w / 2 && y > h / 2

    heuristic : Point → Bool
    heuristic (x,y) = if x < w / 2  then 100 - 2 * x < y else 2 * x - 100 < y

    dist : Point → Int
    dist (x,y) = let d = if x < w / 2 then y - (h - 2 * x) else y - (h - 2 * (w - x)) in
      if d < 0 then 0 - d else d

    collect : List Robot → Int → List (Int × Int) → List (Int × Int)
    collect robots iter acc =
      if iter > w * h then acc else
      let pts = map (move w h iter) robots in

      -- This heuristic found it, attempting to find an outline of a large tree
      -- but the picture looks nothing like that, so I got lucky
      -- let cnt = foldl _+_ 0 $ map dist pts in
      -- so I'll go with min danger (although it takes 4 * as long)
      let q1 = count quad1 pts
          q2 = count quad2 pts
          q3 = count quad3 pts
          q4 = count quad4 pts
          cnt = q1 * q2 * q3 * q4 in
      collect robots (iter + 1) ((iter,cnt) :: acc)

main : IO Unit
main = do
  -- run "aoc2024/day14/eg.txt" 11 7
  run "aoc2024/day14/input.txt" 101 103
