module Distribution.Simple.UHC (
    configure, getInstalledPackages,
    buildLib, buildExe, installLib, registerPackage
  ) where
import Control.Monad
import Data.List
import Distribution.Compat.ReadP
import Distribution.InstalledPackageInfo
import Distribution.Package
import Distribution.PackageDescription
import Distribution.Simple.BuildPaths
import Distribution.Simple.Compiler as C
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.PackageIndex
import Distribution.Simple.Program
import Distribution.Simple.Utils
import Distribution.Text
import Distribution.Verbosity
import Distribution.Version
import Language.Haskell.Extension
import System.Directory
import System.FilePath
import Distribution.System ( Platform )
configure :: Verbosity -> Maybe FilePath -> Maybe FilePath
          -> ProgramConfiguration -> IO (Compiler, Maybe Platform, ProgramConfiguration)
configure verbosity hcPath _hcPkgPath conf = do
  (_uhcProg, uhcVersion, conf') <-
    requireProgramVersion verbosity uhcProgram
    (orLaterVersion (Version [1,0,2] []))
    (userMaybeSpecifyPath "uhc" hcPath conf)
  let comp = Compiler {
               compilerId         =  CompilerId UHC uhcVersion,
               compilerLanguages  =  uhcLanguages,
               compilerExtensions =  uhcLanguageExtensions
             }
      compPlatform = Nothing
  return (comp, compPlatform, conf')
uhcLanguages :: [(Language, C.Flag)]
uhcLanguages = [(Haskell98, "")]
uhcLanguageExtensions :: [(Extension, C.Flag)]
uhcLanguageExtensions =
    let doFlag (f, (enable, disable)) = [(EnableExtension  f, enable),
                                         (DisableExtension f, disable)]
        alwaysOn = ("", "")
    in concatMap doFlag
    [(CPP,                          ("--cpp", "")),
     (PolymorphicComponents,        alwaysOn),
     (ExistentialQuantification,    alwaysOn),
     (ForeignFunctionInterface,     alwaysOn),
     (UndecidableInstances,         alwaysOn),
     (MultiParamTypeClasses,        alwaysOn),
     (Rank2Types,                   alwaysOn),
     (PatternSignatures,            alwaysOn),
     (EmptyDataDecls,               alwaysOn),
     (ImplicitPrelude,              ("", "--no-prelude")),
     (TypeOperators,                alwaysOn),
     (OverlappingInstances,         alwaysOn),
     (FlexibleInstances,            alwaysOn)]
getInstalledPackages :: Verbosity -> Compiler -> PackageDBStack -> ProgramConfiguration
                     -> IO PackageIndex
getInstalledPackages verbosity comp packagedbs conf = do
  let compilerid = compilerId comp
  systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram conf ["--meta-pkgdir-system"]
  userPkgDir   <- getUserPackageDir
  let pkgDirs    = nub (concatMap (packageDbPaths userPkgDir systemPkgDir) packagedbs)
  
  
  pkgs <- liftM (map addBuiltinVersions . concat) .
          mapM (\ d -> getDirectoryContents d >>= filterM (isPkgDir (display compilerid) d)) .
          concatMap lines $ pkgDirs
  
  let iPkgs =
        map mkInstalledPackageInfo $
        concatMap parsePackage $
        pkgs
  
  return (fromList iPkgs)
getUserPackageDir :: IO FilePath
getUserPackageDir =
  do
    homeDir <- getHomeDirectory
    return $ homeDir </> ".cabal" </> "lib"  
packageDbPaths :: FilePath -> FilePath -> PackageDB -> [FilePath]
packageDbPaths user system db =
  case db of
    GlobalPackageDB         ->  [ system ]
    UserPackageDB           ->  [ user ]
    SpecificPackageDB path  ->  [ path ]
addBuiltinVersions :: String -> String
addBuiltinVersions xs      = xs
installedPkgConfig :: String
installedPkgConfig = "installed-pkg-config"
isPkgDir :: String -> String -> String -> IO Bool
isPkgDir _ _   ('.' : _)  = return False  
isPkgDir c dir xs         = do
                              let candidate = dir </> uhcPackageDir xs c
                              
                              doesFileExist (candidate </> installedPkgConfig)
parsePackage :: String -> [PackageId]
parsePackage x = map fst (filter (\ (_,y) -> null y) (readP_to_S parse x))
mkInstalledPackageInfo :: PackageId -> InstalledPackageInfo
mkInstalledPackageInfo p = emptyInstalledPackageInfo
  { installedPackageId = InstalledPackageId (display p),
    sourcePackageId    = p }
buildLib :: Verbosity -> PackageDescription -> LocalBuildInfo
                      -> Library            -> ComponentLocalBuildInfo -> IO ()
buildLib verbosity pkg_descr lbi lib clbi = do
  systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram (withPrograms lbi) ["--meta-pkgdir-system"]
  userPkgDir   <- getUserPackageDir
  let runUhcProg = rawSystemProgramConf verbosity uhcProgram (withPrograms lbi)
  let uhcArgs =    
                   ["--pkg-build=" ++ display (packageId pkg_descr)]
                   
                ++ constructUHCCmdLine userPkgDir systemPkgDir
                                       lbi (libBuildInfo lib) clbi
                                       (buildDir lbi) verbosity
                   
                   
                   
                ++ map (map (\ c -> if c == '.' then pathSeparator else c))
                       (map display (libModules lib))
  runUhcProg uhcArgs
  
  return ()
buildExe :: Verbosity -> PackageDescription -> LocalBuildInfo
                      -> Executable         -> ComponentLocalBuildInfo -> IO ()
buildExe verbosity _pkg_descr lbi exe clbi = do
  systemPkgDir <- rawSystemProgramStdoutConf verbosity uhcProgram (withPrograms lbi) ["--meta-pkgdir-system"]
  userPkgDir   <- getUserPackageDir
  let runUhcProg = rawSystemProgramConf verbosity uhcProgram (withPrograms lbi)
  let uhcArgs =    
                   constructUHCCmdLine userPkgDir systemPkgDir
                                       lbi (buildInfo exe) clbi
                                       (buildDir lbi) verbosity
                   
                ++ ["--output", buildDir lbi </> exeName exe]
                   
                ++ [modulePath exe]
  runUhcProg uhcArgs
constructUHCCmdLine :: FilePath -> FilePath
                    -> LocalBuildInfo -> BuildInfo -> ComponentLocalBuildInfo
                    -> FilePath -> Verbosity -> [String]
constructUHCCmdLine user system lbi bi clbi odir verbosity =
     
     (if      verbosity >= deafening then ["-v4"]
      else if verbosity >= normal    then []
      else                                ["-v0"])
  ++ hcOptions UHC bi
     
  ++ languageToFlags   (compiler lbi) (defaultLanguage bi)
  ++ extensionsToFlags (compiler lbi) (usedExtensions bi)
     
  ++ ["--hide-all-packages"]
  ++ uhcPackageDbOptions user system (withPackageDB lbi)
  ++ ["--package=uhcbase"]
  ++ ["--package=" ++ display (pkgName pkgid) | (_, pkgid) <- componentPackageDeps clbi ]
     
  ++ ["-i" ++ odir]
  ++ ["-i" ++ l | l <- nub (hsSourceDirs bi)]
  ++ ["-i" ++ autogenModulesDir lbi]
     
  ++ ["--odir=" ++ odir]
     
  ++ (case withOptimization lbi of
        NoOptimisation       ->  ["-O0"]
        NormalOptimisation   ->  ["-O1"]
        MaximumOptimisation  ->  ["-O2"])
uhcPackageDbOptions :: FilePath -> FilePath -> PackageDBStack -> [String]
uhcPackageDbOptions user system db = map (\ x -> "--pkg-searchpath=" ++ x)
                                         (concatMap (packageDbPaths user system) db)
installLib :: Verbosity -> LocalBuildInfo
           -> FilePath -> FilePath -> FilePath
           -> PackageDescription -> Library -> IO ()
installLib verbosity _lbi targetDir _dynlibTargetDir builtDir pkg _library = do
    
    
    installDirectoryContents verbosity (builtDir </> display (packageId pkg)) targetDir
uhcTarget, uhcTargetVariant :: String
uhcTarget        = "bc"
uhcTargetVariant = "plain"
uhcPackageDir    :: String -> String -> FilePath
uhcPackageSubDir ::           String -> FilePath
uhcPackageDir    pkgid compilerid = pkgid </> uhcPackageSubDir compilerid
uhcPackageSubDir       compilerid = compilerid </> uhcTarget </> uhcTargetVariant
registerPackage
  :: Verbosity
  -> InstalledPackageInfo
  -> PackageDescription
  -> LocalBuildInfo
  -> Bool
  -> PackageDBStack
  -> IO ()
registerPackage verbosity installedPkgInfo pkg lbi inplace _packageDbs = do
    let installDirs = absoluteInstallDirs pkg lbi NoCopyDest
        pkgdir  | inplace   = buildDir lbi       </> uhcPackageDir    (display pkgid) (display compilerid)
                | otherwise = libdir installDirs </> uhcPackageSubDir                 (display compilerid)
    createDirectoryIfMissingVerbose verbosity True pkgdir
    writeUTF8File (pkgdir </> installedPkgConfig)
                  (showInstalledPackageInfo installedPkgInfo)
  where
    pkgid      = packageId pkg
    compilerid = compilerId (compiler lbi)