{-|
Module: Model.Types.Lms
Description: Types for Learning Management System Interface operated by Know how! AG

-}
module Model.Types.Lms
  ( module Model.Types.Lms 
  ) where

import Import.NoModel
import Database.Persist.Sql 
import qualified Database.Esqueleto.Legacy as E
import qualified Data.Csv as Csv
import qualified Data.Time.Format as Time
import Data.Time.Format.ISO8601 (iso8601ParseM)
import Utils.Lens.TH

newtype LmsIdent = LmsIdent { getLmsIdent :: Text }
  deriving (Eq, Ord, Read, Show, Generic, Typeable)
  deriving newtype (NFData, PathPiece, PersistField, PersistFieldSql, Csv.ToField, Csv.FromField)
instance E.SqlString LmsIdent where
makeLenses_ ''LmsIdent

deriveJSON defaultOptions
  { constructorTagModifier = camelToPathPiece' 1  
  , fieldLabelModifier = camelToPathPiece' 2
  , omitNothingFields = True  
  } ''LmsIdent 

-- TODO: Is this a good idea? An ordinary Enum and a separate Day column in the DB would be better, e.g. allowing use of insertSelect in Jobs.Handler.LMS?
data LmsStatus = LmsBlocked { lmsStatusDay :: Day } 
               | LmsSuccess { lmsStatusDay :: Day }
  deriving (Eq, Ord, Read, Show, Generic, Typeable, NFData)

isLmsSuccess :: LmsStatus -> Bool 
isLmsSuccess LmsSuccess{} = True 
isLmsSuccess _other       = False 

-- Entscheidung 08.04.22: LmsSuccess gewinnt immer über LmsBlocked oder umgekehrt
instance Semigroup LmsStatus where 
  a <> b | a >= b    = a  
         | otherwise = b

deriveJSON  defaultOptions
  { constructorTagModifier = camelToPathPiece' 1      -- remove lms from constructor, since the object is tagged with lms already
  , fieldLabelModifier     = camelToPathPiece' 2      -- just day suffices for the day field
  , omitNothingFields = True
  , sumEncoding = TaggedObject "lms-status" "lms-result"
  } ''LmsStatus
derivePersistFieldJSON ''LmsStatus


-- | LMS interface requires Bool to be encoded by 0 or 1 only
newtype LmsBool = LmsBool { lms2bool :: Bool }
  deriving (Eq, Ord, Read, Show, Generic, Typeable)

_lmsBool :: Iso' Bool LmsBool
_lmsBool = iso LmsBool lms2bool

instance Csv.ToField LmsBool where 
  toField (LmsBool False) = "0"
  toField (LmsBool True ) = "1"
  
instance Csv.FromField LmsBool where 
  parseField i
    | i == "0"  = pure $ LmsBool False
    | i == "1"  = pure $ LmsBool True 
    | otherwise = mempty

-- | LMS interface requires day format not compliant with iso8601
newtype LmsDay = LmsDay { lms2day :: Day }
  deriving (Eq, Ord, Read, Show, Generic, Typeable)

_lmsDay :: Iso' Day LmsDay 
_lmsDay = iso LmsDay lms2day

-- | Format for day for LMS interface
lmsDayFormat :: String 
lmsDayFormat = "%d-%m-%Y"

instance Csv.ToField LmsDay where 
  toField (LmsDay d) = Csv.toField $ Time.formatTime Time.defaultTimeLocale lmsDayFormat d -- TimeLocale should not matter; getTimeLocale requires MonadHandler

instance Csv.FromField LmsDay where 
--  parseField = fmap LmsDay . parseLmsDay <=< Csv.parseField
--    where parseLmsDay = Time.parseTimeM True Time.defaultTimeLocale lmsDayFormat
  parseField i = do 
    s <- Csv.parseField i 
    d <- Time.parseTimeM True Time.defaultTimeLocale lmsDayFormat s
         <|> iso8601ParseM s -- Know-How AG considers supplying iso8601 dates in the future
    return $ LmsDay d
