{-# LANGUAGE ScopedTypeVariables #-}
module Main (main) where

import Autoproc.Configuration (defaultVariables)
import Autoproc.Run (autoprocMain)
import Autoproc.Rules.Dagit (dagitRules)

import Prelude hiding (catch)
import System.IO (openFile, IOMode(WriteMode), hClose)
import System.Posix.Process (executeFile, forkProcess, createSession, getProcessStatus)
import System.Process (runProcess, waitForProcess)
import System.Directory (getAppUserDataDirectory, getModificationTime)
import Control.Monad.Trans (liftIO, MonadIO)
import Control.Exception (bracket, catch, SomeException)
import Control.Monad (when)
import System.Exit (ExitCode(..), exitWith)
import Control.Applicative ((<$>))

-- | The entry point into autoproc. Attempts to compile @~/.autoproc/autoproc.hs@
-- for autoproc, and if it doesn't find one, just compiles the default.
-- This code and method is totally stolen from XMonad. Thanks guys!
main :: IO ()
main = catch (buildLaunch) (\(_::SomeException) -> autoprocMain defaultVariables dagitRules)

-- | Build "~/.autoproc/autoproc.hs" with GHC, then execute it.  If there are no
-- errors, this function does not return.  An exception is raised in any of
-- these cases:
--   * ghc missing
--   * ~/.autoproc/autoproc.hs missing
--   * autoproc.hs fails to compile
--      ** wrong ghc in path (fails to compile)
--      ** type error, syntax error, ..
--   * Missing autoproc/AutoprocContrib modules due to ghc upgrade
--
buildLaunch :: IO ()
buildLaunch = do
    recompile True
    dir  <- getAutoprocDir
    executeFile (dir ++ "/autoproc") False [] Nothing
    return ()

-- | Return the path to @~\/.autoproc@.
getAutoprocDir :: MonadIO m => m String
getAutoprocDir = liftIO $ getAppUserDataDirectory "autoproc"

-- | 'recompile force', recompile @~\/.autoproc\/autoproc.hs@ when any of the
-- following apply:
--      * force is True
--      * the autoproc executable does not exist
--      * the autoproc executable is older than autoproc.hs
--
-- The -i flag is used to restrict recompilation to the autoproc.hs file only.
--
-- Compilation errors (if any) are logged to ~\/.autoproc\/autoproc.errors.  If
-- GHC indicates failure with a non-zero exit code, an xmessage displaying
-- that file is spawned.
--
-- False is returned if there are compilation errors.
recompile :: MonadIO m => Bool -> m Bool
recompile force = liftIO $ do
    dir <- getAutoprocDir
    let binn = "autoproc"
        bin  = dir ++ "/" ++ binn
        base = dir ++ "/" ++ "autoproc"
        err  = base ++ ".errors"
        src  = base ++ ".hs"
    srcT <- getModTime src
    binT <- getModTime bin
    if (force || srcT > binT)
      then do
        status <- bracket (openFile err WriteMode) hClose $ \h -> do
            waitForProcess =<< runProcess "ghc" ["--make", "autoproc.hs", "-i", "-no-recomp", "-v0", "-o",binn] (Just dir)
                                    Nothing Nothing Nothing (Just h)

        -- now, if it fails, run xmessage to let the user know:
        when (status /= ExitSuccess) $ do
            ghcErr <- readFile err
            let msg = unlines $
                    ["Error detected while loading autoproc configuration file: " ++ src]
                    ++ lines ghcErr ++ ["","Please check the file for errors."]
            doubleFork $ executeFile "xmessage" True ["-default", "okay", msg] Nothing
        return (status == ExitSuccess)
      else return True
 where getModTime f = catch (Just <$> getModificationTime f) (\(_::SomeException) -> return Nothing)

-- | Double fork and execute an IO action (usually one of the exec family of
-- functions)
doubleFork :: MonadIO m => IO () -> m ()
doubleFork m = liftIO $ do
    pid <- forkProcess $ do
        forkProcess (createSession >> m)
        exitWith ExitSuccess
    getProcessStatus True False pid
    return ()

