module Huzzy.Base.Sets
( MF(..)
, Fuzzy(..)
, FSet(..)
, tCo
, tGodel
, tProd
, tLuk
, tDras
, tNilMin
, tHam
, discrete
, singleton
, tri
, trap
, bell
, gaus
, up
, down
, sig
) where

-- | Type representing type-1 membership functions.
newtype MF a = MF (a -> Double)
-- | Used internally to represent type-1 membership functions.
-- Not exported for safety reasons.
-- Library users, use the newtype.
type MF' a = a -> Double

-- | FuzOp is used to denote functions expecting operators on fuzzy sets.
type FuzOp a = a -> a -> a

infixr 3 ?&&
infixr 2 ?||
-- | Standard operations on fuzzy sets.
-- Instantiated for each kind of fuzzy set.
-- If you want to overload with a t-norm, instantiate against a newtype or instantiated set.
class Fuzzy a where
    -- | Union over fuzzy values.
    (?&&) :: a -> a -> a
    -- | Intersection over fuzzy values.
    (?||) :: a -> a -> a
    -- | Fuzzy complement.
    fnot  :: a -> a

-- | Standard definitions for operations as defined by Zadeh (1965)
instance Fuzzy Double where
    -- | Equivalent to use of the Godel t-conorm,
    -- > (?&&) = tCo tGodel
    (?&&)  = max
    -- | Equivalent to use of the Godel t-norm,
    -- > (?||) = tGodel
    (?||)  = min
    fnot x = 1 - x

-- | Fuzzy operators for membership functions.
instance (Fuzzy b) => Fuzzy (a -> b) where
    f ?&& g      = \x -> f x ?&& g x
    f ?|| g      = \x -> f x ?|| g x
    fnot f       = fnot (\x -> f x)

instance Fuzzy (MF a) where
    (MF f) ?&& (MF g) = MF (f ?&& g)
    (MF f) ?|| (MF g) = MF (f ?|| g)
    fnot (MF f)       = MF (fnot f)

-- | Instance for tuple needed for interval type-2 fuzzy sets.
instance (Fuzzy a, Fuzzy b) => Fuzzy (a, b) where
  (a, b) ?&& (c, d) = (a ?&& c, b ?&& d)
  (a ,b) ?|| (c, d) = (a ?|| c, b ?|| d)
  fnot (a, b) = (fnot a, fnot b)

instance Num (MF a) where
  (MF f) + (MF g) = MF (\x -> f x + g x)
  (MF f) * (MF g) = MF (\x -> f x * g x)
  (MF f) - (MF g) = MF (\x -> f x - g x)
  abs (MF f)      = MF (\x -> abs (f x))
  signum (MF f)   = MF (\x -> signum (f x))
  fromInteger n   = MF (\x -> fromInteger n)

-- | Specifically for fuzzy sets, as opposed to fuzzy values.
-- Support is all elements of domain for which membership is non-zero.
-- Hedge is a modifier of fuzzy sets.
-- `is` is for application of a value to a fuzzy set.
class FSet a where
  -- | A single value of the domain.
  type Value a
  -- | A list of values from the domain for which membership is non-zero.
  type Support a
  -- | Degree of membership from applying a value to membership function.
  type Returned a
  support :: a -> Support a
  hedge   :: Double -> a -> a
  is      :: Value a -> a -> Returned a

{- Old functional dependency definition, remains for
report purposes.
class FSet a b c d | a -> b, a -> c, a -> d where
  support :: a -> [c]
  hedge   :: Double -> a -> a
  is      :: b -> a -> d
-}

-- | Produces the dual t-conorm from a t-norm
tCo :: (Num a, Fuzzy a) => FuzOp a -> a -> a -> a
tCo tNo a b = (-) 1 $ tNo (1 - a) (1 - b)

-- | Standard t-norm used for intersection.
tGodel :: (Fuzzy a, Ord a) => FuzOp a
tGodel = min

