content: -- |
  -- Module      : AOC.Challenge.Day10
  -- License     : BSD3
  --
  -- Stability   : experimental
  -- Portability : non-portable
  --
  -- Day 10.  See "AOC.Solver" for the types used in this module!
  
  module AOC.Challenge.Day10 (
      day10a
    , day10b
    ) where
  
  import           AOC.Common              (Point, parseAsciiMap, maximumValNE, lineTo, drop')
  import           AOC.Solver              ((:~>)(..))
  import           Control.Monad           (guard)
  import           Data.Foldable           (toList)
  import           Data.List               (sortOn, unfoldr)
  import           Data.Maybe              (listToMaybe)
  import           Data.Semigroup          (Max(..))
  import           Data.Semigroup.Foldable (foldMap1)
  import           Data.Set.NonEmpty       (NESet)
  import           Linear                  (V2(..))
  import qualified Data.Map                as M
  import qualified Data.Map.NonEmpty       as NEM
  import qualified Data.Set.NonEmpty       as NES
  
  angleTo :: Point -> Point -> Double
  angleTo p0 p1 = atan2 (-fromIntegral dx) (fromIntegral dy)
    where
      V2 dx dy = p1 - p0
  
  viewableIn :: NESet Point -> Point -> [Point]
  viewableIn s p = filter good . toList . NES.delete p $ s
    where
      good q = all (`NES.notMember` s) (lineTo p q)
  
  day10a :: NESet Point :~> Int
  day10a = MkSol
      { sParse = NES.nonEmptySet . M.keysSet . parseAsciiMap (\c -> guard (c == '#'))
      , sShow  = show
      , sSolve = \as -> Just . getMax . foldMap1 (Max . length . viewableIn as) $ as
      }
  
  day10b :: NESet Point :~> Point
  day10b = MkSol
      { sParse = NES.nonEmptySet . M.keysSet . parseAsciiMap (\c -> guard (c == '#'))
      , sShow  = \case V2 x y -> show $ x * 100 + y
      , sSolve = \as ->
          let (station, _) = maximumValNE $ NEM.fromSet (length . viewableIn as) as
              as'          = NES.delete station as
          in  listToMaybe . drop' 199 $
                unfoldr (uncurry (shootFrom station)) (Nothing, as')
      }
    where
      shootFrom p aim as = do
          as' <- NES.nonEmptySet as
          let targ:next:_ = dropper . cycle . sortOn (angleTo p) $ viewableIn as' p
          pure (targ, (Just next, NES.delete targ as'))
        where
          dropper = case aim of
            Nothing -> id
            Just a  -> dropWhile (/= a)
  
  