module CabalGild.Unstable.Type.Context where

import qualified CabalGild.Unstable.Class.MonadHandle as MonadHandle
import qualified CabalGild.Unstable.Class.MonadLog as MonadLog
import qualified CabalGild.Unstable.Class.MonadWalk as MonadWalk
import qualified CabalGild.Unstable.Exception.MoreThanOneCabalFileFound as MoreThanOneCabalFileFound
import qualified CabalGild.Unstable.Exception.NoCabalFileFound as NoCabalFileFound
import qualified CabalGild.Unstable.Exception.SpecifiedOutputWithCheckMode as SpecifiedOutputWithCheckMode
import qualified CabalGild.Unstable.Exception.SpecifiedStdinWithFileInput as SpecifiedStdinWithFileInput
import qualified CabalGild.Unstable.Type.Config as Config
import qualified CabalGild.Unstable.Type.Flag as Flag
import qualified CabalGild.Unstable.Type.Input as Input
import qualified CabalGild.Unstable.Type.Leniency as Leniency
import qualified CabalGild.Unstable.Type.Mode as Mode
import qualified CabalGild.Unstable.Type.Optional as Optional
import qualified CabalGild.Unstable.Type.Output as Output
import qualified Control.Monad as Monad
import qualified Control.Monad.Catch as Exception
import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Version as Version
import qualified Paths_cabal_gild as This
import qualified System.Console.GetOpt as GetOpt
import qualified System.Exit as Exit
import qualified System.IO as IO

-- | Represents the context necessary to run the program. This is essentially a
-- simplified 'Config.Config'.
data Context = Context
  { Context -> Leniency
crlf :: Leniency.Leniency,
    Context -> Input
input :: Input.Input,
    Context -> Mode
mode :: Mode.Mode,
    Context -> Output
output :: Output.Output,
    Context -> FilePath
stdin :: FilePath
  }
  deriving (Context -> Context -> Bool
(Context -> Context -> Bool)
-> (Context -> Context -> Bool) -> Eq Context
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Context -> Context -> Bool
== :: Context -> Context -> Bool
$c/= :: Context -> Context -> Bool
/= :: Context -> Context -> Bool
Eq, Int -> Context -> ShowS
[Context] -> ShowS
Context -> FilePath
(Int -> Context -> ShowS)
-> (Context -> FilePath) -> ([Context] -> ShowS) -> Show Context
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Context -> ShowS
showsPrec :: Int -> Context -> ShowS
$cshow :: Context -> FilePath
show :: Context -> FilePath
$cshowList :: [Context] -> ShowS
showList :: [Context] -> ShowS
Show)

-- | Creates a 'Context' from a 'Config.Config'. If the help or version was
-- requested, then this will throw an 'Exit.ExitSuccess'. Otherwise this makes
-- sure the config is valid before returning the context.
fromConfig ::
  ( MonadHandle.MonadHandle m,
    MonadLog.MonadLog m,
    Exception.MonadThrow m,
    MonadWalk.MonadWalk m
  ) =>
  Config.Config ->
  m Context
fromConfig :: forall (m :: * -> *).
(MonadHandle m, MonadLog m, MonadThrow m, MonadWalk m) =>
Config -> m Context
fromConfig Config
config = do
  let version :: FilePath
version = Version -> FilePath
Version.showVersion Version
This.version

  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
Monad.when (Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
Maybe.fromMaybe Bool
False (Maybe Bool -> Bool)
-> (Optional Bool -> Maybe Bool) -> Optional Bool -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Optional Bool -> Maybe Bool
forall a. Optional a -> Maybe a
Optional.toMaybe (Optional Bool -> Bool) -> Optional Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Config -> Optional Bool
Config.help Config
config) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
    let header :: FilePath
header =
          [FilePath] -> FilePath
unlines
            [ FilePath
"cabal-gild version " FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> FilePath
version,
              FilePath
"",
              FilePath
"<https://github.com/tfausak/cabal-gild>"
            ]
    FilePath -> m ()
forall (m :: * -> *). MonadLog m => FilePath -> m ()
MonadLog.logLn
      (FilePath -> m ()) -> ShowS -> FilePath -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
List.dropWhileEnd Char -> Bool
Char.isSpace
      (FilePath -> m ()) -> FilePath -> m ()
forall a b. (a -> b) -> a -> b
$ FilePath -> [OptDescr Flag] -> FilePath
forall a. FilePath -> [OptDescr a] -> FilePath
GetOpt.usageInfo FilePath
header [OptDescr Flag]
Flag.options
    ExitCode -> m ()
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
Exception.throwM ExitCode
Exit.ExitSuccess

  Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
