{-# LANGUAGE ScopedTypeVariables #-}

module Snap.TestCommon where

------------------------------------------------------------------------------
import Control.Concurrent   ( threadDelay                )
import Control.Exception    ( ErrorCall(..)
                            , SomeException
                            , bracket
                            , catch
                            , throwIO
                            )
import Control.Monad        ( forM_                      )
import Data.Maybe           ( fromMaybe                  )
import Data.Monoid          ( First(..), mconcat         )
import Prelude       hiding ( catch                      )
import System.Cmd           ( system                     )
import System.Directory     ( doesFileExist
                            , getCurrentDirectory
                            , findExecutable
                            , removeFile
                            )
import System.Environment   ( getEnv                     )
import System.Exit          ( ExitCode(..)               )
import System.FilePath      ( joinPath, splitPath, (</>) )
import System.FilePath.Glob ( compile, globDir1          )
import System.Process       ( runCommand
                            , terminateProcess
                            , waitForProcess
                            )

------------------------------------------------------------------------------
import SafeCWD


------------------------------------------------------------------------------
testGeneratedProject :: String  -- ^ project name and directory
                     -> String  -- ^ arguments to @snap init@
                     -> String  -- ^ arguments to @cabal install@
                     -> Int     -- ^ port to run http server on
                     -> IO ()   -- ^ action to run when the server goes up
                     -> IO ()
testGeneratedProject projName snapInitArgs cabalInstallArgs httpPort
                     testAction = do
    cwd <- getCurrentDirectory

    --------------------------------------------------------------------------
    let segments     = reverse $ splitPath cwd
        projectPath  = cwd </> "test-snap-exe" </> projName
        snapRoot     = joinPath $ reverse $ drop 1 segments
        snapRepos    = joinPath $ reverse $ drop 2 segments
        sandbox      = cwd </> "test-cabal-dev"
        cabalDevArgs = "-s " ++ sandbox
        args         = cabalDevArgs ++ " --reinstall " ++ cabalInstallArgs

        ----------------------------------------------------------------------
        initialize = do
            snapExe <- findSnap
            systemOrDie $ snapExe ++ " init " ++ snapInitArgs

            snapCoreSrc     <- fromEnv "SNAP_CORE_SRC" $
                               snapRepos </> "snap-core"
            snapServerSrc   <- fromEnv "SNAP_SERVER_SRC" $
                               snapRepos </> "snap-server"
            xmlhtmlSrc      <- fromEnv "XMLHTML_SRC" $ snapRepos </> "xmlhtml"
            heistSrc        <- fromEnv "HEIST_SRC" $ snapRepos </> "heist"
            dynLoaderSrc    <- fromEnv "DYNAMIC_LOADER_SRC" $
                               snapRepos </> "snap-loader-dynamic"
            staticLoaderSrc <- fromEnv "STATIC_LOADER_SRC" $
                               snapRepos </> "snap-loader-static"
            let snapSrc   =  snapRoot

            forM_ [ "snap-core", "snap-server", "xmlhtml", "heist", "snap"
                  , "snap-loader-static", "snap-loader-dynamic"]
                  (pkgCleanUp sandbox)

            forM_ [ snapCoreSrc, snapServerSrc, xmlhtmlSrc, heistSrc
                  , snapSrc, staticLoaderSrc, dynLoaderSrc] $ \s ->
                systemOrDie $ concat [ "cabal-dev "
                                     , cabalDevArgs
                                     , " add-source "
                                     , s
                                     ]

            systemOrDie $ "cabal-dev install " ++ args
            let cmd = ("." </> "dist" </> "build" </> projName </> projName)
                      ++ " -p " ++ show httpPort
            putStrLn $ "Running \"" ++ cmd ++ "\""
            pHandle <- runCommand cmd
            waitABit
            return pHandle

        ----------------------------------------------------------------------
        findSnap = do
            home <- fromEnv "HOME" "."
            p1   <- gimmeIfExists $ snapRoot </> "dist" </> "build"
                                             </> "snap" </> "snap"
            p2   <- gimmeIfExists $ home </> ".cabal" </> "bin" </> "snap"
            p3   <- findExecutable "snap"

            return $ fromMaybe (error "couldn't find snap executable")
                               (getFirst $ mconcat $ map First [p1,p2,p3])

    --------------------------------------------------------------------------
    putStrLn $ "Changing directory to " ++ projectPath
    inDir True projectPath $ bracket initialize cleanup (const testAction)
    removeDirectoryRecursiveSafe projectPath

  where
    --------------------------------------------------------------------------
    fromEnv name def = do
        r <- getEnv name `catch` \(_::SomeException) -> return ""
        if r == "" then return def else return r

    --------------------------------------------------------------------------
    cleanup pHandle = do
        terminateProcess pHandle
        waitForProcess pHandle

    --------------------------------------------------------------------------
    waitABit = threadDelay $ 2*10^(6::Int)

    --------------------------------------------------------------------------
    pkgCleanUp d pkg = do
        paths <- globDir1 (compile $ "packages*conf/" ++ pkg ++ "-*") d
        forM_ paths $ \x ->
            rm x `catch` \(_::SomeException) -> return ()

      where
        rm x = do
            putStrLn $ "removing " ++ x
            removeFile x

    --------------------------------------------------------------------------
    gimmeIfExists p = do
        b <- doesFileExist p
        if b then return (Just p) else return Nothing


------------------------------------------------------------------------------
systemOrDie :: String -> IO ()
systemOrDie s = do
    putStrLn $ "Running \"" ++ s ++ "\""
    system s >>= check

  where
    check ExitSuccess = return ()
    check _           = throwIO $ ErrorCall $ "command failed: '" ++ s ++ "'"
