import           Data.List.Split          (chunksOf)
import qualified Data.MemoCombinators  as Memo
import           Math.NumberTheory.Primes (unPrime, factorise)
import           Text.Printf              (printf)

moebius :: Integer -> Int
moebius = product . fmap m . factorise
  where
    m (p, e)
      | unPrime p == 0 = 0
      | e == 1 = -1
      | otherwise = 0

mertens :: Integer -> Int
mertens = Memo.integral (\n -> sum $ fmap moebius [1..n])

countZeros :: [Integer] -> Int
countZeros = length . filter ((==0) . mertens)

crossesZero :: [Integer] -> Int
crossesZero = length . go . fmap mertens
  where
    go (x:y:xs) 
      | y == 0 && x /= 0 = y: go (y:xs)
      | otherwise        = go (y:xs)
    go _ = []

main :: IO ()
main = do
  printf "The first 99 terms for M(1..99):\n\n   "
  mapM_ (printf "%3d" . mertens) [1..9] >> printf "\n"
  mapM_ (\row -> mapM_ (printf "%3d" . mertens) row >> printf "\n") $ chunksOf 10 [10..99]
  printf "\nM(n) is zero%d times for 1 <= n <= 1000.\n" $ countZeros [1..1000]
  printf "M(n) crosses zero%d times for 1 <= n <= 1000.\n" $ crossesZero [1..1000]