-- |
-- Module:      Math.NumberTheory.Primes.CountingTests
-- Copyright:   (c) 2016 Andrew Lelechenko
-- Licence:     MIT
-- Maintainer:  Andrew Lelechenko <andrew.lelechenko@gmail.com>
-- Stability:   Provisional
--
-- Tests for Math.NumberTheory.Primes.Counting
--

{-# OPTIONS_GHC -fno-warn-type-defaults #-}

module Math.NumberTheory.Primes.CountingTests
  ( testSuite
  ) where

import Test.Tasty
import Test.Tasty.HUnit

import Math.NumberTheory.Primes.Counting
import Math.NumberTheory.Primes.Testing
import Math.NumberTheory.TestUtils

-- | https://en.wikipedia.org/wiki/Prime-counting_function#Table_of_.CF.80.28x.29.2C_x_.2F_ln_x.2C_and_li.28x.29
table :: [(Integer, Integer)]
table =
  [ (10^1,   4)
  , (10^2,   25)
  , (10^3,   168)
  , (10^4,   1229)
  , (10^5,   9592)
  , (10^6,   78498)
  , (10^7,   664579)
  , (10^8,   5761455)
  , (10^9,   50847534)
  , (10^10,  455052511)
  , (10^11,  4118054813)
  , (10^12,  37607912018)
  , (10^13,  346065536839)
  -- , (10^14,  3204941750802)
  -- , (10^15,  29844570422669)
  -- , (10^16,  279238341033925)
  -- , (10^17,  2623557157654233)
  -- , (10^18,  24739954287740860)
  -- , (10^19,  234057667276344607)
  -- , (10^20,  2220819602560918840)
  ]

-- | Check that values of 'primeCount' are non-negative.
primeCountProperty1 :: Integer -> Bool
primeCountProperty1 n = n > primeCountMaxArg
  || n >  0 && primeCount n >= 0
  || n <= 0 && primeCount n == 0

-- | Check that 'primeCount' is monotonically increasing function.
primeCountProperty2 :: Positive Integer -> Positive Integer -> Bool
primeCountProperty2 (Positive n1) (Positive n2)
  =  n1 > primeCountMaxArg
  || n2 > primeCountMaxArg
  || n1 <= n2 && p1 <= p2
  || n1 >  n2 && p1 >= p2
  where
    p1 = primeCount n1
    p2 = primeCount n2

-- | Check that 'primeCount' is strictly increasing iff an argument is prime.
primeCountProperty3 :: Positive Integer -> Bool
primeCountProperty3 (Positive n)
  =  isPrime n && primeCount (n - 1) + 1 == primeCount n
  || not (isPrime n) && primeCount (n - 1) == primeCount n

-- | Check tabulated values.
primeCountSpecialCases :: [Assertion]
primeCountSpecialCases = map a table
  where
  a (n, m) = assertEqual "primeCount" m (primeCount n)


-- | Check that values of 'nthPrime' are positive.
nthPrimeProperty1 :: Positive Integer -> Bool
nthPrimeProperty1 (Positive n) = n > nthPrimeMaxArg
  || nthPrime n > 0

-- | Check that 'nthPrime' is monotonically increasing function.
nthPrimeProperty2 :: Positive Integer -> Positive Integer -> Bool
nthPrimeProperty2 (Positive n1) (Positive n2)
  =  n1 > nthPrimeMaxArg
  || n2 > nthPrimeMaxArg
  || n1 <= n2 && p1 <= p2
  || n1 >  n2 && p1 >= p2
  where
    p1 = nthPrime n1
    p2 = nthPrime n2

-- | Check that values of 'nthPrime' are prime.
nthPrimeProperty3 :: Positive Integer -> Bool
nthPrimeProperty3 (Positive n) = isPrime $ nthPrime n

-- | Check tabulated values.
nthPrimeSpecialCases :: [Assertion]
nthPrimeSpecialCases = map a table
  where
  a (n, m) = assertBool "nthPrime" $ n > nthPrime m


-- | Check that values of 'approxPrimeCount' are non-negative.
approxPrimeCountProperty1 :: Integral a => AnySign a -> Bool
approxPrimeCountProperty1 (AnySign a) = approxPrimeCount a >= 0

-- | Check that 'approxPrimeCount' is consistent with 'approxPrimeCountOverestimateLimit'.
approxPrimeCountProperty2 :: Integral a => Positive a -> Bool
approxPrimeCountProperty2 (Positive a) = a >= approxPrimeCountOverestimateLimit
  || toInteger (approxPrimeCount a) >= primeCount (toInteger a)


-- | Check that values of 'nthPrimeApprox' are positive.
nthPrimeApproxProperty1 :: AnySign Integer -> Bool
nthPrimeApproxProperty1 (AnySign a) = nthPrimeApprox a > 0

-- | Check that 'nthPrimeApprox' is consistent with 'nthPrimeApproxUnderestimateLimit'.
nthPrimeApproxProperty2 :: Positive Integer -> Bool
nthPrimeApproxProperty2 (Positive a) = a >= nthPrimeApproxUnderestimateLimit
  || toInteger (nthPrimeApprox a) <= nthPrime (toInteger a)


testSuite :: TestTree
testSuite = testGroup "Counting"
  [ testGroup "primeCount"
    ( testSmallAndQuick "non-negative"        primeCountProperty1
    : testSmallAndQuick "monotonic"           primeCountProperty2
    : testSmallAndQuick "increases on primes" primeCountProperty3
    : zipWith (\i a -> testCase ("special case " ++ show i) a) [1..] primeCountSpecialCases
    )
  , testGroup "nthPrime"
    ( testSmallAndQuick "positive"  nthPrimeProperty1
    : testSmallAndQuick "monotonic" nthPrimeProperty2
    : testSmallAndQuick "is prime"  nthPrimeProperty3
    : zipWith (\i a -> testCase ("special case " ++ show i) a) [1..] nthPrimeSpecialCases
    )
  , testGroup "approxPrimeCount"
    [ testIntegralProperty "non-negative"             approxPrimeCountProperty1
    , testIntegralProperty "overestimates primeCount" approxPrimeCountProperty2
    ]
  , testGroup "nthPrimeApprox"
    [ testSmallAndQuick "positive"                nthPrimeApproxProperty1
    , testSmallAndQuick "underestimates nthPrime" nthPrimeApproxProperty2
    ]
  ]
