{-# LANGUAGE TemplateHaskell #-}

module Zifter.Recurse
    ( recursiveZift
    , recursively
    , hiddenIn
    ) where

import Control.Monad
import Control.Monad.IO.Class
import Data.List
import Path
import Path.IO
import System.Exit
import qualified System.FilePath as FP
import System.IO
import System.Process

import Zifter.Script
import Zifter.Zift

-- | Recursively call each @zift.hs@ script in the directories below the
-- directory of the currently executing @zift.hs@ script.
--
-- Only the topmost @zift.hs@ script in each directory is executed.
-- This means that, to execute all @zift.hs@ scripts recursively, each of those
-- @zift.hs@ scripts must also have a 'recursiveZift' declaration.
recursiveZift :: ZiftScript ()
recursiveZift = do
    preprocessor $ do
        rd <- getRootDir
        printRecursionMsg $
            unwords ["RECURSIVE PREPROCESSING STARTING FROM", toFilePath rd]
        recursively $ \ziftFile -> runZiftScript ziftFile "preprocess"
        printRecursionMsg $
            unwords ["RECURSIVE PREPROCESSING FROM", toFilePath rd, "DONE."]
    prechecker $ do
        rd <- getRootDir
        printRecursionMsg $
            unwords ["RECURSIVE PRECHECKING STARTING FROM", toFilePath rd]
        recursively $ \ziftFile -> runZiftScript ziftFile "precheck"
        printRecursionMsg $
            unwords ["RECURSIVE PRECHECKING FROM", toFilePath rd, "DONE."]
    checker $ do
        rd <- getRootDir
        printRecursionMsg $
            unwords ["RECURSIVE CHECKING STARTING FROM", toFilePath rd]
        recursively $ \ziftFile -> runZiftScript ziftFile "check"
        printRecursionMsg $
            unwords ["RECURSIVE CHECKING FROM", toFilePath rd, "DONE"]

recursively :: (Path Abs File -> Zift ()) -> Zift ()
recursively func = do
    fs <- findZiftFilesRecursively
    -- In serial on purpose.
    forM_ fs func

halfIndent :: String -> String
halfIndent = ("  " ++)

indent :: String -> String
indent = halfIndent . ("| " ++)

printRecursionMsg :: String -> Zift ()
printRecursionMsg = printZiftMessage . halfIndent

runZiftScript :: Path Abs File -> String -> Zift ()
runZiftScript scriptPath command = do
    rd <- getRootDir
    printRecursionMsg $
        unwords
            [ "ZIFTING"
            , toFilePath scriptPath
            , "AS PART OF RECURSIVE ZIFT FROM"
            , toFilePath rd
            ]
    let cmd = unwords [toFilePath scriptPath, command]
    let cp =
            (shell cmd)
                { cwd = Just $ toFilePath $ parent scriptPath
                , std_out = CreatePipe
                }
    (_, mouth, merrh, ph) <- liftIO $ createProcess cp
    ec <- liftIO $ waitForProcess ph
    case mouth of
        Nothing -> pure ()
        Just outh -> do
            cts <- liftIO (hGetContents outh)
            forM_ (lines cts) $ printZift . indent
    case merrh of
        Nothing -> pure ()
        Just errh -> liftIO (hGetContents errh) >>= printZift
    case ec of
        ExitSuccess ->
            printRecursionMsg $
            unwords
                [ "ZIFTING"
                , toFilePath scriptPath
                , "AS PART OF RECURSIVE ZIFT FROM"
                , toFilePath rd
                , "DONE"
                ]
        ExitFailure c -> do
            printPreprocessingError $ halfIndent "RECURSIVE ZIFT FAILED"
            fail $
                unwords
                    [ show cmd
                    , "failed with exit code"
                    , show c
                    , "while recursively zifting with"
                    , toFilePath scriptPath
                    ]

findZiftFilesRecursively :: Zift [Path Abs File]
findZiftFilesRecursively = do
    rd <- getRootDir
    fs <- findFiles [rd] $(mkRelFile "zift.hs")
    pure $ filter (not . hiddenIn rd) fs

hiddenIn :: Path Abs Dir -> Path Abs File -> Bool
hiddenIn rp af =
    case stripProperPrefix rp af of
        Nothing -> True
        Just rf -> any (isPrefixOf ".") $ FP.splitDirectories $ fromRelFile rf