module Database.PostgreSQL.Embedded.Postgres
    ( startPostgres
    , stopPostgres
    , checkPostgresStarted
    ) where

import           Control.Exception                     (SomeException, try)
import           System.Process                        (system)
import           Data.List                             (isInfixOf)
import           Data.Monoid                           ((<>))
import           System.Directory                      (doesDirectoryExist)
import           System.Exit                           (exitFailure)
import           System.FilePath.Posix                 ((</>))
import           System.Info                           (os)

import           Database.PostgreSQL.Embedded.Download
import           Database.PostgreSQL.Embedded.Types
import           Database.PostgreSQL.Embedded.Exec

{-|
Starts PostgreSQL instance with given config.

Returns @RuntimeConfig@ that is required for @stopPostgres@.
-}
startPostgres :: StartupConfig -> DBConfig -> IO RuntimeConfig
startPostgres (StartupConfig сlean version_ silent_) dConfig@(DBConfig p u) = do
    let execSystem = if silent_ then systemSilent else system
    e <- downloadPostgres silent_ getOS version_
    let d = e </> "data"
    exists <- doesDirectoryExist d
    _ <- if exists && not сlean then do
        putStrLn $ "Directory " <> d <> " must not exist"
        exitFailure
    else execSystem $ "rm" <> " -rf " <> d

    _ <- do
        _ <- execSystem $ e </> "bin" </> "initdb" <> " -A trust -U " <> u <> " -D " <> d <> " -E UTF-8"
        execSystem $ e </> "bin" </> "pg_ctl" <> " -w " <> " -D " <> d <> " -o \"-F -p " <>
            show p <> "\"" <> " -l " <> (e </> "log") <> " start"

    let r = RuntimeConfig e d silent_
    checkPostgresStarted r dConfig
    return r

    where
        getOS | "darwin" `isInfixOf` os = OSX
              | "linux" `isInfixOf` os = Linux
              | "win" `isInfixOf` os = Win
              | otherwise = error $ "Unsupported platform" <> os

{-|
Stops PostgreSQL instance.

Doesn't remove data directory.
-}
stopPostgres :: RuntimeConfig -> IO ()
stopPostgres (RuntimeConfig e d s) = do
        let execSystem = if s then systemSilent else system
        _ <- execSystem $ e </> "bin" </> "pg_ctl" <> " -D " <> d <> " stop" <> " -m fast -t 5 -w"
        return ()

checkPostgresStarted :: RuntimeConfig -> DBConfig -> IO ()
checkPostgresStarted (RuntimeConfig e _ s) (DBConfig p _) = checkPostgresStarted_ 10 >>= \_ -> return ()
    where
        checkPostgresStarted_ :: Int -> IO Bool
        checkPostgresStarted_ n = do
            let execSystem = if s then systemSilent else system
            let check = execSystem (e </> "bin" </> "pg_isready" <> " -p " <> show p <> " -h localhost") >>= \_ -> return True

            res <- try check :: IO (Either SomeException Bool)
            let retry = if n > 0 then checkPostgresStarted_ (n - 1)
                        else return False
            case res of
                Left err   -> print err >>= const retry
                Right res1 -> if not res1 then retry else return True