module LiquidHaskell.Cabal (
    
    liquidHaskellMain
  , liquidHaskellHooks
  , liquidHaskellPostBuildHook
  ) where
import Control.Monad
import Data.List
import Data.Maybe
import Data.Monoid
import Distribution.ModuleName hiding (main)
import Distribution.PackageDescription
import Distribution.PackageDescription.Parse
import Distribution.ParseUtils
import Distribution.Simple
import Distribution.Simple.GHC
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Program
import Distribution.Simple.Program.Db
import Distribution.Simple.Program.GHC
import Distribution.Simple.Setup
import Distribution.Simple.Utils
import Distribution.Verbosity
import System.FilePath
liquidHaskellMain :: IO ()
liquidHaskellMain = defaultMainWithHooks liquidHaskellHooks
liquidHaskellHooks :: UserHooks
liquidHaskellHooks = simpleUserHooks { postBuild = liquidHaskellPostBuildHook }
liquidHaskellPostBuildHook :: Args -> BuildFlags -> PackageDescription
                           -> LocalBuildInfo -> IO ()
liquidHaskellPostBuildHook args flags pkg lbi = do
  enabled <- isFlagEnabled "liquidhaskell" lbi
  when enabled $ do
    let verbosity = fromFlag $ buildVerbosity flags
    withAllComponentsInBuildOrder pkg lbi $ \component clbi ->
      case component of
        CLib lib -> verifyComponent verbosity lbi clbi (libBuildInfo lib)
                    "library"
                      =<< findLibSources lib
        CExe exe -> verifyComponent verbosity lbi clbi (buildInfo exe)
                    ("executable " ++ exeName exe)
                      =<< findExeSources exe
        _ -> return ()
verifyComponent :: Verbosity -> LocalBuildInfo -> ComponentLocalBuildInfo
                -> BuildInfo -> String -> [FilePath] -> IO ()
verifyComponent verbosity lbi clbi bi desc sources = do
  userArgs <- getUserArgs desc bi
  let ghcFlags = makeGhcFlags verbosity lbi clbi bi
  let args = concat
        [ ("--ghc-option=" ++) <$> ghcFlags
        , ("--c-files=" ++) <$> (cSources bi)
        , userArgs
        , sources
        ]
  liquid <- requireLiquidProgram verbosity $ withPrograms lbi
  runProgram verbosity liquid args
getUserArgs :: String -> BuildInfo -> IO [ProgArg]
getUserArgs desc bi =
  case lookup "x-liquidhaskell-options" (customFieldsBI bi) of
    Nothing -> return []
    Just cmd ->
      case parseCommandArgs cmd of
        Right args -> return args
        Left err -> die $
          "failed to parse LiquidHaskell options for " ++ desc ++ ": " ++ err
makeGhcFlags :: Verbosity -> LocalBuildInfo -> ComponentLocalBuildInfo
             -> BuildInfo -> [String]
makeGhcFlags verbosity lbi clbi bi =
  renderGhcOptions (compiler lbi) $
  sanitizeGhcOptions $
  componentGhcOptions verbosity lbi bi clbi $ buildDir lbi
