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

<https://adventofcode.com/2017/day/9>

Day 9 poses a problem of parsing a nested bracket structure.

-}
module Main where

import Advent (format, stageTH)
import Control.Applicative ((<|>))
import Data.Foldable (traverse_)
import Linear (V2(V2))
import Text.ParserCombinators.ReadP (ReadP, get, between, sepBy)

-- | Parse the group string format as defined in Day 9. Parse
-- result is a vector containing the group score and garbage
-- character count.
parseGroup ::
  Int            {- ^ group depth                -} ->
  ReadP (V2 Int) {- ^ group score, garbage count -}
parseGroup :: Int -> ReadP (V2 Int)
parseGroup Int
n =
  (V2 Int -> V2 Int -> V2 Int) -> V2 Int -> [V2 Int] -> V2 Int
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl V2 Int -> V2 Int -> V2 Int
forall a. Num a => a -> a -> a
(+) (Int -> Int -> V2 Int
forall a. a -> a -> V2 a
V2 Int
n Int
0) ([V2 Int] -> V2 Int) -> ReadP [V2 Int] -> ReadP (V2 Int)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
  ReadP String -> ReadP String -> ReadP [V2 Int] -> ReadP [V2 Int]
forall open close a.
ReadP open -> ReadP close -> ReadP a -> ReadP a
between ReadP String
"{" ReadP String
"}"
    (ReadP (V2 Int) -> ReadP String -> ReadP [V2 Int]
forall a sep. ReadP a -> ReadP sep -> ReadP [a]
sepBy (Int -> ReadP (V2 Int)
parseGroup (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)  ReadP (V2 Int) -> ReadP (V2 Int) -> ReadP (V2 Int)
forall a. ReadP a -> ReadP a -> ReadP a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>  Int -> Int -> V2 Int
forall a. a -> a -> V2 a
V2 Int
0 (Int -> V2 Int) -> ReadP Int -> ReadP (V2 Int)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ReadP Int
parseGarbage) ReadP String
",")

-- | Parse a angle-bracket bracketed region of characters and return the
-- number of non-ignored, contained characters. Characters including and
-- following a @!@ are ignored inside garbgae.
parseGarbage :: ReadP Int {- ^ garbage count -}
parseGarbage :: ReadP Int
parseGarbage = ReadP String
"<" ReadP String -> ReadP Int -> ReadP Int
forall a b. ReadP a -> ReadP b -> ReadP b
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Int -> ReadP Int
elt Int
0

elt :: Int -> ReadP Int
elt :: Int -> ReadP Int
elt Int
n =
 do Char
x <- ReadP Char
get
    case Char
x of
      Char
'!' -> ReadP Char
get ReadP Char -> ReadP Int -> ReadP Int
forall a b. ReadP a -> ReadP b -> ReadP b
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> Int -> ReadP Int
elt Int
n
      Char
'>' -> Int -> ReadP Int
forall a. a -> ReadP a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Int
n
      Char
_   -> Int -> ReadP Int
elt (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)

-- | Starting parser named with a single letter to work with format.
p :: ReadP (V2 Int)
p :: ReadP (V2 Int)
p = Int -> ReadP (V2 Int)
parseGroup Int
1

stageTH

-- | Print solution for Day 9. Puzzle input can be overriden by command-line
-- argument.
--
-- >>> :main
-- 14212
-- 6569
main :: IO ()
IO ()
main =
 do V2 Int
answers <- [format|2017 9 @p%n|]
    (Int -> IO ()) -> V2 Int -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ Int -> IO ()
forall a. Show a => a -> IO ()
print V2 Int
answers