{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module: TestLoop.Main
-- Copyright: 2013 Roman Gonzalez
-- License: MIT
--
-- Maintainer: romanandreg@gmail.com
-- Portability: unix
--
module System.TestLoop (
  -- * Main function
  setupTestLoop
  ) where

--------------------

import           Control.Concurrent               (forkIO, threadDelay)
import           Control.Monad                    (forM_, forever)
import           Data.Maybe                       (catMaybes)
import           Data.Monoid                      (mconcat)
import           System.Directory                 (doesFileExist)
import           System.FilePath                  (joinPath)
import           System.IO                        (hPutStrLn, stderr)

--------------------

import qualified Filesystem.Path.CurrentOS        as FS

--------------------

import           System.FSNotify                  (withManager)
import           System.FSNotify.Devel            (treeExtExists)
--------------------

import           System.TestLoop.Internal.Cabal
import           System.TestLoop.Internal.Types
import           System.TestLoop.Internal.Watcher
import           System.TestLoop.Util

--------------------------------------------------------------------------------

startTestLoop :: MainModuleName -> MainModulePath -> HsSourcePaths -> IO ()
startTestLoop moduleName modulePath paths =
   withManager $ \manager -> do
     forM_ paths $ \path -> do
       treeExtExists manager
                     (FS.decodeString path)
                     "hs"
                     (reloadTestSuite moduleName modulePath paths)
     forever $ threadDelay 100

--------------------------------------------------------------------------------

getTestMainFilePath :: HsSourcePaths -> MainModulePath -> IO (Either String FilePath)
getTestMainFilePath sourcePaths modulePath = do
    mainPaths <- mapM getPossiblePath sourcePaths
    case (catMaybes mainPaths) of
      [completeModulePath] -> return $ Right completeModulePath
      [] -> return . Left $ mconcat [ "Could not find `",
                                      modulePath,
                                      "' in ",
                                      show sourcePaths]
      multipleMatches -> return . Left $ mconcat [ "Multiple matches for test `Main' module"
                                                , "on source-paths: \n",
                                                show mainPaths ]

  where
    getPossiblePath sourcePath = do
      let completeModulePath = joinPath [sourcePath, modulePath]
      fileExists <- doesFileExist completeModulePath
      if fileExists
        then return $ Just completeModulePath
        else return Nothing

--------------------------------------------------------------------------------

-- | Parses your project's cabal file to find possible test-suites you
--   may have on your project, then it will start a file modification
--   tracking and once a file is changed it will run the testsuite
--   automatically.  in the test-suite's hs-source-dirs setting.
--
-- Use this function as the main of you testloop executable.
-- e.g
--
-- > module Main where
-- >
-- > import System.TestLoop
-- >
-- > main :: IO ()
-- > main = setupTestLoop
--
setupTestLoop :: IO ()
setupTestLoop = do
 (testsuite, moduleFile, sourcePaths) <- parseCabalFile
 result <- getTestMainFilePath sourcePaths moduleFile
 case result of
   Left e -> hPutStrLn stderr e
   Right fullModuleFilePath -> do
     putStrLn $ "Found test-suite main function on `" ++ fullModuleFilePath ++ "'"
     putStrLn $ "Listening files on source paths: " ++ (join ", " sourcePaths)
     _ <- forkIO $ startTestLoop "Main"
                                 fullModuleFilePath
                                 sourcePaths
     forever $ threadDelay 100