{-|
Module      : Primes
Description : Module with functions to calculate Primme Numbers. Implements 3 sieves.
Copyright   : (c) Luis Rodrigues Soares, 2015
License     : MIT
Maintainer  : luis@decomputed.com
Stability   : experimental
-}

module Numeric.Euler.Primes (
  isPrime,
  trialAndDivision,
  erastothenes,
  sundaram,
  atkin
  ) where

import Data.List (sort)


{-------------------------------------------------
-------------------------------------------------}

{-|
The `isPrime` function checks whether a number `a` is prime by successively checking the remainder
of the integer division with all numbers up to @a-1@.
-}
isPrime :: Integral a => a -> Bool
isPrime n
  | n == 2    = True
  | otherwise = not (any (\i -> n `mod` i == 0) [3..n-1])

{-|
The `trialAndDivision` function calculates prime numbers up to a certain limit by using the traditional
(and very inneficient) trial and division method.
-}
trialAndDivision :: Int -> [Int]
trialAndDivision limit = 2:3:5:[n | n <- [7,9..limit], isPrime n]


{-------------------------------------------------
-------------------------------------------------}

{-|
This is a naïve implementation of the Sieve of Erastothenes.
-}
erastothenes :: Int -> [Int]
erastothenes limit = eSieve [3,5..limit] [2]

eSieve :: [Int] -> [Int] -> [Int]
eSieve [] primes     = primes
eSieve (x:xs) primes = eSieve [y | y <- xs, y `mod` x /= 0] (x:primes)


{-------------------------------------------------
-------------------------------------------------}

initialSundaramSieve :: Int -> [Int]
initialSundaramSieve limit =
  let topi = floor (sqrt (fromIntegral limit / 2) :: Double) in
  [i + j + 2 * i * j | i <- [1..topi],
   j <- [i..floor ((fromIntegral(limit-i) / fromIntegral(2*i+1)) :: Double)]]

{-|
An implementation of the Sieve of Sundaram.
-}
sundaram :: Int -> [Int]
sundaram limit =
  let halfLimit = floor( ((fromIntegral limit / 2)-1) :: Double) in
  2:removeComposites [1..halfLimit] (sort $ initialSundaramSieve halfLimit) 

removeComposites :: [Int] -> [Int] -> [Int]
removeComposites [] _ = []
removeComposites (n:ns) [] = (2*n+1) : removeComposites ns []
removeComposites (n:ns) (c:cs)
  | n == c    = removeComposites ns cs
  | n > c     = removeComposites (n:ns) cs
  | otherwise = (2*n+1) : removeComposites ns (c:cs)

{---------------------------------------------------
---------------------------------------------------}


initialAtkinSieve :: Int -> [(Int,Int)]
initialAtkinSieve limit =
  sort $ zip [n
             | n <- [60 * w + x
                    | w <- [0..limit `div` 60],
                      x <- [1,7,11,13,17,19,23,29,31,37,41,43,47,49,53,59]
                    ],
               n <= limit]
  (take limit [0,0..])


aFlip :: (Int,Int) -> (Int,Int)
aFlip (x,y)
  | y == 0        = (x,1)
  | y == 1        = (x,0)
  | otherwise     = error "Needs zero or one"


flipAll :: [Int] -> [(Int, Int)] -> [(Int, Int)]
flipAll _      [] = []
flipAll []     ss = ss
flipAll (f:fs) ((s,b):ss)
  | s < f      = (s,b): flipAll (f:fs) ss
  | s == f     = flipAll fs (aFlip (s,b):ss)
  | otherwise  = flipAll fs ((s,b):ss)


firstStep :: Int ->  [(Int,Int)] ->  [(Int,Int)]
firstStep size sieve =
  let topx = floor( sqrt(fromIntegral (size `div` 4)) :: Double)
      topy = floor( sqrt(fromIntegral size) :: Double) in
  flipAll (sort [n | n <- [4*x^(2::Integer) + y^(2::Integer)| x <- [1..topx],
                           y <- [1,3..topy]],
                 n `mod` 60 `elem` [1,13,17,29,37,41,49,53]])
  sieve


secondStep :: Int ->  [(Int,Int)] ->  [(Int,Int)]
secondStep size sieve =
  let topx = floor( sqrt(fromIntegral (size `div` 3)) :: Double)
      topy = floor( sqrt(fromIntegral size) :: Double) in
  flipAll (sort [n | n <- [3*x^(2::Integer) + y^(2::Integer) | x <- [1,3..topx],
                           y <- [2,4..topy]],
                 n `mod` 60 `elem` [7,19,31,43]])
  sieve


thirdStep :: Int ->  [(Int,Int)] ->  [(Int,Int)]
thirdStep size sieve =
  let topx = floor( sqrt(fromIntegral size) :: Double) in
  flipAll (sort [n | n <- [3*x^(2::Integer) - y^(2 :: Integer) | x <- [1..topx],
                           y <- [(x-1),(x-3)..1]],
                 n `mod` 60 `elem` [11,23,47,59]])
  sieve

unmarkMultiples :: Int -> Int -> [(Int,Int)] -> [(Int,Int)]
unmarkMultiples limit n sieve =
  let nonPrimes = [n,n+n..limit] in
  unmarkAll nonPrimes sieve


unmarkAll :: [Int] -> [(Int,Int)] -> [(Int, Int)]
unmarkAll _        []    = []
unmarkAll []       sieve = sieve
unmarkAll (np:nps) ((s,b):ss) 
  | np == s   = unmarkAll nps ss
  | np < s    = unmarkAll nps ((s,b):ss)
  | otherwise = (s,b) : unmarkAll (np:nps) ss


{-|
An implementation of the sieve of Atkin.
-}
atkin :: Int ->  [(Int,Int)]
atkin limit =
  aSieve limit (thirdStep limit . secondStep limit . firstStep limit $ initialAtkinSieve limit) [(5,1),(3,1),(2,1)]


aSieve :: Int -> [(Int,Int)] ->[(Int,Int)] -> [(Int,Int)]
aSieve _     []         primes = primes
aSieve limit ((x,b):xs) primes
  | b == 1 = aSieve limit (unmarkMultiples limit (x^(2::Integer)) xs) ((x,b): primes)
  | otherwise = aSieve limit xs primes