tProd :: (Fuzzy a, Num a) => FuzOp a
tProd = (*)

tLuk :: (Fuzzy a, Num a, Ord a) => FuzOp a
tLuk a b = max 0 (a + b - 1)

tDras :: (Fuzzy a, Eq a, Num a) => FuzOp a
tDras a b | a == 1 = b
          | b == 1 = a
          | otherwise = 0

tNilMin :: (Fuzzy a, Eq a, Num a, Ord a) => FuzOp a
tNilMin a b | a + b > 1 = min a b
            | otherwise = 0

tHam :: (Fuzzy a, Eq a, Num a, Fractional a) => FuzOp a
tHam a b | a == b && b == 0 = 0
         | otherwise        = a*b/a+b-a*b

support' :: [a] -> MF' a -> [a]
support' xs f = filter (\x -> f x > 0) xs

hedge' :: Double -> MF' a -> MF' a
hedge' p f x | f x == 0 = 0
            | otherwise = f x ** p

very', extremely', somewhat', slightly' :: MF' a -> MF' a
very'      = hedge' 2
extremely' = hedge' 3
somewhat'  = hedge' 0.5
slightly'  = hedge' (1/3)

-- | Ensure that input list is correctly ordered for desired performance.
-- I.e. if desired property is that for a u with 2 values of z, max is chosen, order descending on right value of tuple.
discrete :: Eq a => [(a, Double)] -> MF a
discrete vs = MF (\x -> discrete' vs x)

discrete' :: Eq a => [(a, Double)] -> MF' a
discrete' vs x = case lookup x vs of
                  Just t -> t
                  Nothing -> 0

-- | Used for type-2 defuzzification.
singleton :: Double -> MF a
singleton d = MF (\x -> singleton' d x)

singleton' :: Double -> MF' a
singleton' d x = d

up :: Double -> Double -> MF Double
up a b = MF (\x -> up' a b x)

up' :: Double -> Double -> MF' Double
up' a b x
  | x < a = 0
  | x < b = (x - a) / (b - a)
  | otherwise = 1

down :: Double -> Double -> MF Double
down a b = MF (\x -> down' a b x)

down' :: Double -> Double -> MF' Double
down' a b x
    | x < a = 1.0
    | x < b = (x-b)/(a-b)
    | otherwise = 0.0

tri :: Double -> Double -> Double -> MF Double
tri a b c = MF (\x -> tri' a b c x)

tri' :: Double -> Double -> Double -> MF' Double
tri' a b c x | x <= a = 0
             | a <= x && x <= b = (x-a)/(b-a)
             | b <= x && x <= c = (c-x)/(c-b)
             | c <= x = 0

trap :: Double -> Double -> Double -> Double -> MF Double
trap a b c d = MF (\x -> trap' a b c d x)

trap' :: Double -> Double -> Double -> Double -> MF' Double
trap' a b c d x | x <= a || d <= x = 0
                | a <= x && x <= b = (x-a)/(b-a)
                | b <= x && x <= c = 1
                | c <= x && x <= d = (d-x)/(d-c)
                | otherwise = 0

gaus :: Double -> Double -> MF Double
gaus sig c = MF (\x -> gaus' sig c x)

gaus' :: Double -> Double -> MF' Double
gaus' sig c x = let e = exp 1 in e**(top/bottom)
                where
                  top = negate $ (x-c)**2
                  bottom = 2*(sig**2)

bell :: Double -> Double -> Double -> MF Double
bell a b c = MF (\x -> bell' a b c x)

bell' :: Double -> Double -> Double -> MF' Double
bell' a b c x = 1/(1+abs (((x-c)/a)**(2*b)))

sig :: Double -> Double -> MF Double
sig a c = MF (\x -> sig' a c x)

sig' :: Double -> Double -> MF' Double
sig' a c x = 1/(1+exp(-a*(x-c)))