module Zifter.Hindent where

import Control.Monad.IO.Class
import Path
import Path.IO
import Safe
import System.Exit (ExitCode(..))
import qualified System.FilePath as FP (splitPath)
import System.IO
import System.Process

import Zifter.Zift

newtype HindentBin =
    HindentBin (Path Abs File)
    deriving (Show, Eq)

hindentZift :: Zift ()
hindentZift = hindentZiftExcept []

hindentZiftExcept :: [FilePath] -> Zift ()
hindentZiftExcept ps = do
    hindentBin <- getHindent
    () <- hindentCheckAndPrintVersion hindentBin
    rd <- getRootDir
    fs <- liftIO $ snd <$> listDirRecur rd
    exclusions <- mapM (resolveFile rd) ps
    let sources =
            filter (not . (`elem` exclusions)) . filter (not . hidden) $
            filter ((== ".hs") . fileExtension) fs
    forZ_ sources $ hindentSingleSource hindentBin

getHindent :: Zift HindentBin
getHindent = do
    home <- liftIO getHomeDir
    file <- liftIO $ parseRelFile ".local/bin/hindent"
    pure $ HindentBin $ home </> file

hindentCmd :: HindentBin -> [String] -> String
hindentCmd (HindentBin ap) args = unwords $ toFilePath ap : args

hindentCheckAndPrintVersion :: HindentBin -> Zift ()
hindentCheckAndPrintVersion hb = do
    let cmd = hindentCmd hb ["--version"]
    (_, mouth, _, ph) <-
        liftIO $ createProcess ((shell cmd) {std_out = CreatePipe})
    ec <- liftIO $ waitForProcess ph
    case mouth of
        Nothing -> pure ()
        Just outh -> liftIO (hGetContents outh) >>= printZift
    case ec of
        ExitFailure c -> fail $ unwords [cmd, "failed with exit code", show c]
        ExitSuccess -> pure ()

hindentSingleSource :: HindentBin -> Path Abs File -> Zift ()
hindentSingleSource hb file = do
    let cmd =
            hindentCmd
                hb
                ["--indent-size", "4", "--line-length", "80", toFilePath file]
    let cp = shell cmd
    ec <-
        liftIO $ do
            (_, _, _, ph) <- createProcess cp
            waitForProcess ph
    case ec of
        ExitSuccess ->
            printPreprocessingDone $
            unwords
                ["Formatted Haskell source file with hindent:", toFilePath file]
        ExitFailure c -> do
            printPreprocessingError $
                unwords
                    ["Failed to format Haskell source file:", toFilePath file]
            fail $ unwords [cmd, "failed", "with exit code", show c]

hidden :: Path Abs t -> Bool
hidden = any ((Just '.' ==) . headMay) . FP.splitPath . toFilePath