content: module Network.IMAP.Utils where
  
  import Network.IMAP.Types
  import qualified Data.ByteString.Char8 as BSC
  import System.Random
  import qualified Data.Text as T
  import Data.Text.Encoding (encodeUtf8)
  
  import Control.Monad.STM
  import Control.Concurrent.STM.TQueue
  import Control.Concurrent.STM.TVar
  
  import Control.Monad (MonadPlus(..))
  import Control.Monad.IO.Class (MonadIO(..))
  import Control.Concurrent.STM.Delay
  
  genRequestId :: IO BSC.ByteString
  genRequestId = do
    randomGen <- newStdGen
    return $ BSC.pack . Prelude.take 9 $ randomRs ('a', 'z') randomGen
  
  readResults :: (MonadPlus m, MonadIO m, Universe m) =>
    IMAPState -> ResponseRequest -> m CommandResult
  readResults state req@(ResponseRequest resultsQueue requestId) = do
    let timeout = imapTimeout . imapSettings $ state
    let reqs = outstandingReqs state
  
    delay <- liftIO . newDelay $ timeout * 1000000
    let d_wait = do
          didComplete <- tryWaitDelay delay
          if didComplete
            then do
              -- Remove current request from the list of outstanding ones
              allReqs <- readTVar reqs
              writeTVar reqs $ filter (/=req) allReqs
  
              return . Tagged $ TaggedResult {
                commandId=requestId,
                resultState=BAD,
                resultRest="Connection timeout"
              }
            else retry
        readResult = readTQueue resultsQueue
  
    nextResult <- liftIO . atomically $ d_wait `orElse` readResult
    case nextResult of
      Tagged _ -> return nextResult
      Untagged _ -> return nextResult `mplus` readResults state req
  
  escapeText :: T.Text -> T.Text
  escapeText t = T.replace "{" "\\{" $
               T.replace "}" "\\}" $
               T.replace "\"" "\\\"" $
               T.replace "\\" "\\\\" t
  
  
  ifNotDisconnected :: (MonadPlus m, MonadIO m) =>
                       IMAPConnection -> m CommandResult -> m CommandResult
  ifNotDisconnected conn action = do
    connState <- liftIO . atomically . readTVar $ connectionState conn
    if isDisconnected connState
      then return . Tagged $ TaggedResult {
          commandId = "noid",
          resultState = BAD,
          resultRest = "Cannot post a command when watcher is disconnected"
        }
      else action
  
  flagsToText :: [Flag] -> BSC.ByteString
  flagsToText flagList = BSC.concat ["(", encodedFlags, ")"]
    where encodedFlags = BSC.intercalate " " textFlags
          textFlags = flip map flagList (\case
            FSeen -> "\\Seen"
            FAnswered -> "\\Answered"
            FFlagged -> "\\Flagged"
            FDeleted -> "\\Deleted"
            FDraft -> "\\Draft"
            FRecent -> "\\Recent"
            FAny -> "*"
            FOther t -> encodeUtf8 t)
  