module Handler.Sheet.PersonalisedFiles.Meta
  ( formatPersonalisedSheetFilesMeta
  ) where

import Import

import Handler.Sheet.PersonalisedFiles.Types

import qualified Data.Char as Char
import qualified Data.Text as Text
import qualified Data.ByteString.Lazy as Lazy (ByteString)
import qualified Data.ByteString.Lazy as Lazy.ByteString

import qualified Data.YAML as YAML
import qualified Data.YAML.Event as YAML (untagged)
import qualified Data.YAML.Event as YAML.Event
import qualified Data.YAML.Token as YAML (Encoding(..))

import Control.Monad.Trans.State.Lazy (evalState)

import qualified Database.Esqueleto.Legacy as E

import qualified Data.CaseInsensitive as CI

  
data PrettifyState
  = PrettifyInitial
  | PrettifyFlowSequence PrettifyState
  | PrettifyBlockSequence PrettifyState
  | PrettifySeed | PrettifySeedDone
  deriving (Eq, Ord, Read, Show, Generic, Typeable)


  
formatPersonalisedSheetFilesMeta
  :: MonadIO m
  => MsgRendererS UniWorX
  -> PersonalisedSheetFilesDownloadAnonymous
  -> CourseParticipant
  -> CryptoFileNameUser
  -> Maybe (UserIdent -> PersonalisedSheetFilesSeed)
  -> SqlPersistT m Lazy.ByteString
