import Control.Concurrent       (setNumCapabilities)
import Control.Monad.Par        (runPar, get, spawnP)
import Control.Monad            (join, (>=>))
import Data.List.Split          (chunksOf)
import Data.List                (intercalate, mapAccumL, genericTake, genericDrop)
import Data.Bifunctor           (bimap)
import GHC.Conc                 (getNumProcessors)
import Math.NumberTheory.Primes (factorise, unPrime)
import Text.Printf              (printf)

chowla :: Word -> Word
chowla 1 = 0
chowla n = f n
  where
    f = (-) =<< pred . product . fmap sumFactor . factorise
    sumFactor (n, e) = foldr (\p s -> s + unPrime n^p) 1 [1..e]

chowlas :: [Word] -> [(Word, Word)]
chowlas [] = []
chowlas xs = runPar $ join <$>
  (mapM (spawnP . fmap ((,) <*> chowla)) >=> mapM get) (chunksOf (10^6) xs)

chowlaPrimes :: [(Word, Word)] -> (Word, Word) -> (Word, Word)
chowlaPrimes chowlas range = (count chowlas, snd range)
  where
    isPrime (1, n) = False
    isPrime (_, n) = n == 0
    count = fromIntegral . length . filter isPrime . between range
    between (min, max) = genericTake (max - pred min) . genericDrop (pred min)

chowlaPerfects :: [(Word, Word)] -> [Word]
chowlaPerfects = fmap fst . filter isPerfect
  where 
    isPerfect (1, _) = False
    isPerfect (n, c) = c == pred n

commas :: (Show a, Integral a) => a -> String
commas = reverse . intercalate "," . chunksOf 3 . reverse . show

main :: IO ()
main = do
  cores <- getNumProcessors
  setNumCapabilities cores
  printf "Using%d cores\n" cores

  mapM_ (uncurry (printf "chowla(%2d) =%d\n")) $ take 37 allChowlas
  mapM_ (uncurry (printf "There are%8s primes <%10s\n"))
    (chowlaP
       [ (1, 10^2)
       , (succ $ 10^2, 10^3)
       , (succ $ 10^3, 10^4)
       , (succ $ 10^4, 10^5)
       , (succ $ 10^5, 10^6)
       , (succ $ 10^6, 10^7) ])

  mapM_ (printf "%10s is a perfect number.\n" . commas) perfects
  printf "There are%2d perfect numbers < 35,000,000\n" $ length perfects
  where
    chowlaP = fmap (bimap commas commas) . snd
      . mapAccumL (\total (count, max) -> (total + count, (total + count, max))) 0
      . fmap (chowlaPrimes $ take (10^7) allChowlas)
    perfects = chowlaPerfects allChowlas
    allChowlas = chowlas [1..35*10^6]