{-# Language LambdaCase, QuasiQuotes, TemplateHaskell #-}
{-|
Module      : Main
Description : Day 12 solution
Copyright   : (c) Eric Mertens, 2020
License     : ISC
Maintainer  : emertens@gmail.com

<https://adventofcode.com/2020/day/12>

-}
module Main (main) where

import Advent.Coord
import Advent (format, stageTH)
import Data.List (foldl')

type Command = Either (D, Int) (T, A)
data D = DN | DS | DE | DW | DF
data T = TL | TR
data A = A90 | A180 | A270

stageTH

-- | The simulation tracks the current location and the vector used
-- when moving /forward/.
data Sim = Sim { Sim -> Coord
here, Sim -> Coord
vect :: !Coord }

-- | Apply an update function to an @a@ typed subcomponent of a
-- @s@ typed value.
type Update s a = (a -> a) -> (s -> s)

mapHere, mapVect :: Update Sim Coord
mapHere :: Update Sim Coord
mapHere Coord -> Coord
f Sim
s = Sim
s { here = f (here s) }
mapVect :: Update Sim Coord
mapVect Coord -> Coord
f Sim
s = Sim
s { vect = f (vect s) }

-- |
-- >>> :main
-- 1007
-- 41212
main :: IO ()
IO ()
main =
  do [Either (D, Int) (T, A)]
inp <- [format|2020 12 ((@D%u|@T@A)%n)*|]
     Int -> IO ()
forall a. Show a => a -> IO ()
print (Update Sim Coord -> Sim -> [Either (D, Int) (T, A)] -> Int
walk Update Sim Coord
mapHere (Coord -> Coord -> Sim
Sim Coord
origin Coord
east                ) [Either (D, Int) (T, A)]
inp)
     Int -> IO ()
forall a. Show a => a -> IO ()
print (Update Sim Coord -> Sim -> [Either (D, Int) (T, A)] -> Int
walk Update Sim Coord
mapVect (Coord -> Coord -> Sim
Sim Coord
origin (Int -> Coord -> Coord -> Coord
move Int
10 Coord
east Coord
north)) [Either (D, Int) (T, A)]
inp)

walk :: Update Sim Coord -> Sim -> [Command] -> Int
walk :: Update Sim Coord -> Sim -> [Either (D, Int) (T, A)] -> Int
walk Update Sim Coord
f Sim
st [Either (D, Int) (T, A)]
xs = Coord -> Coord -> Int
manhattan Coord
origin (Sim -> Coord
here ((Sim -> Either (D, Int) (T, A) -> Sim)
-> Sim -> [Either (D, Int) (T, A)] -> Sim
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (Update Sim Coord -> Sim -> Either (D, Int) (T, A) -> Sim
action Update Sim Coord
f) Sim
st [Either (D, Int) (T, A)]
xs))

action ::
  Update Sim Coord {- ^ cardinal direction component -} ->
  Sim -> Command -> Sim
action :: Update Sim Coord -> Sim -> Either (D, Int) (T, A) -> Sim
action Update Sim Coord
mapCard Sim
st = \case
  Left  (D
DN,   Int
n) -> Update Sim Coord
mapCard (Int -> Coord -> Coord -> Coord
move Int
n Coord
north    ) Sim
st
  Left  (D
DS,   Int
n) -> Update Sim Coord
mapCard (Int -> Coord -> Coord -> Coord
move Int
n Coord
south    ) Sim
st
  Left  (D
DE,   Int
n) -> Update Sim Coord
mapCard (Int -> Coord -> Coord -> Coord
move Int
n Coord
east     ) Sim
st
  Left  (D
DW,   Int
n) -> Update Sim Coord
mapCard (Int -> Coord -> Coord -> Coord
move Int
n Coord
west     ) Sim
st
  Left  (D
DF,   Int
n) -> Update Sim Coord
mapHere (Int -> Coord -> Coord -> Coord
move Int
n (Sim -> Coord
vect Sim
st)) Sim
st
  Right (T
TL, A
A90) -> Update Sim Coord
mapVect Coord -> Coord
turnLeft           Sim
st
  Right (T
TL,A
A270) -> Update Sim Coord
mapVect Coord -> Coord
turnRight          Sim
st
  Right (T
TL,A
A180) -> Update Sim Coord
mapVect Coord -> Coord
turnAround         Sim
st
  Right (T
TR, A
A90) -> Update Sim Coord
mapVect Coord -> Coord
turnRight          Sim
st
  Right (T
TR,A
A180) -> Update Sim Coord
mapVect Coord -> Coord
turnAround         Sim
st
  Right (T
TR,A
A270) -> Update Sim Coord
mapVect Coord -> Coord
turnLeft           Sim
st

move :: Int -> Coord -> Coord -> Coord
move :: Int -> Coord -> Coord -> Coord
move Int
n Coord
v Coord
d = Int -> Coord -> Coord
scaleCoord Int
n Coord
v Coord -> Coord -> Coord
forall a. Num a => a -> a -> a
+ Coord
d