{-|
Module      : Define and evaluate Features 
Description : Defines the Feature type and its component types, constructors, 
              and class instances
Copyright   : (c) NoviSci, Inc 2020
License     : BSD3
Maintainer  : bsaul@novisci.com
-}

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE Safe #-}
{-# LANGUAGE OverloadedStrings #-}

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

module FeatureCompose(
    -- * Types
      FeatureSpec(..)
    , Feature(..)
    , FeatureData(..)
    , MissingReason(..)
    -- , FeatureDefinition(..)
    , FeatureDefinition(..)
    , makeFeatureSpec
    , featureDataR
    , featureDataL
    , define
    , defineM
    , define2
    , defineM2
    , eval
) where

import safe GHC.Read                   ( Read )
import safe GHC.Show                   ( Show(show) )
import safe GHC.Generics               ( Generic )
import safe Control.Applicative        ( Applicative(..) )
import safe Control.Monad              ( Functor(..), Monad(..), join, liftM, liftM2)
import safe Data.Either                ( Either(..) )
import safe Data.Eq                    ( Eq )
import safe Data.Function              ( ($), (.) )
import safe Data.List                  ( (++), zipWith )
import safe Data.Maybe                 ( Maybe(..), maybe )
import safe Data.Ord                   ( Ord )
import safe Data.Traversable           ( Traversable(..) )
import safe Data.Text                  ( Text )
import safe Data.Tuple                 ( uncurry, curry )

-- import safe Test.QuickCheck       ( Property )

{- | A 'FeatureSpec' contains all the information needed to derive a 'Feature':
      * its name
      * its attributes
      * the function needed to derive a feature (i.e. the 'FeatureDefinition')
-}
data (Show b) => FeatureSpec b di d0 = MkFeatureSpec {
        FeatureSpec b di d0 -> Text
getSpecName :: Text
      , FeatureSpec b di d0 -> b
getSpecAttr :: b
      , FeatureSpec b di d0 -> FeatureDefinition di d0
getDefn :: FeatureDefinition di d0
      -- To add in future: an optional list of properties to check
      -- , getProp :: Maybe [Feature d -> Events a -> Property] 
    }

-- | TODO
makeFeatureSpec :: Show b =>
     Text
  -> b
  -> FeatureDefinition di d0
  -> FeatureSpec b di d0
makeFeatureSpec :: Text -> b -> FeatureDefinition di d0 -> FeatureSpec b di d0
makeFeatureSpec = Text -> b -> FeatureDefinition di d0 -> FeatureSpec b di d0
forall b di d0.
Text -> b -> FeatureDefinition di d0 -> FeatureSpec b di d0
MkFeatureSpec

{- | A 'Feature' contains the following:
      * a name
      * its attributes
      * 'FeatureData'
-}
data (Show b) => Feature b d = MkFeature {
        Feature b d -> Text
getName :: Text
      , Feature b d -> b
getAttr :: b
      , Feature b d -> FeatureData d
getData :: FeatureData d
      } deriving (Feature b d -> Feature b d -> Bool
(Feature b d -> Feature b d -> Bool)
-> (Feature b d -> Feature b d -> Bool) -> Eq (Feature b d)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall b d.
(Show b, Eq b, Eq d) =>
Feature b d -> Feature b d -> Bool
/= :: Feature b d -> Feature b d -> Bool
$c/= :: forall b d.
(Show b, Eq b, Eq d) =>
Feature b d -> Feature b d -> Bool
== :: Feature b d -> Feature b d -> Bool
$c== :: forall b d.
(Show b, Eq b, Eq d) =>
Feature b d -> Feature b d -> Bool
Eq)

instance (Show b, Show d) => Show (Feature b d) where
    show :: Feature b d -> String
show Feature b d
x = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
forall a. Show a => a -> String
show (Feature b d -> Text
forall b d. Show b => Feature b d -> Text
getName Feature b d
x) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
": (" String -> ShowS
forall a. [a] -> [a] -> [a]
++ b -> String
forall a. Show a => a -> String
show (Feature b d -> b
forall b d. Show b => Feature b d -> b
getAttr Feature b d
x) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
") "  String -> ShowS
forall a. [a] -> [a] -> [a]
++ FeatureData d -> String
forall a. Show a => a -> String
show (Feature b d -> FeatureData d
forall b d. Show b => Feature b d -> FeatureData d
getData Feature b d
x) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" )\n"

instance (Show b) => Functor (Feature b) where
  fmap :: (a -> b) -> Feature b a -> Feature b b
fmap a -> b
f (MkFeature Text
n b
a FeatureData a
d) = Text -> b -> FeatureData b -> Feature b b
forall b d. Text -> b -> FeatureData d -> Feature b d
MkFeature Text
n b
a ((a -> b) -> FeatureData a -> FeatureData b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> b
f FeatureData a
d)

{- | 'FeatureData' is @'Either' 'MissingReason' d@, where @d@ can be any type 
     of data derivable from 'Hasklepias.Event.Events'.
-}
newtype FeatureData d = MkFeatureData { FeatureData d -> Either MissingReason [d]
getFeatureData :: Either MissingReason [d] }
  deriving ((forall x. FeatureData d -> Rep (FeatureData d) x)
-> (forall x. Rep (FeatureData d) x -> FeatureData d)
-> Generic (FeatureData d)
forall x. Rep (FeatureData d) x -> FeatureData d
forall x. FeatureData d -> Rep (FeatureData d) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall d x. Rep (FeatureData d) x -> FeatureData d
forall d x. FeatureData d -> Rep (FeatureData d) x
$cto :: forall d x. Rep (FeatureData d) x -> FeatureData d
$cfrom :: forall d x. FeatureData d -> Rep (FeatureData d) x
Generic, Int -> FeatureData d -> ShowS
[FeatureData d] -> ShowS
FeatureData d -> String
(Int -> FeatureData d -> ShowS)
-> (FeatureData d -> String)
-> ([FeatureData d] -> ShowS)
-> Show (FeatureData d)
forall d. Show d => Int -> FeatureData d -> ShowS
forall d. Show d => [FeatureData d] -> ShowS
forall d. Show d => FeatureData d -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [FeatureData d] -> ShowS
$cshowList :: forall d. Show d => [FeatureData d] -> ShowS
show :: FeatureData d -> String
$cshow :: forall d. Show d => FeatureData d -> String
showsPrec :: Int -> FeatureData d -> ShowS
$cshowsPrec :: forall d. Show d => Int -> FeatureData d -> ShowS
Show, FeatureData d -> FeatureData d -> Bool
(FeatureData d -> FeatureData d -> Bool)
-> (FeatureData d -> FeatureData d -> Bool) -> Eq (FeatureData d)
forall d. Eq d => FeatureData d -> FeatureData d -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: FeatureData d -> FeatureData d -> Bool
$c/= :: forall d. Eq d => FeatureData d -> FeatureData d -> Bool
== :: FeatureData d -> FeatureData d -> Bool
$c== :: forall d. Eq d => FeatureData d -> FeatureData d -> Bool
Eq)

instance Functor FeatureData where
  fmap :: (a -> b) -> FeatureData a -> FeatureData b
fmap a -> b
f (MkFeatureData Either MissingReason [a]
x) = Either MissingReason [b] -> FeatureData b
forall d. Either MissingReason [d] -> FeatureData d
MkFeatureData (([a] -> [b])
-> Either MissingReason [a] -> Either MissingReason [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> b
f) Either MissingReason [a]
x)

instance Applicative FeatureData where
  pure :: a -> FeatureData a
pure = [a] -> FeatureData a
forall d. [d] -> FeatureData d
featureDataR ([a] -> FeatureData a) -> (a -> [a]) -> a -> FeatureData a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure
  liftA2 :: (a -> b -> c) -> FeatureData a -> FeatureData b -> FeatureData c
liftA2 a -> b -> c
f (MkFeatureData Either MissingReason [a]
x) (MkFeatureData Either MissingReason [b]
y) =
    Either MissingReason [c] -> FeatureData c
forall d. Either MissingReason [d] -> FeatureData d
MkFeatureData ( ([a] -> [b] -> [c])
-> Either MissingReason [a]
-> Either MissingReason [b]
-> Either MissingReason [c]
forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2 ((a -> b -> c) -> [a] -> [b] -> [c]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith a -> b -> c
f) Either MissingReason [a]
x Either MissingReason [b]
y )

instance Monad FeatureData where
  (MkFeatureData Either MissingReason [a]
x) >>= :: FeatureData a -> (a -> FeatureData b) -> FeatureData b
>>= a -> FeatureData b
f = -- TODO: surely there's a cleaner way
    case ([a] -> [FeatureData b])
-> Either MissingReason [a] -> Either MissingReason [FeatureData b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((a -> FeatureData b) -> [a] -> [FeatureData b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> FeatureData b
f) Either MissingReason [a]
x of
         Left MissingReason
l  -> MissingReason -> FeatureData b
forall d. MissingReason -> FeatureData d
featureDataL MissingReason
l
         Right [FeatureData b]
v -> case FeatureData [b] -> Either MissingReason [[b]]
forall d. FeatureData d -> Either MissingReason [d]
getFeatureData ([FeatureData b] -> FeatureData [b]
forall (t :: * -> *) (f :: * -> *) a.
(Traversable t, Applicative f) =>
t (f a) -> f (t a)
sequenceA [FeatureData b]
v) of
                      Left MissingReason
l  -> MissingReason -> FeatureData b
forall d. MissingReason -> FeatureData d
featureDataL MissingReason
l
                      Right [[b]]
v -> Either MissingReason [b] -> FeatureData b
forall d. Either MissingReason [d] -> FeatureData d
MkFeatureData (Either MissingReason [b] -> FeatureData b)
-> Either MissingReason [b] -> FeatureData b
forall a b. (a -> b) -> a -> b
$ [b] -> Either MissingReason [b]
forall a b. b -> Either a b
Right ([[b]] -> [b]
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join [[b]]
v)

-- | Create the 'Right' side of 'FeatureData'.
featureDataR :: [d] -> FeatureData d
featureDataR :: [d] -> FeatureData d
featureDataR = Either MissingReason [d] -> FeatureData d
forall d. Either MissingReason [d] -> FeatureData d
MkFeatureData (Either MissingReason [d] -> FeatureData d)
-> ([d] -> Either MissingReason [d]) -> [d] -> FeatureData d
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [d] -> Either MissingReason [d]
forall a b. b -> Either a b
Right

-- | Create the 'Left' side of 'FeatureData'.
featureDataL :: MissingReason -> FeatureData d
featureDataL :: MissingReason -> FeatureData d
featureDataL = Either MissingReason [d] -> FeatureData d
forall d. Either MissingReason [d] -> FeatureData d
MkFeatureData (Either MissingReason [d] -> FeatureData d)
-> (MissingReason -> Either MissingReason [d])
-> MissingReason
-> FeatureData d
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MissingReason -> Either MissingReason [d]
forall a b. a -> Either a b
Left

-- | 'FeatureData' may be missing for any number of reasons. 
data MissingReason =
    InsufficientData
  | Excluded
  | Other Text
  | Unknown
  deriving (MissingReason -> MissingReason -> Bool
(MissingReason -> MissingReason -> Bool)
-> (MissingReason -> MissingReason -> Bool) -> Eq MissingReason
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: MissingReason -> MissingReason -> Bool
$c/= :: MissingReason -> MissingReason -> Bool
== :: MissingReason -> MissingReason -> Bool
$c== :: MissingReason -> MissingReason -> Bool
Eq, ReadPrec [MissingReason]
ReadPrec MissingReason
Int -> ReadS MissingReason
ReadS [MissingReason]
(Int -> ReadS MissingReason)
-> ReadS [MissingReason]
-> ReadPrec MissingReason
-> ReadPrec [MissingReason]
-> Read MissingReason
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [MissingReason]
$creadListPrec :: ReadPrec [MissingReason]
readPrec :: ReadPrec MissingReason
$creadPrec :: ReadPrec MissingReason
readList :: ReadS [MissingReason]
$creadList :: ReadS [MissingReason]
readsPrec :: Int -> ReadS MissingReason
$creadsPrec :: Int -> ReadS MissingReason
Read, Int -> MissingReason -> ShowS
[MissingReason] -> ShowS
MissingReason -> String
(Int -> MissingReason -> ShowS)
-> (MissingReason -> String)
-> ([MissingReason] -> ShowS)
-> Show MissingReason
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [MissingReason] -> ShowS
$cshowList :: [MissingReason] -> ShowS
show :: MissingReason -> String
$cshow :: MissingReason -> String
showsPrec :: Int -> MissingReason -> ShowS
$cshowsPrec :: Int -> MissingReason -> ShowS
Show, (forall x. MissingReason -> Rep MissingReason x)
-> (forall x. Rep MissingReason x -> MissingReason)
-> Generic MissingReason
forall x. Rep MissingReason x -> MissingReason
forall x. MissingReason -> Rep MissingReason x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep MissingReason x -> MissingReason
$cfrom :: forall x. MissingReason -> Rep MissingReason x
Generic)

-- TODO: the code below should be generalized so that there is a single define/eval
--       interface and the recursive structure is realizing and not hacked together.
newtype FeatureDefinition di d0 = MkFeatureDefinition (di -> FeatureData d0)

class Eval di d0 where
  eval :: FeatureDefinition di d0 -> di -> FeatureData d0
  eval (MkFeatureDefinition di -> FeatureData d0
f) = di -> FeatureData d0
f

instance Eval (FeatureData d1) d0 where
instance Eval (FeatureData d2, FeatureData d1) d0 where

defineM :: (d1 -> FeatureData d0) -> FeatureDefinition (FeatureData d1) d0
defineM :: (d1 -> FeatureData d0) -> FeatureDefinition (FeatureData d1) d0
defineM d1 -> FeatureData d0
f = (FeatureData d1 -> FeatureData d0)
-> FeatureDefinition (FeatureData d1) d0
forall di d0. (di -> FeatureData d0) -> FeatureDefinition di d0
MkFeatureDefinition (FeatureData d1 -> (d1 -> FeatureData d0) -> FeatureData d0
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= d1 -> FeatureData d0
f)

defineM2 :: (d2 -> d1 -> FeatureData d0) -> FeatureDefinition (FeatureData d2, FeatureData d1) d0
defineM2 :: (d2 -> d1 -> FeatureData d0)
-> FeatureDefinition (FeatureData d2, FeatureData d1) d0
defineM2 d2 -> d1 -> FeatureData d0
f = ((FeatureData d2, FeatureData d1) -> FeatureData d0)
-> FeatureDefinition (FeatureData d2, FeatureData d1) d0
forall di d0. (di -> FeatureData d0) -> FeatureDefinition di d0
MkFeatureDefinition (\ (FeatureData d2
x, FeatureData d1
y) -> FeatureData (FeatureData d0) -> FeatureData d0
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join ((d2 -> d1 -> FeatureData d0)
-> FeatureData d2 -> FeatureData d1 -> FeatureData (FeatureData d0)
forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2 d2 -> d1 -> FeatureData d0
f FeatureData d2
x FeatureData d1
y))

define :: (d1 -> d0) -> FeatureDefinition (FeatureData d1) d0
define :: (d1 -> d0) -> FeatureDefinition (FeatureData d1) d0
define d1 -> d0
f = (FeatureData d1 -> FeatureData d0)
-> FeatureDefinition (FeatureData d1) d0
forall di d0. (di -> FeatureData d0) -> FeatureDefinition di d0
MkFeatureDefinition ((d1 -> d0) -> FeatureData d1 -> FeatureData d0
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap d1 -> d0
f)

define2 :: (d2 -> d1 -> d0) -> FeatureDefinition (FeatureData d2, FeatureData d1) d0
define2 :: (d2 -> d1 -> d0)
-> FeatureDefinition (FeatureData d2, FeatureData d1) d0
define2 d2 -> d1 -> d0
f = ((FeatureData d2, FeatureData d1) -> FeatureData d0)
-> FeatureDefinition (FeatureData d2, FeatureData d1) d0
forall di d0. (di -> FeatureData d0) -> FeatureDefinition di d0
MkFeatureDefinition (((FeatureData d2, FeatureData d1) -> FeatureData d0)
 -> FeatureDefinition (FeatureData d2, FeatureData d1) d0)
-> ((FeatureData d2, FeatureData d1) -> FeatureData d0)
-> FeatureDefinition (FeatureData d2, FeatureData d1) d0
forall a b. (a -> b) -> a -> b
$ (FeatureData d2 -> FeatureData d1 -> FeatureData d0)
-> (FeatureData d2, FeatureData d1) -> FeatureData d0
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ((d2 -> d1 -> d0)
-> FeatureData d2 -> FeatureData d1 -> FeatureData d0
forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2 d2 -> d1 -> d0
f)