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

<https://adventofcode.com/2016/day/4>

-}
module Main where

import Advent (format, counts)
import Data.Char (ord, chr)
import Data.List (sortBy)
import qualified Data.Map as Map

type Entry = ([String], Int, String)

-- | >>> :main
-- 158835
-- [993]
main :: IO ()
IO ()
main =
 do [([String], Int, String)]
input <- [format|2016 4 ((%a+-)*%d[%a*]%n)*|]
    let valid :: [([String], Int, String)]
valid = [([String], Int, String)
e | ([String], Int, String)
e <- [([String], Int, String)]
input, ([String], Int, String) -> Bool
isGoodEntry ([String], Int, String)
e]
    Int -> IO ()
forall a. Show a => a -> IO ()
print ([Int] -> Int
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Int
sid | ([String]
_, Int
sid, String
_) <- [([String], Int, String)]
valid])
    [Int] -> IO ()
forall a. Show a => a -> IO ()
print [Int
sid | e :: ([String], Int, String)
e@([String]
_, Int
sid, String
_) <- [([String], Int, String)]
valid, ([String], Int, String) -> String
decryptEntry ([String], Int, String)
e String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"northpole object storage"]

decryptEntry :: Entry -> String
decryptEntry :: ([String], Int, String) -> String
decryptEntry ([String]
name, Int
sid, String
_) = [String] -> String
unwords ((String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map ((Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map (Int -> Char -> Char
decrypt Int
sid)) [String]
name)

isGoodEntry :: Entry -> Bool
isGoodEntry :: ([String], Int, String) -> Bool
isGoodEntry ([String]
name, Int
_, String
hash) = String
hash String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> String
computeHash ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [String]
name)

computeHash :: String -> String
computeHash :: String -> String
computeHash String
x = Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
5 (((Char, Int) -> Char) -> [(Char, Int)] -> String
forall a b. (a -> b) -> [a] -> [b]
map (Char, Int) -> Char
forall a b. (a, b) -> a
fst (((Char, Int) -> (Char, Int) -> Ordering)
-> [(Char, Int)] -> [(Char, Int)]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (Char, Int) -> (Char, Int) -> Ordering
forall {a} {a}. (Ord a, Ord a) => (a, a) -> (a, a) -> Ordering
ordering (Map Char Int -> [(Char, Int)]
forall k a. Map k a -> [(k, a)]
Map.toList (String -> Map Char Int
forall (f :: * -> *) a. (Foldable f, Ord a) => f a -> Map a Int
counts String
x))))
  where
    ordering :: (a, a) -> (a, a) -> Ordering
ordering (a
xa,a
xn) (a
ya,a
yn)
       = a -> a -> Ordering
forall a. Ord a => a -> a -> Ordering
compare a
yn a
xn -- descending
      Ordering -> Ordering -> Ordering
forall a. Semigroup a => a -> a -> a
<> a -> a -> Ordering
forall a. Ord a => a -> a -> Ordering
compare a
xa a
ya -- ascending

decrypt :: Int -> Char -> Char
decrypt :: Int -> Char -> Char
decrypt Int
n Char
c = Int -> Char
chr ((Char -> Int
ord Char
c Int -> Int -> Int
forall a. Num a => a -> a -> a
- Char -> Int
ord Char
'a' Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
n) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
26 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Char -> Int
ord Char
'a')