{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Handler.Exam.AutoOccurrence
  ( examAutoOccurrenceCalculateWidget
  , postEAutoOccurrenceR
  ) where

import Import
import Handler.Utils
import Handler.Utils.Exam

import qualified Data.Map as Map

import qualified Database.Esqueleto as E
import Database.Persist.Sql (updateWhereCount)


newtype ExamAutoOccurrenceCalculateForm = ExamAutoOccurrenceCalculateForm
  { eaofConfig :: ExamAutoOccurrenceConfig
  } deriving stock   (Eq, Ord, Read, Show, Generic, Typeable)
    deriving newtype (Default, FromJSON, ToJSON)

makeLenses_ ''ExamAutoOccurrenceCalculateForm

data ExamAutoOccurrenceAcceptForm = ExamAutoOccurrenceAcceptForm
  { eaofMapping    :: Maybe (ExamOccurrenceMapping ExamOccurrenceId)
  , eaofAssignment :: Map UserId (Maybe ExamOccurrenceId)
  } deriving (Eq, Ord, Read, Show, Generic, Typeable)

deriveJSON defaultOptions
  { fieldLabelModifier = camelToPathPiece' 1
  } ''ExamAutoOccurrenceAcceptForm

data ExamAutoOccurrenceButton
  = BtnExamAutoOccurrenceCalculate
  | BtnExamAutoOccurrenceAccept
  | BtnExamAutoOccurrenceNudgeUp | BtnExamAutoOccurrenceNudgeDown
  deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
instance Universe ExamAutoOccurrenceButton
instance Finite ExamAutoOccurrenceButton

nullaryPathPiece ''ExamAutoOccurrenceButton $ camelToPathPiece' 4

embedRenderMessage ''UniWorX ''ExamAutoOccurrenceButton id
instance Button UniWorX ExamAutoOccurrenceButton where
  btnClasses BtnExamAutoOccurrenceNudgeUp = [BCIsButton]
  btnClasses BtnExamAutoOccurrenceNudgeDown = [BCIsButton]
  btnClasses _ = [BCIsButton, BCPrimary]


examAutoOccurrenceCalculateForm :: ExamAutoOccurrenceCalculateForm -> Form ExamAutoOccurrenceCalculateForm
examAutoOccurrenceCalculateForm ExamAutoOccurrenceCalculateForm{ eaofConfig }
  = identifyForm FIDExamAutoOccurrenceCalculate . renderAForm FormStandard $ ExamAutoOccurrenceCalculateForm <$> eaocForm
  where
    eaocForm =
      (set _eaocMinimizeRooms <$> apopt checkBoxField (fslI MsgExamAutoOccurrenceMinimizeRooms & setTooltip MsgExamAutoOccurrenceMinimizeRoomsTip) (Just $ eaofConfig ^. _eaocMinimizeRooms))
      <*> pure def

examAutoOccurrenceNudgeForm :: ExamOccurrenceId -> Maybe ExamAutoOccurrenceCalculateForm -> Form ExamAutoOccurrenceCalculateForm
examAutoOccurrenceNudgeForm occId protoForm html = do
  cID <- encrypt occId
  (btnRes, wgt) <- identifyForm (FIDExamAutoOccurrenceNudge $ ciphertext cID) (buttonForm' [BtnExamAutoOccurrenceNudgeUp, BtnExamAutoOccurrenceNudgeDown]) html
  oldDataRes <- globalPostParamField PostExamAutoOccurrencePrevious secretJsonField
  oldDataId <- newIdent

  let protoForm' = fromMaybe def $ oldDataRes <|> protoForm
      genForm btn = protoForm' & _eaofConfig . _eaocNudge %~ Map.insertWith (+) occId n
        where n = case btn of
                BtnExamAutoOccurrenceNudgeUp   ->  1
                BtnExamAutoOccurrenceNudgeDown -> -1
                _other                         -> 0
      res = genForm <$> btnRes
      oldDataView = fieldView (secretJsonField :: Field Handler _) oldDataId (toPathPiece PostExamAutoOccurrencePrevious) [] (Right . fromMaybe protoForm' $ formResult' res) False
  return (res, wgt <> oldDataView)

examAutoOccurrenceAcceptForm :: Maybe ExamAutoOccurrenceAcceptForm -> Form ExamAutoOccurrenceAcceptForm
examAutoOccurrenceAcceptForm confirmData = identifyForm FIDExamAutoOccurrenceConfirm $ \html -> do
  (confirmDataRes, confirmDataView) <- mreq secretJsonField "" confirmData
  (acceptRes, acceptView) <- buttonForm' [BtnExamAutoOccurrenceAccept] mempty
  return (acceptRes *> confirmDataRes, toWidget html <> fvWidget confirmDataView <> acceptView)


examAutoOccurrenceCalculateWidget :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Widget
examAutoOccurrenceCalculateWidget tid ssh csh examn = do
  (formView, formEncoding) <- liftHandler . generateFormPost $ examAutoOccurrenceCalculateForm def

  wrapForm' BtnExamAutoOccurrenceCalculate $(i18nWidgetFile "exam-auto-occurrence-calculate") def
    { formAction = Just . SomeRoute $ CExamR tid ssh csh examn EAutoOccurrenceR
    , formEncoding
    }


postEAutoOccurrenceR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
postEAutoOccurrenceR tid ssh csh examn = do
  (Entity eId Exam{ examOccurrenceRule }, occurrences) <- runDB $ do
    exam@(Entity eId _) <- fetchExam tid ssh csh examn
    occurrences <- selectList [ ExamOccurrenceExam ==. eId ] [ Asc ExamOccurrenceName ]
    return (exam, occurrences)


  ((calculateRes, _), _) <- runFormPost $ examAutoOccurrenceCalculateForm def

  nudgeRes <- sequence . flip Map.fromSet (setOf (folded . _entityKey) occurrences) $ \occId ->
    runFormPost $ examAutoOccurrenceNudgeForm occId (formResult' calculateRes)

  let calculateRes' = asum $ calculateRes : nudgeRes ^.. folded . _1 . _1

  calcResult <- formResultMaybe calculateRes' $ \ExamAutoOccurrenceCalculateForm{..} -> runDB $ do
    participants <- E.select . E.from $ \(registration `E.InnerJoin` user) -> do
      E.on $ registration E.^. ExamRegistrationUser E.==. user E.^. UserId
      E.where_ $ registration E.^. ExamRegistrationExam E.==. E.val eId
      return (user, registration)
    let participants' = Map.fromList $ do
          (Entity uid userRec, Entity _ ExamRegistration{..}) <- participants
          return (uid, (userRec, examRegistrationOccurrence))
        occurrences' = Map.fromList $ map (\(Entity eoId ExamOccurrence{..}) -> (eoId, fromIntegral examOccurrenceCapacity)) occurrences
        (eaofMapping, eaofAssignment) = examAutoOccurrence eId examOccurrenceRule eaofConfig occurrences' participants'
    return $ Just ExamAutoOccurrenceAcceptForm{..}

  ((confirmRes, confirmView), confirmEncoding) <- runFormPost $ examAutoOccurrenceAcceptForm calcResult
  let confirmWidget = wrapForm confirmView def
        { formAction   = Just . SomeRoute $ CExamR tid ssh csh examn EAutoOccurrenceR
        , formEncoding = confirmEncoding
        , formSubmit   = FormNoSubmit
        }

  formResult confirmRes $ \ExamAutoOccurrenceAcceptForm{..} -> do
    Sum assignedCount <- runDB $ do
      let eaofMapping'' :: Maybe (Maybe (ExamOccurrenceMapping ExamOccurrenceName))
          eaofMapping'' = (<$> eaofMapping) . traverseExamOccurrenceMapping $ \eoId -> case filter ((== eoId) . entityKey) occurrences of
            [Entity _ ExamOccurrence{..}] -> Just examOccurrenceName
            _other -> Nothing
      eaofMapping' <- case eaofMapping'' of
        Nothing        -> return Nothing
        Just  Nothing  -> invalidArgsI [MsgExamAutoOccurrenceOccurrencesChangedInFlight]
        Just (Just x ) -> return $ Just x
      update eId [ ExamExamOccurrenceMapping =. eaofMapping' ]
      fmap fold . iforM eaofAssignment $ \pid occ -> case occ of
        Just _  -> Sum <$> updateWhereCount [ ExamRegistrationExam ==. eId, ExamRegistrationUser ==. pid, ExamRegistrationOccurrence ==. Nothing ] [ ExamRegistrationOccurrence =. occ ]
        Nothing -> return mempty
    addMessageI Success $ MsgExamAutoOccurrenceParticipantsAssigned assignedCount
    redirect $ CExamR tid ssh csh examn EUsersR

  let nudgeWgt = nudgeRes <&> \((_, nudgeView), nudgeEncoding) ->
        wrapForm nudgeView def
          { formAction   = Just . SomeRoute $ CExamR tid ssh csh examn EAutoOccurrenceR
          , formEncoding = nudgeEncoding
          , formSubmit   = FormNoSubmit
          , formAttrs = [("class", "buttongroup")]
          }

  ExamAutoOccurrenceAcceptForm{..} <- maybe (redirect $ CExamR tid ssh csh examn EUsersR) return calcResult

  let heading = MsgTitleExamAutoOccurrence tid ssh csh examn
      mappingWgt
        = let occLoads :: Map ExamOccurrenceId Natural
              occLoads = Map.fromListWith (+) . mapMaybe (\(_, mOcc) -> (, 1) <$> mOcc) $ Map.toList eaofAssignment

              occLoad = fromMaybe 0 . flip Map.lookup occLoads

              occMappingRule = examOccurrenceMappingRule <$> eaofMapping

              loadProp curr max'
                | max' /= 0 = MsgProportion (toMessage curr) (toMessage max') (toRational curr / toRational max')
                | otherwise = MsgProportionNoRatio (toMessage curr) (toMessage max')

              occMapping occId = examOccurrenceMappingDescriptionWidget <$> occMappingRule <*> (Map.lookup occId . examOccurrenceMappingMapping =<< eaofMapping)
           in $(widgetFile "widgets/exam-occurrence-mapping")

  siteLayoutMsg heading $ do
    setTitleI heading
    $(widgetFile "exam/auto-occurrence-confirm")
