{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
module Hpack (
  hpack
, hpackResult
, Result(..)
, Status(..)
, Verbose(..)
, Force(..)
, version
, main
, mainWith
, RunOptions(..)
, defaultRunOptions
#ifdef TEST
, header
, hpackWithVersionResult
#endif
) where

import           Control.Monad
import           Data.Version (Version)
import qualified Data.Version as Version
import           System.Environment
import           System.Exit
import           System.IO (stderr)
import           Data.Aeson (Value)

import           Paths_hpack (version)
import           Hpack.Options
import           Hpack.Config
import           Hpack.Run
import           Hpack.Util
import           Hpack.Utf8 as Utf8
import           Hpack.CabalFile
import           Hpack.Yaml

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

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

main :: IO ()
main = mainWith packageConfig decodeYaml

mainWith :: FilePath -> (FilePath -> IO (Either String Value)) -> IO ()
mainWith configFile decode = do
  result <- getArgs >>= parseOptions configFile
  case result of
    PrintVersion -> putStrLn (programVersion version)
    PrintNumericVersion -> putStrLn (Version.showVersion version)
    Help -> printHelp
    Run options -> case options of
      Options _verbose _force True dir file -> hpackStdOut (RunOptions dir file decode)
      Options verbose force False dir file -> hpack (RunOptions dir file decode) verbose force
    ParseError -> do
      printHelp
      exitFailure

printHelp :: IO ()
printHelp = do
  Utf8.hPutStrLn stderr $ unlines [
      "Usage: hpack [ --silent ] [ --force | -f ] [ PATH ] [ - ]"
    , "       hpack --version"
    , "       hpack --help"
    ]

hpack :: RunOptions -> Verbose -> Force -> IO ()
hpack = hpackWithVersion version

hpackResult :: RunOptions -> Force -> IO Result
hpackResult = hpackWithVersionResult version

data Result = Result {
  resultWarnings :: [String]
, resultCabalFile :: String
, resultStatus :: Status
} deriving (Eq, Show)

data Status =
    Generated
  | ExistingCabalFileWasModifiedManually
  | AlreadyGeneratedByNewerHpack
  | OutputUnchanged
  deriving (Eq, Show)

hpackWithVersion :: Version -> RunOptions -> Verbose -> Force -> IO ()
hpackWithVersion v options verbose force = do
    r <- hpackWithVersionResult v options force
    printWarnings (resultWarnings r)
    when (verbose == 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."
        ExistingCabalFileWasModifiedManually -> resultCabalFile r ++ " was modified manually, please use --force to overwrite."

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

mkStatus :: [String] -> Version -> CabalFile -> Status
mkStatus new v (CabalFile mOldVersion mHash old) = case (mOldVersion, mHash) of
  (Nothing, _) -> ExistingCabalFileWasModifiedManually
  (Just oldVersion, _) | oldVersion < makeVersion [0, 20, 0] -> Generated
  (_, Nothing) -> ExistingCabalFileWasModifiedManually
  (Just oldVersion, Just hash)
    | v < oldVersion -> AlreadyGeneratedByNewerHpack
    | sha256 (unlines old) /= hash -> ExistingCabalFileWasModifiedManually
    | old == new -> OutputUnchanged
    | otherwise -> Generated

hpackWithVersionResult :: Version -> RunOptions -> Force -> IO Result
hpackWithVersionResult v options@RunOptions{..} force = do
  (warnings, cabalFile, new) <- run options
  oldCabalFile <- readCabalFile cabalFile
  let
    status = case force of
      Force -> Generated
      NoForce -> maybe Generated (mkStatus (lines new) v) oldCabalFile
  case status of
    Generated -> do
      let hash = sha256 new
      Utf8.writeFile cabalFile (header runOptionsConfigFile v hash ++ new)
    _ -> return ()
  return Result {
      resultWarnings = warnings
    , resultCabalFile = cabalFile
    , resultStatus = status
    }

hpackStdOut :: RunOptions -> IO ()
hpackStdOut options = do
  (warnings, _cabalFile, new) <- run options
  Utf8.putStr new
  printWarnings warnings