--- /dev/null
+-- Writeup at https://work.njae.me.uk/2024/12/17/advent-of-code-2024-day-17/
+
+
+import AoC
+import Data.Text (Text)
+import qualified Data.Text.IO as TIO
+import Data.Attoparsec.Text hiding (take)
+-- import Control.Applicative
+import qualified Data.IntMap.Strict as M
+import Data.IntMap.Strict ((!))
+import Control.Monad
+import Control.Monad.State.Strict
+import Control.Monad.Reader
+import Control.Monad.Writer
+import Control.Monad.RWS.Strict
+import Data.Bits
+
+type Memory = M.IntMap Int
+
+data Machine = Machine { regA :: Int
+ , regB :: Int
+ , regC :: Int
+ , ip :: Int
+ }
+ deriving (Show, Eq, Ord)
+
+type MachineHandler = RWS Memory [Int] Machine
+
+main :: IO ()
+main =
+ do dataFileName <- getDataFileName
+ text <- TIO.readFile dataFileName
+ let (machine, program) = successfulParse text
+ print machine
+ print program
+ print $ part1 program machine
+ print $ part2 program machine
+
+part1 :: Memory -> Machine -> [Int]
+part1 program machine = snd $ runMachine program machine
+
+part2 :: Memory -> Machine -> Int
+part2 program machine = minimum $ foldl' go [0] target
+ where
+ target = reverse $ M.elems program
+ go starts t =
+ do start <- starts
+ n <- [0..7]
+ let res = snd $ runModified program machine (start * 8 + n)
+ guard (head res == t)
+ return $ start * 8 + n
+
+runModified :: Memory -> Machine -> Int -> (Machine, [Int])
+runModified program machine n = runMachine program (machine { regA = n })
+
+runMachine :: Memory -> Machine -> (Machine, [Int])
+runMachine memory machine = execRWS runAll memory machine
+
+runAll :: MachineHandler ()
+runAll =
+ do mem <- ask
+ ip <- gets ip
+ if ip `M.notMember` mem then return () else runStep >> runAll
+
+runStep :: MachineHandler ()
+runStep =
+ do mem <- ask
+ i <- gets ip
+ let opcode = mem!i
+ let operand = mem!(i+1)
+ putOutput opcode operand
+ machine <- get
+ let machine' = perform opcode operand i machine
+ put machine'
+
+putOutput :: Int -> Int -> MachineHandler ()
+putOutput 5 operand =
+ do machine <- get
+ let v = comboValue operand machine
+ tell [v `mod` 8]
+putOutput _ _ = return ()
+
+perform :: Int -> Int -> Int -> Machine -> Machine
+perform 0 operand i machine = machine { regA = machine.regA `div` denom , ip=i+2 }
+ where denom = 2 ^ (comboValue operand machine)
+perform 1 operand i machine = machine { regB = machine.regB `xor` operand , ip=i+2 }
+perform 2 operand i machine = machine { regB = b , ip=i+2 }
+ where b = (comboValue operand machine) `mod` 8
+perform 3 operand i machine
+ | machine.regA == 0 = machine { ip=i+2 }
+ | otherwise = machine { ip = operand }
+perform 4 _ i machine = machine { regB = machine.regB `xor` machine.regC , ip=i+2 }
+perform 5 _ i machine = machine { ip=i+2 }
+perform 6 operand i machine = machine { regB = machine.regA `div` denom , ip=i+2 }
+ where denom = 2 ^ (comboValue operand machine)
+perform 7 operand i machine = machine { regC = machine.regA `div` denom , ip=i+2 }
+ where denom = 2 ^ (comboValue operand machine)
+
+comboValue :: Int -> Machine -> Int
+comboValue 4 machine = regA machine
+comboValue 5 machine = regB machine
+comboValue 6 machine = regC machine
+comboValue n _ = n
+
+-- Parse the input file
+
+fullMachineP :: Parser (Machine, Memory)
+machineP :: Parser Machine
+regP :: Parser Int
+memoryP :: Parser Memory
+
+fullMachineP = (,) <$> machineP <* endOfLine <*> memoryP
+
+machineP = machineify <$> regP <*> regP <*> regP
+ where machineify a b c = Machine a b c 0
+
+regP = ("Register " *> letter *> ": " *> decimal) <* endOfLine
+
+memoryP = memify <$> ("Program: " *> (decimal `sepBy` ","))
+ where memify = M.fromList . zip [0..]
+
+successfulParse :: Text -> (Machine, Memory)
+successfulParse input =
+ case parseOnly fullMachineP input of
+ Left _err -> (Machine 0 0 0 0, M.empty) -- TIO.putStr $ T.pack $ parseErrorPretty err
+ Right fullMachine -> fullMachine