module Day24

import Prelude
import Node
import Aoc
import SortedMap

data Value : U where
  One Zero None : Value

data Op : U where
  Xor And Or : Op

instance Eq Op where
  Xor == Xor = True
  And == And = True
  Or == Or = True
  _ == _ = False

record Gate where
  constructor MkG
  in1 : String
  in2 : String
  op : Op
  out : String

in1 : Gate → String
in1 g = g.in1

in2 : Gate → String
in2 g = g.in2

out : Gate → String
out g = g.out

op : Gate → Op
op g = g.op

Wire : U
Wire = String × Int

parseFile : String → Either String (List Wire × List Gate)
parseFile text = do
  let (a :: b :: Nil) = split (trim text) "\n\n" | _ => Left "too many parts"
  wires <- traverse parseWire $ lines a
  gates <- traverse parseGate $ lines b
  Right (wires, gates)

  where
    parseWire : String → Either String (String × Int)
    parseWire s = let (a :: b :: Nil) = split s ": " | _ => Left $ "bad wire: " ++ s in
      Right (a, stringToInt b)

    getOp : String → Either String Op
    getOp "XOR" = Right Xor
    getOp "AND" = Right And
    getOp "OR"  = Right Or
    getOp op = Left $ "bad op " ++ op

    parseGate : String → Either String Gate
    parseGate s = do
      let (in1 :: op :: in2 :: _ :: out :: Nil) = split s " " | _ => Left $ "bad gate: " ++ s
      op <- getOp op
      Right $ MkG in1 in2 op out


State : U
State = SortedMap String Int


mkFire : List Gate → SortedMap String (List String)
mkFire gates = go EmptyMap gates
  where
    go : SortedMap String (List String) → List Gate → SortedMap String (List String)
    go acc Nil = acc
    go acc (g :: gs) =
      let acc = updateMap (in1 g) (out g :: fromMaybe Nil (snd <$> lookupMap (in1 g) acc)) acc in
      let acc = updateMap (in2 g) (out g :: fromMaybe Nil (snd <$> lookupMap (in2 g) acc)) acc in
      go acc gs



exec : SortedMap String Gate → SortedMap String (List String) → List String → State → State
exec gateMap fireMap todo state = go todo state
  where
    combine : Op → Int → Int → Int
    combine And 1 x = x
    combine Xor 1 1 = 0
    combine Xor 1 0 = 1
    combine Xor 0 1 = 1
    combine Or 1 _ = 1
    combine Or _ 1 = 1
    combine _ _ _ = 0

    go : List String → State → State
    go Nil st = st
    go (x :: xs) st = case lookupMap x st of
      -- already done
      Just _ => go xs st
      Nothing => case lookupMap' x gateMap of
        Nothing => go xs st
        Just gate => case (lookupMap' (in1 gate) st, lookupMap' (in2 gate) st) of
          (Just x, Just y) =>
            let v = combine (op gate) x y
                st = updateMap (out gate) v st
                foo = fromMaybe Nil $ lookupMap' (out gate) fireMap
                xs' = foo ++ xs
            in go xs' st
          _ => go xs st

bitsToInt : Int → List Int → Int
bitsToInt _ Nil = 0
bitsToInt m (x :: xs) = m * x + bitsToInt (2 * m) xs


-- so I can find the lowest bit that is wrong
-- or trace out the circuit?

infixl 5 _%_
pfunc _%_ : Int → Int → Int := `(x,y) => x % y`

label : Char -> Int -> String
label c bit = pack $ c :: chr (bit / 10 + 48) :: chr ((bit % 10) + 48) :: Nil

range : Int → Int → List Int
range a b = if a < b then a :: range (a + 1) b else Nil

swapPins : String → String → Gate → Gate
swapPins a g (MkG i1 i2 op out) =
  if out == a then MkG i1 i2 op g
  else if out == g then MkG i1 i2 op a
  else MkG i1 i2 op out

fail : ∀ a. String -> a
fail msg = let x = trace "FAIL" msg in ?

check : List Gate → List Int → String → Either (String × String) Unit
check gates Nil carry = Right MkUnit
check gates (bit :: bits) carry = do
  let xl = label 'x' bit
  let (Just a1) = matchIn xl And | _ => fail $ "no a1 " ++ show bit
  let (Just x1) = matchIn xl Xor | _ => fail $ "no x1 " ++ show bit
  -- just peel of the carry for bit0
  let (False) = bit == 0 | _ => check gates bits (out a1)

  let (Just x2) = matchIn carry Xor
    | _ => do
      -- carry is bad
      let (Just x2) = matchOut (label 'z' bit) Xor | _ => fail $ "no x2 for " ++ show bit
      if in1 x2 == out x1 then Left (in2 x2, carry) else Left (in1 x2, carry)
  let (Just a2) = matchIn carry And | _ => fail $ "no a2 " ++ show bit
  checkPins x2 carry (out x1)
  checkPins a2 carry (out x1)

  case (matchIn (out a1) Or, matchIn (out a2) Or) of
    (Nothing, Nothing) => fail "todo2"
    (Just g, Nothing) => checkPins g (out a1) (out a2)
    (Nothing, Just g) => checkPins g (out a1) (out a2)
    (Just g, Just g') =>
      if out g == out g'
        then check gates bits (out g)
        else fail "todo"

  where
    checkPins : Gate → String → String → Either (String × String) Unit
    checkPins g a b =
      if a == in1 g && b == in2 g then pure MkUnit
      else if a == in2 g && b == in1 g then pure MkUnit
      else if a == in1 g then Left (b, in2 g)
      else if a == in2 g then Left (b, in1 g)
      else if b == in1 g then Left (a, in2 g)
      else Left (a, in1 g)

    matchOut : String -> Op -> Maybe Gate
    matchOut l o = find (\ g => out g == l && op g == o) gates

    matchIn : String -> Op -> Maybe Gate
    matchIn l o = find (\ g => (in1 g == l || in2 g == l) && op g == o) gates

trampoline : List Gate → List String → IO (List String)
trampoline gates acc = do
  case check gates (range 0 44) "" of
    Right _ => pure acc
    Left (a,b) => do
      putStrLn $ "SWAP " ++ a ++ " and " ++ b
      trampoline (map (swapPins a b) gates) (a :: b :: acc)

run : String -> IO Unit
run fn = do
  putStrLn fn
  text <- readFile fn
  let (Right (wires, gates)) = parseFile text | Left msg => putStrLn $ "fail: " ++ msg
  let state = foldMap const EmptyMap wires
  let gateMap = foldMap const EmptyMap $ map (\ gate => (out gate, gate)) gates
  let fireMap = mkFire gates
  let init = join $ map snd $ mapMaybe (\ wire => lookupMap wire fireMap) $ map fst wires
  let state' = exec gateMap fireMap init state
  let bits = map snd $ filter (_<_ "z" ∘ fst) $ toList state'
  let p1 = bitsToInt 1 bits
  putStrLn $ "part1 " ++ show p1

  stuff <- trampoline gates Nil
  putStrLn $ "part2 " ++ (joinBy "," $ qsort _<_ stuff)
  putStrLn ""

main : IO Unit
main = do
  -- run "aoc2024/day24/eg.txt"
  -- run "aoc2024/day24/eg2.txt"
  -- this is the only adder circuit
  run "aoc2024/day24/input.txt"
