{-# LANGUAGE Unsafe #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

-- | Main entry point to hindent. 
--
-- hindent
module Main where

import           Control. Applicative
import           Control. Exception
import qualified Data. ByteString as S
import qualified Data. ByteString. Builder as S
import qualified Data. ByteString. Lazy. Char8 as L8
import           Data. Maybe
import           Data. Text (Text)
import qualified Data. Text as T
import           Data. Version (showVersion)
import qualified Data. Yaml as Y
import           Descriptive
import           Descriptive. Options
import           Foreign. C.Error
import           GHC.IO.Exception
import           HIndent
import           HIndent. Types
import           Language. Haskell. Exts hiding (Style, style)
import           Path
import qualified Path. Find as Path
import qualified Path. IO as Path
import           Paths_hindent (version)
import qualified System. Directory as IO
import           System. Environment
import qualified System. IO as IO
import           Text. Read

-- | Main entry point. 
main :: IO ()
main = do
  args <- getArgs
  config <- getConfig
  case consume (options config) (map T.pack args) of
    Succeeded (style, exts, mfilepath) ->
      case mfilepath of
        Just filepath -> do
          text <- S.readFile filepath
          tmpDir <- IO.getTemporaryDirectory
          (fp, h) <- IO.openTempFile tmpDir "hindent.hs"
          case reformat style (Just exts) text of
            Left e -> error (filepath ++ ": " ++ e)
            Right out -> do
              L8.hPutStr h (S.toLazyByteString out)
              IO.hFlush h
              IO.hClose h
              let exdev e =
                    if ioe_errno e == Just ((\(Errno a) -> a) eXDEV)
                      then IO.copyFile fp filepath >> IO.removeFile fp
                      else throw e
              IO.renameFile fp filepath `catch` exdev
        Nothing ->
          L8.interact
            (either error S.toLazyByteString .  reformat style (Just exts) .  L8.toStrict)
    Failed (Wrap (Stopped Version) _) ->
      putStrLn ("hindent " ++ showVersion version)
    Failed (Wrap (Stopped Help) _) -> putStrLn (help defaultConfig)
    _ -> error (help defaultConfig)

-- | Read config from a config file, or return 'defaultConfig'. 
getConfig :: IO Config
getConfig = do
  cur <- Path.getCurrentDir
  homeDir <- Path.getHomeDir
  mfile <-
    Path.findFileUp cur ((== ".hindent.yaml") .  toFilePath .  filename) (Just homeDir)
  case mfile of
    Nothing -> return defaultConfig
    Just file -> do
      result <- Y.decodeFileEither (toFilePath file)
      case result of
        Left e -> error (show e)
        Right config -> return config

-- | Help text. 
help :: Config -> String
help config =
  "hindent " ++
  T.unpack (textDescription (describe (options config) [])) ++
  "\nVersion " ++
  showVersion version ++
  "\n" ++
  "Default --indent-size is 2.  Specify --indent-size 4 if you prefer that.\n" ++
  "-X to pass extensions e.g. -XMagicHash etc.\n" ++
  "The --style option is now ignored, but preserved for backwards-compatibility.\n" ++
  "Johan Tibell is the default and only style." 

-- | Options that stop the argument parser. 
data Stoppers
  = Version
  | Help
   deriving (Show)

-- | Program options. 
options
  :: Monad m
  => Config -> Consumer [Text] (Option Stoppers) m (Config, [Extension], Maybe FilePath)
options config = ver *> ((,,) <$> style <*> exts <*> file)
  where
    ver =
      stop (flag "version" "Print the version" Version) *>
      stop (flag "help" "Show help" Help)
    style =
      makeStyle <$>
      fmap
        (const config)
        (optional
           (constant "--style" "Style to print with" () *> anyString "STYLE")) <*>
      lineLen <*>
      indentSpaces <*>
      trailingNewline
    exts = fmap getExtensions (many (prefix "X" "Language extension"))
    indentSpaces =
      fmap
        (>>= (readMaybe .  T.unpack))
        (optional
           (arg "indent-size" "Indentation size in spaces, default: 4" <|>
            arg "tab-size" "Same as --indent-size, for compatibility"))
    lineLen =
      fmap
        (>>= (readMaybe .  T.unpack))
        (optional (arg "line-length" "Desired length of lines"))
    trailingNewline =
      optional
        (constant "--no-force-newline" "Don't force a trailing newline" False)
    makeStyle s mlen tabs trailing =
      s
      { configMaxColumns = fromMaybe (configMaxColumns s) mlen
      , configIndentSpaces = fromMaybe (configIndentSpaces s) tabs
      , configTrailingNewline = fromMaybe (configTrailingNewline s) trailing
      }
    file = fmap (fmap T.unpack) (optional (anyString "[<filename>]"))
