module Huzzy.TypeOne.Sets
( T1Set(mf, dom)
, Fuzzy(..)
, FSet(..)
, contT1
, discT1
, unsafeMkT1
, alpha
, findCuts
)where

import Data.List(sortBy, nub, elemIndex)
import Data.Maybe(fromJust)
import Huzzy.Base.Sets


-- | Type-1 fuzzy sets, with associated membership function and domain. Use smart constructors to create.
data T1Set a = T1S { mf  :: MF a
                   , dom :: [a]
                   }

-- | Fuzzy operators are supported on T1Sets.
-- Applies operator to membership functions inside T1Set type.
instance Fuzzy (T1Set a) where
    a ?&& b = a { mf = (mf a) ?&& (mf b)}
    a ?|| b = a { mf = (mf a) ?|| (mf b)}
    fnot  a  = a { mf = fnot (mf a)}

-- | Type-1 fuzzy sets are the most basic fuzzy set.
instance FSet (T1Set a) where
    -- | Single element of the domain.
    type Value (T1Set a)        = a
    -- | List of elements from the domain with non-zero membership.
    type Support (T1Set a)      = [a]
    -- | Type-1 membership functions return a double, hopefully in the range 0 to 1.
    type Returned (T1Set a)     = Double
    support s = filter (\x -> (x `is` s)  > 0) d
                where
                    d = dom s

    hedge p s = s {mf = MF (\x -> mf' x)}
                where
                    (MF f) = mf s
                    mf' x | f x == 0 = 0
                          | otherwise = f x ** p
    x `is` s  = f x
                where
                    (MF f) = mf s
{-
instance FSet (T1Set a) a a Double where
    support s = filter (\x -> (x `is` s)  > 0) d
                where
                    d = dom s

    hedge p s = s {mf = MF (\x -> mf' x)}
                where
                    (MF f) = mf s
                    mf' x | f x == 0 = 0
                          | otherwise = f x ** p
    x `is` s  = f x
                where
                    (MF f) = mf s
-}

instance Num (T1Set a) where
  a + b = a { mf = (mf a) + (mf b)}
  a * b = a { mf = (mf a) * (mf b)}
  a - b = a { mf = (mf a) * (mf b)}
  abs a = a { mf = abs (mf a)}
  signum  a      = a {mf = signum (mf a)}
  fromInteger n  = T1S { mf  = MF $ \x -> fromInteger n
                       , dom = [] }


-- | Smart constructor for continuous membership functions. Warning, fine resolutions will make this a very slow construction.
contT1 :: (Num a, Enum a) => a -> a -> a -> MF a -> T1Set a
contT1 minB maxB res (MF mf) = case check of
                                True -> error "Truth values must be in the range [0..1]"
                                False -> T1S { mf = MF mf
                                             , dom = domain
                                             }
                                where
                                    domain = [minB, minB+res .. maxB]
                                    check  = any (\x -> x > 1 || x < 0) (map mf domain)

-- | Smart constructor for discrete continuous membership functions. Avoid large domains.
discT1 :: [a] -> MF a -> T1Set a
discT1 dom (MF mf) = case check of
                        True -> error "Truth values must be in the range [0..1]"
                        False -> T1S { mf = MF mf
                                     , dom = dom
                                     }
                        where
                            check = any (\x -> x > 1 || x < 0) (map mf dom)

-- | Only use this if you're sure your membership functions are safe, or your domain is huge.
unsafeMkT1 :: [a] -> MF a -> T1Set a
unsafeMkT1 dom mf = T1S { mf = mf
                        , dom = dom
                        }

-- | Cuts a type-1 fuzzy set at a given degree of membership.
alpha :: Double -> T1Set a -> [a]
alpha d s = filter (\x -> f x >= d) (dom s)
             where
                (MF f) = mf s

-- | Performs a cut and then finds the x values on the curve at point of cut.
findCuts :: Ord a =>  T1Set a -> Double -> (a, a)
findCuts s d = (l, r)
                where
                    as = alpha d s
                    l  = maximum as
                    li = fromJust $ elemIndex l as
                    r  = maximum (snd $ splitAt li as)