    {-# LANGUAGE CPP #-}
    module Hpack (
      hpack
    , hpackResult
    , Result(..)
    , Status(..)
    , version
    , main
    #ifdef TEST
    , hpackWithVersion
    , extractVersion
    , parseVersion
    , splitDirectory
    #endif
    ) where

    import           Prelude ()
    import           Prelude.Compat

    import           Control.Monad.Compat
    import qualified Data.ByteString as B
    import           Data.List.Compat
    import           Data.Maybe
    import qualified Data.Text as T
    import           Data.Text.Encoding (encodeUtf8)
    import           Data.Version (Version)
    import qualified Data.Version as Version
    import           System.Environment
    import           System.Exit
    import           System.IO
    import           System.FilePath
    import           System.Directory
    import           Text.ParserCombinators.ReadP

    import           Paths_hpack (version)
    import           Hpack.Options
    import           Hpack.Config
    import           Hpack.Run
    import           Hpack.Util

    programVersion :: Version -> String
    programVersion v = "hpack version " ++ Version.showVersion v

    header :: FilePath -> Version -> String
    header p v = unlines [
        "-- This file has been generated from " ++ p ++ " by " ++ programVersion v ++ "."
      , "--"
      , "-- see: https://github.com/sol/hpack"
      , ""
      ]

    main :: IO ()
    main = do
      args <- getArgs
      case parseOptions args of
        PrintVersion -> putStrLn (programVersion version)
        Help -> printHelp
        Run options -> case options of
          Options _verbose True dir -> hpackStdOut dir
          Options verbose False dir -> hpack dir verbose
        ParseError -> do
          printHelp
          exitFailure

    printHelp :: IO ()
    printHelp = do
      hPutStrLn stderr $ unlines [
          "Usage: hpack [ --silent ] [ dir ] [ - ]"
        , "       hpack --version"
        ]

    safeInit :: [a] -> [a]
    safeInit [] = []
    safeInit xs = init xs

    extractVersion :: [String] -> Maybe Version
    extractVersion = listToMaybe . mapMaybe (stripPrefix prefix >=> parseVersion . safeInit)
      where
        prefix = "-- This file has been generated from package.yaml by hpack version "

    parseVersion :: String -> Maybe Version
    parseVersion xs = case [v | (v, "") <- readP_to_S Version.parseVersion xs] of
      [v] -> Just v
      _ -> Nothing

    hpack :: Maybe FilePath -> Bool -> IO ()
    hpack = hpackWithVersion version

    hpackResult :: Maybe FilePath -> IO Result
    hpackResult = hpackWithVersionResult version

    data Result = Result {
      resultWarnings :: [String]
    , resultCabalFile :: String
    , resultStatus :: Status
    }

    data Status = Generated | AlreadyGeneratedByNewerHpack | OutputUnchanged

    hpackWithVersion :: Version -> Maybe FilePath -> Bool -> IO ()
    hpackWithVersion v p verbose = do
        r <- hpackWithVersionResult v p
        printWarnings (resultWarnings r)
        when verbose $ putStrLn $
          case resultStatus r of
            Generated -> "generated " ++ resultCabalFile r
            OutputUnchanged -> resultCabalFile r ++ " is up-to-date"
            AlreadyGeneratedByNewerHpack -> resultCabalFile r ++ " was generated with a newer version of hpack, please upgrade and try again."

    printWarnings :: [String] -> IO ()
    printWarnings warnings = do
      forM_ warnings $ \warning -> hPutStrLn stderr ("WARNING: " ++ warning)

    splitDirectory :: Maybe FilePath -> IO (Maybe FilePath, FilePath)
    splitDirectory Nothing = return (Nothing, packageConfig)
    splitDirectory (Just p) = do
      isDirectory <- doesDirectoryExist p
      return $ if isDirectory
        then (Just p, packageConfig)
        else let
          file = takeFileName p
          dir = takeDirectory p
          in (guard (p /= file) >> Just dir, if null file then packageConfig else file)

    hpackWithVersionResult :: Version -> Maybe FilePath -> IO Result
    hpackWithVersionResult v p = do
      (dir, file) <- splitDirectory p
      (warnings, cabalFile, new) <- run dir file
      old <- fmap splitHeader <$> tryReadFile cabalFile
      let oldVersion = fmap fst old >>= extractVersion
      status <-
        if (oldVersion <= Just v) then
          if (fmap snd old == Just (lines new)) then
            return OutputUnchanged
          else do
            B.writeFile cabalFile $ encodeUtf8 $ T.pack $ header file v ++ new
            return Generated
        else
          return AlreadyGeneratedByNewerHpack
      return Result
        { resultWarnings = warnings
        , resultCabalFile = cabalFile
        , resultStatus = status
        }
      where
        splitHeader :: String -> ([String], [String])
        splitHeader = fmap (dropWhile null) . span ("--" `isPrefixOf`) . lines

    hpackStdOut :: Maybe FilePath -> IO ()
    hpackStdOut p = do
      (dir, file) <- splitDirectory p
      (warnings, _cabalFile, new) <- run dir file
      B.putStr $ encodeUtf8 $ T.pack new
      printWarnings warnings