    {-# LANGUAGE FlexibleContexts #-}
    -- | Pearson's chi squared test.
    module Statistics.Test.ChiSquared (
        chi2test
      , chi2testCont
      , module Statistics.Test.Types
      ) where

    import Prelude hiding (sum)

    import Statistics.Distribution
    import Statistics.Distribution.ChiSquared
    import Statistics.Function        (square)
    import Statistics.Sample.Internal (sum)
    import Statistics.Test.Types
    import Statistics.Types
    import qualified Data.Vector as V
    import qualified Data.Vector.Generic as G
    import qualified Data.Vector.Unboxed as U

    -- | Generic form of Pearson chi squared tests for binned data. Data
    --   sample is supplied in form of tuples (observed quantity,
    --   expected number of events). Both must be positive.
    --
    --   This test should be used only if all bins have expected values of
    --   at least 5.
    chi2test :: (G.Vector v (Int,Double), G.Vector v Double)
             => Int                 -- ^ Number of additional degrees of
                                    --   freedom. One degree of freedom
                                    --   is due to the fact that the are
                                    --   N observation in total and
                                    --   accounted for automatically.
             -> v (Int,Double)      -- ^ Observation and expectation.
             -> Maybe (Test ChiSquared)
    chi2test ndf vec
      | ndf <  0  = error $ "Statistics.Test.ChiSquare.chi2test: negative NDF " ++ show ndf
      | n   > 0   = Just Test
                  { testSignificance = mkPValue $ complCumulative d chi2
                  , testStatistics   = chi2
                  , testDistribution = chiSquared ndf
                  }
      | otherwise = Nothing
      where
        n     = G.length vec - ndf - 1
        chi2  = sum $ G.map (\(o,e) -> square (fromIntegral o - e) / e) vec
        d     = chiSquared n
    {-# INLINABLE  chi2test #-}
    {-# SPECIALIZE
        chi2test :: Int -> U.Vector (Int,Double) -> Maybe (Test ChiSquared) #-}
    {-# SPECIALIZE
        chi2test :: Int -> V.Vector (Int,Double) -> Maybe (Test ChiSquared) #-}

    -- | Chi squared test for data with normal errors. Data is supplied in
    --   form of pair (observation with error, and expectation).
    chi2testCont
      :: (G.Vector v (Estimate NormalErr Double, Double), G.Vector v Double)
      => Int                                   -- ^ Number of additional
                                               --   degrees of freedom.
      -> v (Estimate NormalErr Double, Double) -- ^ Observation and expectation.
      -> Maybe (Test ChiSquared)
    chi2testCont ndf vec
      | ndf < 0   = error $ "Statistics.Test.ChiSquare.chi2testCont: negative NDF " ++ show ndf
      | n   > 0   = Just Test
                  { testSignificance = mkPValue $ complCumulative d chi2
                  , testStatistics   = chi2
                  , testDistribution = chiSquared ndf
                  }
      | otherwise = Nothing
      where
        n     = G.length vec - ndf - 1
        chi2  = sum $ G.map (\(Estimate o (NormalErr s),e) -> square (o - e) / s) vec
        d     = chiSquared n