formatPersonalisedSheetFilesMeta (MsgRenderer mr) anonMode CourseParticipant{..} cID mkSeed = do
  User{..} <- getJust courseParticipantUser
  exams <- E.select . E.from $ \(exam `E.InnerJoin` examRegistration) -> E.distinctOnOrderBy [E.asc $ exam E.^. ExamName] $ do
    E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
    E.where_ $ exam E.^. ExamCourse E.==. E.val courseParticipantCourse
    E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val courseParticipantUser
    return $ exam E.^. ExamName
  
  let uglyYAML = YAML.Event.writeEvents YAML.UTF8 $ concat
        [ [ YAML.Event.StreamStart
          , YAML.Event.DocumentStart $ YAML.Event.DirEndMarkerVersion 2
          , YAML.Event.MappingStart Nothing YAML.untagged YAML.Event.Block
          ]
        , mapEvents (str' "user") (str $ toPathPiece cID)
        , mapEvents (str' "seed") (maybe (YAML.Scalar () YAML.SNull) (str . toPathPiece . ($ userIdent)) mkSeed)
        , guardOnM (isn't _PersonalisedSheetFilesDownloadAnonymous anonMode) $ concat
          [ mapEvents (str' "display_name") (str userDisplayName)
          , mapEvents (str' "surname") (str userSurname)
          , mapEvents (str' "first_names") (str userFirstName)
          , case userMatrikelnummer of
              Just matr -> mapEvents (str' "matriculation") (str matr)
              Nothing -> mzero
          , mapEvents (str' "email") (str $ CI.original userEmail)
          ]
        , map flowStyle $ mapEvents (str' "languages") . YAML.Sequence () YAML.untagged $ maybe [] (views _Wrapped $ map str) userLanguages
        , mapEvents (str' "registered_exams") . YAML.Sequence () YAML.untagged $ map (str . CI.original . E.unValue) exams
        , [ YAML.Event.MappingEnd
          , YAML.Event.DocumentEnd False
          , YAML.Event.StreamEnd
          ]
        ]
        where
          str :: forall t. Textual t => t -> YAML.Node ()
          str = YAML.Scalar () . YAML.SStr . repack
          str' :: Text -> YAML.Node ()
          str' = str

          mapEvents :: YAML.Node () -> YAML.Node () -> [YAML.Event.Event]
          mapEvents k v = filterEvs . nodeEvents . YAML.Mapping () YAML.untagged $ singletonMap k v
            where filterEvs ((YAML.Event.MappingStart{} : inner) :> YAML.Event.MappingEnd) = inner
                  filterEvs _other = error "Could not strip Mapping"

          nodeEvents :: YAML.Node () -> [YAML.Event.Event]
          nodeEvents = filterEvs . mapMaybe (fmap YAML.Event.eEvent . preview _Right) . YAML.Event.parseEvents . YAML.encodeNode . pure . YAML.Doc
            where filterEvs = filter $ \case
                    YAML.Event.StreamStart -> False
                    YAML.Event.StreamEnd -> False
                    YAML.Event.DocumentStart _ -> False
                    YAML.Event.DocumentEnd _ -> False
                    _other -> True

          flowStyle :: YAML.Event.Event -> YAML.Event.Event
          flowStyle = \case
            YAML.Event.SequenceStart a t _ -> YAML.Event.SequenceStart a t YAML.Event.Flow
            YAML.Event.MappingStart a t _ -> YAML.Event.MappingStart a t YAML.Event.Flow
            other -> other

      prettyYAML = annotate . (evalState ?? PrettifyInitial) . transduce' $ YAML.Event.parseEvents uglyYAML
        where
          transduce' (Left _ : _) = error "Parse error on uglyYAML"
          transduce' (Right YAML.Event.EvPos{ eEvent, ePos = pos1 } : es@(Right YAML.Event.EvPos{ ePos = pos2 }: _))
            = (:) <$> ((YAML.Event.posByteOffset pos1, YAML.Event.posByteOffset pos2, ) <$> state (`transduce` eEvent)) <*> transduce' es
          transduce' (Right YAML.Event.EvPos{..} : es)
            = (:) <$> ((YAML.Event.posByteOffset ePos, fromIntegral $ Lazy.ByteString.length uglyYAML, ) <$> state (`transduce` eEvent)) <*> transduce' es
          transduce' [] = return []

          annotate = fst . foldl' annotate' (uglyYAML, Lazy.ByteString.length uglyYAML) . reverse
            where annotate' (dat, mLength) (fromIntegral -> pos1, fromIntegral -> pos2, (fromStrict . encodeUtf8 -> ann1, ann3, ann2))
                    = let (before', after) = Lazy.ByteString.splitAt pos2' dat
                          (before, event) = Lazy.ByteString.splitAt pos1' before'
                          event' = decodeUtf8 $ toStrict event
                          ws = Text.takeWhileEnd Char.isSpace event'
                          event'' = Text.dropWhileEnd Char.isSpace event'
                          pos1' = min pos1 mLength
                          pos2' = min pos2 mLength
                       in (before <> ann1 <> fromStrict (encodeUtf8 $ ann3 event'') <> fromStrict (encodeUtf8 $ ann2 ws) <> after, pos1')

          transduce :: PrettifyState -> YAML.Event.Event -> ((Text, Text -> Text, Text -> Text), PrettifyState)
          transduce PrettifyInitial (YAML.Event.Scalar _ _ _ k)
            | k == "seed", is _Just mkSeed = (("\n# " <> mr MsgSheetPersonalisedFilesMetaYAMLSeedComment <> "\n", id, id), PrettifySeed)
            | k == "seed" = (("\n# " <> mr MsgSheetPersonalisedFilesMetaYAMLNoSeedComment <> "\n", id, id), PrettifySeed)
          transduce PrettifySeed YAML.Event.Scalar{}
            = ((mempty, id, beforeBreak "\n"), PrettifySeedDone)
          transduce cState (YAML.Event.SequenceStart _ _ YAML.Event.Flow) = ((mempty, id, bool " " mempty . null), PrettifyFlowSequence cState)
          transduce (PrettifyFlowSequence pState) YAML.Event.SequenceEnd = ((mempty, id, id), pState)
          transduce cState@(PrettifyFlowSequence _) _ = ((mempty, f, bool " " mempty . null), cState)
            where f str | ']' `elem` str = filter (/= '\n') str
                        | otherwise = str
          -- transduce PrettifyInitial _ = ((mempty, id), PrettifyInitial)
          transduce cState (YAML.Event.SequenceStart _ _ YAML.Event.Block) = (("  ", id, id), PrettifyBlockSequence cState)
          transduce (PrettifyBlockSequence pState) YAML.Event.SequenceEnd = ((mempty, id, id), pState)
          transduce cState@(PrettifyBlockSequence _) _ = ((mempty, Text.replace "\n-" "\n  -", id), cState)
          transduce cState _ = ((mempty, id, id), cState)
          -- transduce cState _ = (("<", id, \ws -> "|" <> ws <> ">"), cState) -- TODO

          beforeBreak :: Text -> Text -> Text
          beforeBreak ins ws = before <> ins <> break' <> after
            where (before', after) = Text.breakOnEnd "\n" ws
                  before = Text.dropWhileEnd (== '\n') before'
                  break' = Text.takeWhileEnd (== '\n') before'

  return prettyYAML
