{-# LANGUAGE DoAndIfThenElse #-}
module Cook.Uploader
    ( Uploader
    , mkUploader
    , killUploader
    , enqueueImage
    , waitForCompletion
    )
where

import Cook.Types
import Cook.Util

import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import Data.Maybe
import GHC.Natural
import System.Process
import System.Exit
import qualified Data.Text as T

data Uploader
   = Uploader
   { _u_threadId :: ThreadId
   , _u_queue :: TBQueue DockerImage
   , _u_task :: TVar (Maybe DockerImage)
   }

mkUploader :: Natural -> IO Uploader
mkUploader queueSize =
    do q <- newTBQueueIO queueSize
       v <- newTVarIO Nothing
       tid <- forkIO (uploader q v)
       return (Uploader tid q v)

enqueueImage :: Uploader -> DockerImage -> IO ()
enqueueImage (Uploader _ queue _) im =
    atomically $ writeTBQueue queue im

killUploader :: Uploader -> IO [DockerImage]
killUploader (Uploader tid queue taskV) =
    do (queueVals, currentTask) <-
           atomically $ ((,) <$> readAll <*> readTVar taskV)
       killThread tid
       return $ queueVals ++ (maybeToList currentTask)
    where
      readAll :: STM [DockerImage]
      readAll =
          do qr <- tryReadTBQueue queue
             case qr of
               Just val ->
                   do more <- readAll
                      return (val : more)
               Nothing ->
                   return []

waitForCompletion :: Uploader -> IO ()
waitForCompletion (Uploader _ q v) =
    atomically $
    do isEmpty <- isEmptyTBQueue q
       mTask <- readTVar v
       when ((not isEmpty) || (isJust mTask)) $ retry

uploadImage :: DockerImage -> IO (Either String ())
uploadImage (DockerImage imName') =
    do let imName = T.unpack imName'
       logInfo ("Pushing " ++ imName ++ " to the registry")
       (ec, stdOut, stdErr) <-
           readProcessWithExitCode "docker"
                                       ["push"
                                       , imName] ""
       if ec == ExitSuccess
       then return $ Right ()
       else return $ Left ("Failed to upload " ++ imName
                           ++ "\n"
                           ++ stdOut
                           ++ "\n"
                           ++ stdErr
                          )

uploader :: TBQueue DockerImage -> TVar (Maybe DockerImage) -> IO ()
uploader q v =
    do nextImage <-
           atomically $
           do t <- readTBQueue q
              writeTVar v (Just t)
              return t
       uploadStatus <- uploadImage nextImage
       atomically $ writeTVar v Nothing
       case uploadStatus of
         Left err -> logWarn (err ++ "\n Uploader quit.")
         Right _ ->
             do atomically $ writeTVar v Nothing
                uploader q v
