{-# LANGUAGE
    MultiParamTypeClasses,
    FlexibleInstances, FlexibleContexts,
    UndecidableInstances
  #-}

module Data.Random.Distribution.Triangular where

import Data.Random.RVar
import Data.Random.Distribution
import Data.Random.Distribution.Uniform

-- |A description of a triangular distribution - a distribution whose PDF
-- is a triangle ramping up from a lower bound to a specified midpoint
-- and back down to the upper bound.  This is a very simple distribution
-- that does not generally occur naturally but is used sometimes as an
-- estimate of a true distribution when only the range of the values and
-- an approximate mode of the true distribution are known.
data Triangular a = Triangular {
    -- |The lower bound of the triangle in the PDF (the smallest number the distribution can generate)
    triLower  :: a,
    -- |The midpoint of the triangle (also the mode of the distribution)
    triMid    :: a,
    -- |The upper bound of the triangle (and the largest number the distribution can generate)
    triUpper  :: a}
    deriving (Eq, Show)

-- |Compute a triangular distribution for a 'Floating' type.
floatingTriangular :: (Floating a, Ord a, Distribution StdUniform a) => a -> a -> a -> RVarT m a
floatingTriangular a b c
    | a > b     = floatingTriangular b a c
    | b > c     = floatingTriangular a c b
    | otherwise = do
        let p = (c-b)/(c-a)
        u <- stdUniformT
        let d   | u >= p    = a
                | otherwise = c
            x   | u >= p    = (u - p) / (1 - p)
                | otherwise = u / p
-- may prefer this: reusing u costs resolution, especially if p or 1-p is small and c-a is large.
--        x <- stdUniform
        return (b - ((1 - sqrt x) * (b-d)))

-- |@triangularCDF a b c@ is the CDF of @realFloatTriangular a b c@.
triangularCDF :: RealFrac a => a -> a -> a -> a -> Double
triangularCDF a b c x
    | x < a
    = 0
    | x <= b
    = realToFrac ((x - a)^(2 :: Int) / ((c - a) * (b - a)))
    | x <= c
    = realToFrac (1 - (c - x)^(2 :: Int) / ((c - a) * (c - b)))
    | otherwise
    = 1

instance (RealFloat a, Ord a, Distribution StdUniform a) => Distribution Triangular a where
    rvarT (Triangular a b c) = floatingTriangular a b c
instance (RealFrac a, Distribution Triangular a) => CDF Triangular a where
    cdf  (Triangular a b c) = triangularCDF a b c