{-|
  Defines the core types and aliases for the LineFilterApp tagging API.

  Projects using `asclepias` and its LineFilterApp should defined a
  `TaggerConfig t c m`, which provides a filepath to a dhall configuration file
  that can be marshaled to @M.Map Text c@, e.g. of the form `toMap { key1 =
  <val1>, key2 = <val2> }`, where the value types can be marshaled to type @c@
  via @FromDhall@.

  `TaggerConfig` also contains a list of `Tagger t c m`, functions that combine
  information from @M.Map Text c@ with values of type @m@ to produce a tag of
  type @t@. See @TaggerConfig@ for details.
      -}

{-# LANGUAGE GADTs               #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Hasklepias.LineFilterApp.ProcessLines.Taggers where

import qualified Data.Map.Strict as M
import           Data.Text       (Text)
import           Dhall
import           EventDataTheory (EventLine)



{- EXPORTED TYPES and CONFIG -}

-- | Entrypoint for specifying taggers in the `asclepias` line filter app.
-- Configuration required to read in a `dhall` file, @tagMapFile@, as a @M.Map
-- Text c@ for use in each of the @Tagger t c m@ functions. @t@ is the tag type
-- returned, and @m@ is the type with which values in the @M.Map Text c@
-- argument of the tagger will be compared to produce the tag. When
-- @TaggerConfig t c m@ is used in the `asclepias` line fitltering app, @t, m@
-- will correspond to @Event t m a@. 
--
-- @c@ will almost invariably be a @Codelist@ type, as exported by the
-- non-public `fact-models` package, and similarly the @m@ will be a model type
-- from that package.  Nonetheless they are left generic because the @Codelist@
-- and model types are not and will not be imported into `hasklepias-main`.
-- Note that @c@ is required to be @FromDhall@. The `dhall` shape from which
-- @M.Map Text c@ is marshaled is of the form `toMap { key1 = val1, key2
-- = val2, ... }`, where `val1, val2` are values that marshal to the Haskell
-- type `c`. 
--
-- The path `tagMapFile` should be relative to where the line filter app will
-- execute.
--
-- This example (not run in doctests) assumes you have a P0000Model in the
-- `fact-models` package that has a simple `Diagnosis` variant, which holds a
-- [`Code`](https://docs.novisci.com/event-data/3.0/models/facts.html#_code)
-- fact in its `code` field.
--
--  import Models.P0000Model
--  import qualified Data.Map.Strict as M
--  isCancer :: M.Map Text Codelist -> P0000Model -> Maybe Text
--  isCancer cls (Diagnosis cd) = makeTag =<< lookInCl cd.code . (\cl -> cl.sets) <$> SM.lookup "cancer" cls
--   where lookInCl cd' csets' = any (\cs -> cd'.codebook == cs.codebook  && (cd'.code `elem` Prelude.map (\x -> x.code) cs.codes)) csets'
--         makeTag b = if b then Just "is_cancer" else Nothing
--
--  tc :: TaggerConfig Text Codelist P0000Model
--  tc = MkTaggerConfig [isCancer] "plans/codelists.dhall"

data TaggerConfig t c m where 
  MkTaggerConfig :: (FromDhall c) => { forall c t m. TaggerConfig t c m -> [Tagger t c m]
taggers :: [Tagger t c m], forall c t m. TaggerConfig t c m -> FilePath
tagMapFile :: FilePath } -> TaggerConfig t c m

-- TODO delete. exists only as a placeholder for runFilterEventLineAppSimple
-- until implementation MR is complete.
-- https://gitlab.com/TargetRWE/epistats/nsstat/asclepias/-/issues/331
emptyTaggerConfig :: TaggerConfig t Text m
emptyTaggerConfig :: forall t m. TaggerConfig t Text m
emptyTaggerConfig = forall c t m.
FromDhall c =>
[Tagger t c m] -> FilePath -> TaggerConfig t c m
MkTaggerConfig [] FilePath
""

-- | Type alias for a tagging function, as used by the line filter application.
-- @c@ is the value type for a @M.Map Text c@, @m@ is a type to which those
-- values can be compared to (@Maybe@) produce some tag of type @t@. @t, m@
-- should correspond to @EventLine t m a@. A return of @Nothing@ will indicate
-- that no tag should be created. The return type is useful to accommodate
-- possible failed lookups in the @M.Map@.
type Tagger t c m = M.Map Text c -> m -> Maybe t

-- | Alias for a function that ingests a @Tagger c m@ and modifies an
-- @EventLine t m a@.
type EventLineTagger t c m a = Tagger t c m -> EventLine t m a -> EventLine t m a

  {- DHALL UTILITIES -}

-- TODO might want to change depending on
-- https://gitlab.com/TargetRWE/epistats/nsstat/asclepias/-/issues/331
-- That MR also should provide a more detailed implementation here, one like
-- dhall's 'inputWithSettings', which instead of calling throwIO in case of a
-- decoding error it provides a more helpful error message in this context,
-- about what shape the input file should be. We could alternatively use some
-- ugly try/catch statements in the runner itself, provide info to the logger,
-- etc.
--
-- In addition, that MR should consider whether this function should have signature
-- (FromDhall c) => FilePath -> M.Map Text c. Now that this function is
-- exported, it's perhaps inconvenient to have TaggerConfig as input.
--
-- | Construct the @M.Map Text c@ by parsing the @tagMapFile@.
inputTagMap :: TaggerConfig t c m -> IO (M.Map Text c)
inputTagMap :: forall t c m. TaggerConfig t c m -> IO (Map Text c)
inputTagMap MkTaggerConfig { tagMapFile :: forall c t m. TaggerConfig t c m -> FilePath
tagMapFile  = FilePath
tm } = forall a. Decoder a -> FilePath -> IO a
inputFile forall a. FromDhall a => Decoder a
auto FilePath
tm