Monad.when (Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
Maybe.fromMaybe Bool
False (Maybe Bool -> Bool)
-> (Optional Bool -> Maybe Bool) -> Optional Bool -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Optional Bool -> Maybe Bool
forall a. Optional a -> Maybe a
Optional.toMaybe (Optional Bool -> Bool) -> Optional Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Config -> Optional Bool
Config.version Config
config) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
    FilePath -> m ()
forall (m :: * -> *). MonadLog m => FilePath -> m ()
MonadLog.logLn FilePath
version
    ExitCode -> m ()
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
Exception.throwM ExitCode
Exit.ExitSuccess

  case (Config -> Optional Input
Config.input Config
config, Config -> Optional FilePath
Config.stdin Config
config) of
    (Optional.Specific (Input.File FilePath
_), Optional.Specific FilePath
_) ->
      SpecifiedStdinWithFileInput -> m ()
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
Exception.throwM SpecifiedStdinWithFileInput
SpecifiedStdinWithFileInput.SpecifiedStdinWithFileInput
    (Optional Input, Optional FilePath)
_ -> () -> m ()
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

  case (Config -> Optional Mode
Config.mode Config
config, Config -> Optional Output
Config.output Config
config) of
    (Optional.Specific Mode
Mode.Check, Optional.Specific Output
_) ->
      SpecifiedOutputWithCheckMode -> m ()
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
Exception.throwM SpecifiedOutputWithCheckMode
SpecifiedOutputWithCheckMode.SpecifiedOutputWithCheckMode
    (Optional Mode, Optional Output)
_ -> () -> m ()
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

  let preInput :: Input
preInput = Input -> Maybe Input -> Input
forall a. a -> Maybe a -> a
Maybe.fromMaybe Input
Input.Stdin (Maybe Input -> Input)
-> (Optional Input -> Maybe Input) -> Optional Input -> Input
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Optional Input -> Maybe Input
forall a. Optional a -> Maybe a
Optional.toMaybe (Optional Input -> Input) -> Optional Input -> Input
forall a b. (a -> b) -> a -> b
$ Config -> Optional Input
Config.input Config
config
      filePath :: FilePath
filePath = case Input
preInput of
        Input
Input.Stdin -> FilePath
"."
        Input.File FilePath
f -> FilePath
f
      preOutput :: Output
preOutput = Output -> Maybe Output -> Output
forall a. a -> Maybe a -> a
Maybe.fromMaybe Output
Output.Stdout (Maybe Output -> Output)
-> (Optional Output -> Maybe Output) -> Optional Output -> Output
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Optional Output -> Maybe Output
forall a. Optional a -> Maybe a
Optional.toMaybe (Optional Output -> Output) -> Optional Output -> Output
forall a b. (a -> b) -> a -> b
$ Config -> Optional Output
Config.output Config
config
  (theInput, theOutput) <- do
    isTerm <- Handle -> m Bool
forall (m :: * -> *). MonadHandle m => Handle -> m Bool
MonadHandle.isTerminalDevice Handle
IO.stdin
    if preInput == Input.Stdin && preOutput == Output.Stdout && isTerm
      then do
        cabalFiles <- MonadWalk.walk "." ["*.cabal"] []
        case cabalFiles of
          [FilePath
fp] -> (Input, Output) -> m (Input, Output)
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FilePath -> Input
Input.File FilePath
fp, FilePath -> Output
Output.File FilePath
fp)
          [] -> NoCabalFileFound -> m (Input, Output)
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
Exception.throwM NoCabalFileFound
NoCabalFileFound.NoCabalFileFound
          [FilePath]
_ -> MoreThanOneCabalFileFound -> m (Input, Output)
forall e a. (HasCallStack, Exception e) => e -> m a
forall (m :: * -> *) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
Exception.throwM MoreThanOneCabalFileFound
MoreThanOneCabalFileFound.MoreThanOneCabalFileFound
      else pure (preInput, preOutput)
  pure
    Context
      { crlf = Maybe.fromMaybe Leniency.Lenient . Optional.toMaybe $ Config.crlf config,
        input = theInput,
        mode = Maybe.fromMaybe Mode.Format . Optional.toMaybe $ Config.mode config,
        output = theOutput,
        stdin = Maybe.fromMaybe filePath . Optional.toMaybe $ Config.stdin config
      }