module Jobs.Types
  ( Job(..), Notification(..)
  , JobCtl(..)
  , JobContext(..)
  , JobState(..)
  , jobWorkerNames
  , JobWorkerId
  , showWorkerId, newWorkerId
  ) where

import Import.NoFoundation hiding (Unique)

import Data.Aeson (defaultOptions, Options(..), SumEncoding(..))
import Data.Aeson.TH (deriveJSON)

import Data.List.NonEmpty (NonEmpty)

import Data.Unique

import qualified Data.Map.Strict as Map
import qualified Data.Set as Set


data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notification }
         | JobSendTestEmail { jEmail :: Email, jMailContext :: MailContext }
         | JobQueueNotification { jNotification :: Notification }
         | JobHelpRequest { jHelpSender :: Either (Maybe Address) UserId
                          , jRequestTime :: UTCTime
                          , jSubject :: Maybe Text
                          , jHelpRequest :: Text
                          , jReferer :: Maybe Text
                          }
         | JobSetLogSettings { jInstance :: InstanceId, jLogSettings :: LogSettings }
         | JobDistributeCorrections { jSheet :: SheetId }
         | JobSendCourseCommunication { jRecipientEmail :: Either UserEmail UserId
                                      , jAllRecipientAddresses :: Set Address
                                      , jCourse :: CourseId
                                      , jSender :: UserId
                                      , jMailObjectUUID :: UUID
                                      , jSubject :: Maybe Text
                                      , jMailContent :: Html
                                      }
         | JobInvitation { jInviter :: UserId
                         , jInvitee :: UserEmail
                         , jInvitationUrl :: Text
                         , jInvitationSubject :: Text
                         , jInvitationExplanation :: Html
                         }
  deriving (Eq, Ord, Show, Read, Generic, Typeable)
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
                  | NotificationSheetActive { nSheet :: SheetId }
                  | NotificationSheetSoonInactive { nSheet :: SheetId }
                  | NotificationSheetInactive { nSheet :: SheetId }
                  | NotificationCorrectionsAssigned { nUser :: UserId, nSheet :: SheetId }
                  | NotificationCorrectionsNotDistributed { nSheet :: SheetId }
                  | NotificationUserRightsUpdate { nUser :: UserId, originalRights :: [(SchoolShorthand,Bool,Bool)] } -- User rights (admin, lecturer,...) were changed somehow
  deriving (Eq, Ord, Show, Read, Generic, Typeable)

instance Hashable Job
instance Hashable Notification

deriveJSON defaultOptions
  { constructorTagModifier = camelToPathPiece' 1
  , fieldLabelModifier = camelToPathPiece' 1
  , tagSingleConstructors = True
  , sumEncoding = TaggedObject "job" "data"
  } ''Job

deriveJSON defaultOptions
  { constructorTagModifier = camelToPathPiece' 1
  , fieldLabelModifier = camelToPathPiece' 1
  , tagSingleConstructors = True
  , sumEncoding = TaggedObject "notification" "data"
  } ''Notification


data JobCtl = JobCtlFlush
            | JobCtlPerform QueuedJobId
            | JobCtlDetermineCrontab
            | JobCtlQueue Job
            | JobCtlGenerateHealthReport HealthCheck
            | JobCtlNoOp
  deriving (Eq, Ord, Read, Show, Generic, Typeable)

instance Hashable JobCtl


newtype JobWorkerId = JobWorkerId { jobWorkerUnique :: Unique }
  deriving (Eq, Ord)

showWorkerId :: JobWorkerId -> Text
-- ^ Make a `JobWorkerId` somewhat human readable as a small-ish Number
showWorkerId = tshow . hashUnique . jobWorkerUnique

newWorkerId :: MonadIO m => m JobWorkerId
newWorkerId = JobWorkerId <$> liftIO newUnique
  
data JobContext = JobContext
  { jobCrontab :: TVar (Crontab JobCtl)
  , jobConfirm :: TVar (HashMap JobCtl (NonEmpty (TMVar (Maybe SomeException))))
  }

data JobState = JobState
  { jobWorkers :: Map (Async ()) (TChan JobCtl)
  , jobWorkerName :: Async () -> JobWorkerId
  , jobContext :: JobContext
  , jobPoolManager :: Async ()
  , jobCron :: Async ()
  , jobShutdown :: TMVar ()
  }

jobWorkerNames :: JobState -> Set JobWorkerId
jobWorkerNames JobState{..} = Set.map jobWorkerName $ Map.keysSet jobWorkers