sanitizeGhcOptions :: GhcOptions -> GhcOptions
sanitizeGhcOptions opts = GhcOptions
  { ghcOptMode               = ghcOptMode               opts
  , ghcOptExtra              = ghcOptExtra              opts
  , ghcOptExtraDefault       = ghcOptExtraDefault       opts
  , ghcOptInputFiles         = ghcOptInputFiles         opts
  , ghcOptInputModules       = ghcOptInputModules       opts
  , ghcOptOutputFile         = ghcOptOutputFile         opts
  , ghcOptOutputDynFile      = ghcOptOutputDynFile      opts
  , ghcOptSourcePathClear    = ghcOptSourcePathClear    opts
  , ghcOptSourcePath         = ghcOptSourcePath         opts
#if MIN_VERSION_Cabal(1,22,0)
  , ghcOptPackageKey         = ghcOptPackageKey         opts
#else
  , ghcOptPackageName        = ghcOptPackageName        opts
#endif
  , ghcOptPackageDBs         = ghcOptPackageDBs         opts
  , ghcOptPackages           = ghcOptPackages           opts
  , ghcOptHideAllPackages    = ghcOptHideAllPackages    opts
  , ghcOptNoAutoLinkPackages = ghcOptNoAutoLinkPackages opts
#if MIN_VERSION_Cabal(1,22,0)
  , ghcOptSigOf              = ghcOptSigOf              opts
#endif
  , ghcOptLinkLibs           = ghcOptLinkLibs           opts
  , ghcOptLinkLibPath        = ghcOptLinkLibPath        opts
  , ghcOptLinkOptions        = ghcOptLinkOptions        opts
  , ghcOptLinkFrameworks     = ghcOptLinkFrameworks     opts
  , ghcOptNoLink             = NoFlag 
  , ghcOptLinkNoHsMain       = ghcOptLinkNoHsMain       opts
  , ghcOptCcOptions          = ghcOptCcOptions          opts
  , ghcOptCppOptions         = ghcOptCppOptions         opts
  , ghcOptCppIncludePath     = ghcOptCppIncludePath     opts
  , ghcOptCppIncludes        = ghcOptCppIncludes        opts
  , ghcOptFfiIncludes        = ghcOptFfiIncludes        opts
  , ghcOptLanguage           = ghcOptLanguage           opts
  , ghcOptExtensions         = ghcOptExtensions         opts
  , ghcOptExtensionMap       = ghcOptExtensionMap       opts
  , ghcOptOptimisation       = NoFlag 
#if MIN_VERSION_Cabal(1,22,0)
  , ghcOptDebugInfo          = ghcOptDebugInfo          opts
#endif
  , ghcOptProfilingMode      = NoFlag 
  , ghcOptSplitObjs          = ghcOptSplitObjs          opts
#if MIN_VERSION_Cabal(1,20,0)
  , ghcOptNumJobs            = NoFlag 
#endif
#if MIN_VERSION_Cabal(1,22,0)
  , ghcOptHPCDir             = NoFlag 
#endif
  , ghcOptGHCiScripts        = mempty 
  , ghcOptHiSuffix           = ghcOptHiSuffix           opts
  , ghcOptObjSuffix          = ghcOptObjSuffix          opts
  , ghcOptDynHiSuffix        = ghcOptDynHiSuffix        opts
  , ghcOptDynObjSuffix       = ghcOptDynObjSuffix       opts
  , ghcOptHiDir              = ghcOptHiDir              opts
  , ghcOptObjDir             = ghcOptObjDir             opts
  , ghcOptOutputDir          = ghcOptOutputDir          opts
  , ghcOptStubDir            = ghcOptStubDir            opts
  , ghcOptDynLinkMode        = ghcOptDynLinkMode        opts
  , ghcOptShared             = ghcOptShared             opts
  , ghcOptFPic               = ghcOptFPic               opts
  , ghcOptDylibName          = ghcOptDylibName          opts
#if MIN_VERSION_Cabal(1,22,0)
  , ghcOptRPaths             = ghcOptRPaths             opts
#endif
  , ghcOptVerbosity          = ghcOptVerbosity          opts
  , ghcOptCabal              = ghcOptCabal              opts
  }
findLibSources :: Library -> IO [FilePath]
findLibSources lib = findModuleSources (libBuildInfo lib) (exposedModules lib)
findExeSources :: Executable -> IO [FilePath]
findExeSources exe = do
  moduleSrcs <- findModuleSources (buildInfo exe) []
  mainSrc <- findFile (hsSourceDirs $ buildInfo exe) (modulePath exe)
  return (mainSrc : moduleSrcs)
findModuleSources :: BuildInfo -> [ModuleName] -> IO [FilePath]
findModuleSources bi exposed = do
  let modules = exposed ++ otherModules bi
  hsSources <- mapM (findModuleSource ["hs", "lhs"] bi) modules
  hsBootSources <- mapM (findModuleSource ["hs-boot", "lhs-boot"] bi) modules
  return $ catMaybes (hsSources ++ hsBootSources)
findModuleSource :: [String] -> BuildInfo -> ModuleName -> IO (Maybe FilePath)
findModuleSource suffixes bi mod =
  findFileWithExtension suffixes (hsSourceDirs bi) (toFilePath mod)
requireLiquidProgram :: Verbosity -> ProgramDb -> IO ConfiguredProgram
requireLiquidProgram verbosity db =
  fst <$> requireProgram verbosity liquidProgram db
liquidProgram :: Program
liquidProgram = simpleProgram "liquid"
isFlagEnabled :: String -> LocalBuildInfo -> IO Bool
isFlagEnabled name lbi = case getOverriddenFlagValue name lbi of
  Just enabled -> return enabled
  Nothing -> getDefaultFlagValue name lbi False
getOverriddenFlagValue :: String -> LocalBuildInfo -> Maybe Bool
getOverriddenFlagValue name lbi = lookup (FlagName name) overriddenFlags
  where
    overriddenFlags = configConfigurationsFlags $ configFlags lbi
getDefaultFlagValue :: String -> LocalBuildInfo -> Bool -> IO Bool
getDefaultFlagValue name lbi def = case pkgDescrFile lbi of
  Nothing -> return def
  Just cabalFile -> do
    descr <- readPackageDescription silent cabalFile
    let flag = find ((FlagName name ==) . flagName) $ genPackageFlags descr
    return $ maybe def flagDefault flag
parseCommandArgs :: String -> Either String [ProgArg]
parseCommandArgs cmd =
  case fieldSet field 0 cmd [] of
    ParseOk _ out -> Right $ concat $ map snd out
    ParseFailed err -> Left $ snd $ locatedErrorMsg err
  where
    field = optsField "x-liquidhaskell-options"
                      (OtherCompiler "LiquidHaskell")
                      id (++)