module Day19 where

import AoC

import Control.Applicative
import Data.Char
import Data.Foldable
import Data.Map (Map, (!))
import qualified Data.Map as M
import Data.Maybe
import Data.Tuple
import Text.ParserCombinators.ReadP
import Text.Read.Lex

data RuleElement = Literal String | Reference Int

parseElement :: ReadP RuleElement
parseElement = parseRef <|> parseLit
    where parseRef = Reference <$> readDecP
          parseLit = Literal <$> between (char '"') (char '"') (many1 $ satisfy isAlphaNum)

parseRule :: ReadP (Int, [[RuleElement]])
parseRule = do ruleNum <- readDecP
               string ": "
               alternatives <- parseAlternatives
               return (ruleNum, alternatives)
    where parseAlternatives = parseSequence `sepBy` string " | "
          parseSequence = parseElement `sepBy` char ' '

buildParser :: Map Int [[RuleElement]] -> Int -> ReadP String
buildParser m i = buildAlternatives $ m ! i
    where buildAlternatives = asum . map buildSequence
          buildSequence = foldl1 (liftA2 (++)) . map buildElement
          buildElement (Literal s) = string s
          buildElement (Reference j) = buildParser m j

countRootMatches :: [String] -> Map Int [[RuleElement]] -> Int
countRootMatches input rules = length . filter (isJust . oneCompleteResult rootParser) $ input
    where rootParser = buildParser rules 0

main = runAoC splitInput run (run . withExtraRules)
    where splitInput = swap . fmap tail . break (=="") . lines
          withExtraRules = fmap (++ ["8: 42 | 42 8", "11: 42 31 | 42 11 31"])
          run = uncurry countRootMatches . fmap parseRules
          parseRules = M.fromList . map (fromJust . oneCompleteResult parseRule)
