{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}

module Refact.Run (refactMain, runPipe) where

import Control.Monad
import Data.List hiding (find)
import Data.Maybe
import Data.Version
import Debug.Trace
#if MIN_VERSION_ghc(9,12,0)
import Language.Haskell.GHC.ExactPrint.Utils (showAst)
#else
import Language.Haskell.GHC.ExactPrint.ExactPrint (showAst)
#endif
import qualified GHC.Paths
import Options.Applicative
import Paths_apply_refact
import Refact.Apply (parseExtensions)
import Refact.Fixity
import Refact.Internal
  ( Verbosity (..),
    apply,
    onError,
    parseModuleWithArgs,
  )
import Refact.Options (Options (..), optionsWithHelp)
import Refact.Types hiding (SrcSpan)
import qualified Refact.Types as R
import System.Exit
import System.FilePath.Find
import System.IO.Extra
import qualified System.PosixCompat.Files as F

refactMain :: IO ()
refactMain = do
  o@Options {..} <- execParser optionsWithHelp
  when optionsVersion (putStr ("v" ++ showVersion version) >> exitSuccess)
  unless (isJust optionsTarget || isJust optionsRefactFile) . die $
    "Must specify either the target file, or the refact file, or both.\n"
      ++ "If either the target file or the refact file is not specified, "
      ++ "it will be read from stdin.\n"
      ++ "To show usage, run 'refactor -h'."
  case optionsTarget of
    Nothing ->
      withTempFile $ \fp -> do
        getContents >>= writeFileUTF8 fp
        runPipe o fp
    Just target -> do
      targetStatus <- F.getFileStatus target
      if F.isDirectory targetStatus
        then findHsFiles target >>= mapM_ (runPipe o)
        else runPipe o target

-- Given base directory finds all haskell source files
findHsFiles :: FilePath -> IO [FilePath]
findHsFiles = find filterDirectory filterFilename

filterDirectory :: FindClause Bool
filterDirectory =
  p <$> fileName
  where
    p x
      | "." `isPrefixOf` x = False
      | otherwise = True

filterFilename :: FindClause Bool
filterFilename = do
  ext <- extension
  fname <- fileName
  pure (ext == ".hs" && p fname)
  where
    p x
      | "Setup.hs" `isInfixOf` x = False
      | otherwise = True

runPipe :: Options -> FilePath -> IO ()
runPipe Options {..} file = do
  let verb = optionsVerbosity
  rawhints <- getHints optionsRefactFile
  when (verb == Loud) (traceM "Got raw hints")
  let inp :: [(String, [Refactoring R.SrcSpan])] = read rawhints
      n = length inp
  when (verb == Loud) (traceM $ "Read " ++ show n ++ " hints")

  output <-
    if null inp
      then readFileUTF8' file
      else do
        when (verb == Loud) (traceM "Parsing module")
        let (enabledExts, disabledExts, invalidExts) = parseExtensions optionsLanguage
        unless (null invalidExts) . when (verb >= Normal) . putStrLn $
          "Invalid extensions: " ++ intercalate ", " invalidExts
        m <-
          either (onError "runPipe") applyFixities
            =<< parseModuleWithArgs GHC.Paths.libdir (enabledExts, disabledExts) file
        when optionsDebug (putStrLn (showAst m))
        apply optionsPos optionsStep inp (Just file) verb m

  if optionsInplace && isJust optionsTarget
    then writeFileUTF8 file output
    else case optionsOutput of
      Nothing -> putStr output
      Just f -> do
        when (verb == Loud) (traceM $ "Writing result to " ++ f)
        writeFileUTF8 f output

getHints :: Maybe FilePath -> IO String
getHints (Just hintFile) = readFileUTF8' hintFile
getHints Nothing = getContents
