module Day3

import Prelude
import Node
import Aoc

Parser : U → U
Parser a = List Char → Maybe (a × List Char)

instance Monad Parser where
  pure a = \ cs => Just (a, cs)
  bind ma mab = \ cs => ma cs >>= uncurry mab

instance Alternative Parser where
  pa <|> pb = \ cs => case pa cs of
    Nothing => pb cs
    res => res

fail : ∀ a. Parser a
fail = \ cs => Nothing

satisfy : (Char → Bool) → Parser Char
satisfy pred = λ cs => case cs of
  Nil => Nothing
  (c :: cs) => if pred c then Just (c, cs) else Nothing

match : Char → Parser Char
match d = satisfy (_==_ d)

any : Parser Char
any = satisfy (λ _ => True)

some many : ∀ a. Parser a → Parser (List a)
many p = some p <|> pure Nil
some p = do
  v <- p
  vs <- many p
  pure (v :: vs)

pnum : Parser Int
pnum = do
  chars <- many (satisfy isDigit)
  if S (S (S Z)) < length chars then fail
    else pure $ stringToInt $ pack chars

data Inst : U where
  Mult : Int → Int → Inst
  Do : Inst
  Dont : Inst

mul : Parser Inst
mul = do
  match 'm'
  match 'u'
  match 'l'
  match '('
  x <- pnum
  match ','
  y <- pnum
  match ')'
  pure $ Mult x y

pdo : Parser Inst
pdo = do
  match 'd'
  match 'o'
  match '('
  match ')'
  pure Do

pdont : Parser Inst
pdont = do
  match 'd'
  match 'o'
  match 'n'
  match '\''
  match 't'
  match '('
  match ')'
  pure Dont

some' many' : ∀ a. Parser a → Parser (List a)
many' p = do
  pure MkUnit
  some' p <|> (any >> many' p) <|> pure Nil

some' p = do
  v <- p
  vs <- many' p
  pure (v :: vs)

inst : Parser Inst
inst = mul <|> pdo <|> pdont

pfile : Parser (List Inst)
pfile = many' inst

value : Inst → Int
value (Mult x y) = x * y
value _ = 0

part2 : List Inst → Bool → Int → Int
part2 Nil _ acc = acc
part2 (Do :: insts) _ acc = part2 insts True acc
part2 (Dont :: insts) _ acc = part2 insts False acc
part2 (_ :: insts) False acc = part2 insts False acc
part2 (Mult x y :: insts) True acc = part2 insts True (acc + x * y)

run : String → IO Unit
run fn = do
  putStrLn fn
  text <- trim <$> readFile fn
  let (Just (insts, Nil)) = pfile (unpack text) | _ => putStrLn "parse failed"
  let part1 = foldl _+_ 0 $ map value insts
  putStrLn $ "part1 " ++ show part1
  putStrLn $ "part2 " ++ show (part2 insts True 0)

main : IO Unit
main = do
  run "aoc2024/day3/eg.txt"
  run "aoc2024/day3/input.txt"
