content: -- |
  -- Module      : AOC.Challenge.Day06
  -- Copyright   : (c) Justin Le 2018
  -- License     : BSD3
  --
  -- Maintainer  : justin@jle.im
  -- Stability   : experimental
  -- Portability : non-portable
  --
  -- Day 6.  See "AOC.Solver" for the types used in this module!
  
  module AOC.Challenge.Day06 (
      day06a
    , day06b
    ) where
  
  import           AOC.Common         (freqs, clearOut, Point, boundingBox, mannDist)
  import           AOC.Solver         ((:~>)(..), dyno_)
  import           Control.Monad      (guard, (<=<))
  import           Data.Char          (isDigit)
  import           Data.Functor       ((<&>))
  import           Data.Ix            (range)
  import           Data.List.NonEmpty (NonEmpty(..))
  import           Data.Witherable    (mapMaybe, catMaybes)
  import           Linear             (V2(..))
  import           Text.Read          (readMaybe)
  import qualified Data.List.NonEmpty as NE
  import qualified Data.Map           as M
  import qualified Data.Set           as S
  
  type Box   = V2 Point
  
  bbPoints :: Box -> [Point]
  bbPoints (V2 mins maxs) = range (mins, maxs)
  
  labelVoronoi
      :: NonEmpty Point     -- ^ set of sites
      -> Point              -- ^ point to label
      -> Maybe Point        -- ^ the label, if unique
  labelVoronoi sites p = do
      (closestSite, _) :| [] <- Just
                              . NE.head
                              . NE.groupWith1 snd
                              . NE.sortWith snd
                              $ dists
      pure closestSite
    where
      dists                  = sites <&> \site -> (site, mannDist p site)
  
  day06a :: NonEmpty Point :~> Int
  day06a = MkSol
      { sParse = (NE.nonEmpty <=< traverse parseLine) . lines
      , sShow  = show
      , sSolve = \sites -> Just $
          let bb    = boundingBox sites
              voron = catMaybes
                    . M.fromSet (labelVoronoi sites)
                    . S.fromList
                    . bbPoints
                    $ bb
              edges = S.fromList
                    . mapMaybe (\(point, site) -> site <$ guard (onEdge bb point))
                    . M.toList
                    $ voron
          in  maximum . freqs . M.filter (`S.notMember` edges) $ voron
      }
    where
      onEdge :: Box -> Point -> Bool
      onEdge (V2 xMin yMin `V2` V2 xMax yMax) (V2 x y) =
              x `elem` [xMin, xMax]
           || y `elem` [yMin, yMax]
  
  day06b :: NonEmpty Point :~> Int
  day06b = MkSol
      { sParse = (NE.nonEmpty <=< traverse parseLine) . lines
      , sShow  = show
      , sSolve = \sites ->
              Just
            . length
            . filter ((< dyno_ "lim" 10000) . (`totalDist` sites))
            . bbPoints
            . boundingBox
            $ sites
      }
    where
      totalDist p = sum . fmap (mannDist p)
  
  parseLine :: String -> Maybe Point
  parseLine = (packUp =<<)
            . traverse readMaybe
            . words
            . clearOut (not . isDigit)
    where
      packUp [x,y] = Just $ V2 x y
      packUp _     = Nothing
  