module Handler.Allocation.Prios
  ( getAPriosR, postAPriosR
  ) where

import Import

import Handler.Utils
import Handler.Utils.Csv
import Handler.Utils.Allocation

import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E

import qualified Data.Conduit.List as C

import qualified Data.Csv as Csv


data AllocationPrioritiesMode
  = AllocationPrioritiesNumeric
  | AllocationPrioritiesOrdinal
  deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe AllocationPrioritiesMode
instance Finite AllocationPrioritiesMode

nullaryPathPiece ''AllocationPrioritiesMode $ camelToPathPiece' 2

embedRenderMessage ''UniWorX ''AllocationPrioritiesMode id


getAPriosR, postAPriosR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAPriosR = postAPriosR
postAPriosR tid ssh ash = do
  doNumericPrios <- runDB $ do
    Entity aId _ <- getBy404 $ TermSchoolAllocationShort tid ssh ash

    numericPrios <- E.selectCountRows . E.from $ \allocationUser -> do
      E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
      E.where_ . E.maybe E.false sqlAllocationPriorityNumeric $ allocationUser E.^. AllocationUserPriority

    ordinalPrios <- E.selectCountRows . E.from $ \allocationUser -> do
      E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
      E.where_ . E.maybe E.false (E.not_ . sqlAllocationPriorityNumeric) $ allocationUser E.^. AllocationUserPriority
    let doNumericPrios = ((>=) :: Int64 -> Int64 -> Bool) numericPrios ordinalPrios

    return doNumericPrios

  let explainAllocationPrioMode = \case
        AllocationPrioritiesNumeric -> return $(i18nWidgetFile "allocation-priority-explanation/numeric")
        AllocationPrioritiesOrdinal -> return $(i18nWidgetFile "allocation-priority-explanation/ordinal")

  ignoreWarningMsg <- messageIconI Warning IconMissingAllocationPriority MsgAllocationMissingPrioritiesIgnored
  ((priosRes, priosView), priosEnctype) <- runFormPost . renderAForm FormStandard $ (,)
    <$> apopt (explainedSelectionField Nothing (explainOptionList optionsFinite explainAllocationPrioMode)) (fslI MsgAllocationPrioritiesMode) (Just $ bool AllocationPrioritiesOrdinal AllocationPrioritiesNumeric doNumericPrios)
    <*  aformMessage ignoreWarningMsg
    <*> areq fileField (fslI MsgAllocationPrioritiesFile) Nothing

  formResult priosRes $ \(mode, fInfo) -> do
    let sourcePrios = case mode of
          AllocationPrioritiesNumeric -> transPipe liftHandler fInfo .| fileSourceCsvPositional Csv.NoHeader
          AllocationPrioritiesOrdinal -> transPipe liftHandler fInfo .| fileSourceCsvPositional Csv.NoHeader .| C.map Csv.fromOnly .| ordinalPriorities

    (matrSunk, matrMissing) <- runDB $ do
      Entity aId _ <- getBy404 $ TermSchoolAllocationShort tid ssh ash
      updateWhere
        [ AllocationUserAllocation ==. aId ]
        [ AllocationUserPriority =. Nothing ]
      matrSunk <- runConduit $ sourcePrios .| sinkAllocationPriorities aId
      matrMissing <- E.selectCountRows . E.from $ \allocationUser -> do
        E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
             E.&&. E.isNothing (allocationUser E.^. AllocationUserPriority)

        E.where_ . E.exists . E.from $ \(courseApplication `E.InnerJoin` allocationCourse) -> do
          E.on $ allocationCourse E.^. AllocationCourseCourse E.==. courseApplication E.^. CourseApplicationCourse
           E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
          E.where_ $ courseApplication E.^. CourseApplicationUser E.==. allocationUser E.^. AllocationUserUser
               E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (E.val aId)
      return (matrSunk, matrMissing)

    when (matrSunk > 0) $
      addMessageI Success $ MsgAllocationPrioritiesSunk matrSunk
    when (matrMissing > 0) $
      addMessageI Error $ MsgAllocationPrioritiesMissing matrMissing
    redirect $ AllocationR tid ssh ash AUsersR

  siteLayoutMsg MsgAllocationPriorities $ do
    setTitleI $ MsgAllocationPrioritiesTitle tid ssh ash

    let priosForm = wrapForm priosView def
          { formEncoding = priosEnctype
          , formAction = Just . SomeRoute $ AllocationR tid ssh ash APriosR
          }
    gradeScale <- getsYesod $ view _appAllocationGradeScale
    gradeOrdinalProportion <- getsYesod $ view _appAllocationGradeOrdinalProportion

    $(i18nWidgetFile "allocation-priorities")
