content: module Pier.Build.ConfiguredPackage
      ( ConfiguredPackage(..)
      , getConfiguredPackage
      , targetDepNames
      , exeDepNames
      ) where
  
  import Data.List (nub)
  import Development.Shake
  import Distribution.Package
  import Distribution.PackageDescription
  import Distribution.Text (display)
  import Distribution.Version (mkVersion)
  
  import qualified Data.HashMap.Strict as HM
  import qualified Data.Set as Set
  
  import Pier.Build.Config
  import Pier.Build.Custom
  import Pier.Build.Package
  import Pier.Build.Stackage (Flags, InstalledGhc)
  import Pier.Core.Artifact
  
  data ConfiguredPackage = ConfiguredPackage
      { confdDesc :: PackageDescription
      , confdSourceDir :: Artifact
      , confdDataFiles :: Maybe Artifact
      , confdExtraSrcFiles  :: [FilePath] -- relative to source dir
      }
  
  instance Package ConfiguredPackage where
      packageId = packageId . confdDesc
  
  -- TODO: merge with Resolved
  -- TODO: don't copy everything if configuring a local package?  Or at least
  -- treat deps less coarsely?
  getConfiguredPackage
      :: PackageName -> Action (Either PackageId ConfiguredPackage)
  getConfiguredPackage p = do
      conf <- askConfig
      case resolvePackage conf p of
          Builtin pid -> return $ Left pid
          Hackage pid flags -> do
              dir <- getPackageSourceDir pid
              Right . addHappyAlexSourceDirs <$> getConfigured conf flags dir
          Local dir _ -> Right <$> getConfigured conf HM.empty dir
    where
      getConfigured :: Config -> Flags -> Artifact -> Action ConfiguredPackage
      getConfigured conf flags dir = do
          (desc, dir') <- configurePackage (plan conf) flags dir
          datas <- collectDataFiles (configGhc conf) desc dir'
          extras <- fmap (nub . concat)
                              . mapM (matchArtifactGlob dir')
                              . extraSrcFiles
                              $ desc
          return $ ConfiguredPackage desc dir' datas extras
  
  targetDepNames :: BuildInfo -> [PackageName]
  targetDepNames bi = [n | Dependency n _ <- targetBuildDepends bi]
  
  -- | In older versions of Cabal, executables could use packages that were only
  -- explicitly depended on in the library or in other executables.  Some existing
  -- packages still assume this behavior.
  exeDepNames :: PackageDescription -> BuildInfo -> [PackageName]
  exeDepNames desc bi
      | specVersion desc >= mkVersion [1,8] = targetDepNames bi
      | otherwise = maybe [] (const [packageName desc]) (library desc)
                      ++ allDependencies desc
  
  allDependencies :: PackageDescription -> [PackageName]
  allDependencies desc = let
      allBis = [libBuildInfo l | Just l <- [library desc]]
                      ++ map buildInfo (executables desc)
                      ++ map testBuildInfo (testSuites desc)
                      ++ map benchmarkBuildInfo (benchmarks desc)
     in Set.toList . Set.fromList . concatMap targetDepNames $ allBis
  
  addHappyAlexSourceDirs :: ConfiguredPackage -> ConfiguredPackage
  addHappyAlexSourceDirs confd
      | packageName (confdDesc confd) `elem` map mkPackageName ["happy", "alex"]
          = confd { confdDesc = addDistSourceDirs $ confdDesc confd }
      | otherwise = confd
  
  collectDataFiles
      :: InstalledGhc -> PackageDescription -> Artifact -> Action (Maybe Artifact)
  collectDataFiles ghc desc dir = case display (packageName desc) of
      "happy" -> Just <$> collectHappyDataFiles ghc dir
      "alex" -> Just <$> collectAlexDataFiles ghc dir
      _ -> collectPlainDataFiles desc dir
  
  -- TODO: should we filter out packages without data-files?
  -- Or short-cut it somewhere else?
  collectPlainDataFiles
      :: PackageDescription -> Artifact -> Action (Maybe Artifact)
  collectPlainDataFiles desc dir = do
      let inDir = dir /> dataDir desc
      if null (dataFiles desc)
          then return Nothing
          else Just <$> do
              files <- concat <$> mapM (matchArtifactGlob inDir) (dataFiles desc)
              groupFiles inDir . map (\x -> (x,x)) $ files
  