content: {-# LANGUAGE FlexibleInstances #-}
  import Control.Exception (SomeException, try)
  import Control.Monad
  import Data.Maybe
  import Data.List (isPrefixOf, (\\), intercalate, stripPrefix)
  import Data.Map (Map)
  import qualified Data.Map as Map
  import Data.Monoid
  import Data.Char
  import Distribution.Simple
  import Distribution.Simple.PreProcess
  import Distribution.Simple.Program
  import Distribution.Simple.Setup hiding (Flag)
  import Distribution.Simple.LocalBuildInfo
  import Distribution.PackageDescription
  import Distribution.Version
  import System.Environment
  import System.SetEnv
  import Distribution.System
  
  -- define these selectively in C files (where _not_ using HsFFI.h),
  -- rather than universally in the ccOptions, because HsFFI.h currently defines them
  -- without checking they're already defined and so causes warnings.
  uncheckedHsFFIDefines = ["__STDC_LIMIT_MACROS"]
  
  llvmVersion = Version [3,5] []
  
  llvmConfigNames = [
    "llvm-config-" ++ (intercalate "." . map show . versionBranch $ llvmVersion),
    "llvm-config"
   ]
  
  findJustBy :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
  findJustBy f (x:xs) = do
    x' <- f x
    case x' of
      Nothing -> findJustBy f xs
      j -> return j
  findJustBy _ [] = return Nothing
  
  class ProgramSearch a where
    programSearch :: (String -> a) -> a
  
  -- this instance is used before Cabal-1.18.0, when programFindLocation took one argument
  instance Monad m => ProgramSearch (v -> m (Maybe b)) where
    programSearch checkName = \v -> findJustBy (\n -> checkName n v) llvmConfigNames
  
  -- this instance is used for and after Cabal-1.18.0, when programFindLocation took two arguments
  instance Monad m => ProgramSearch (v -> p -> m (Maybe b)) where
    programSearch checkName = \v p -> findJustBy (\n -> checkName n v p) llvmConfigNames
  
  class OldHookable hook where
    preHookOld :: (PackageDescription -> LocalBuildInfo -> UserHooks -> TestFlags -> IO ()) -> hook -> hook
  
  -- this instance is used before Cabal-1.22.0.0, when testHook took four arguments
  instance OldHookable (PackageDescription -> LocalBuildInfo -> UserHooks -> TestFlags -> IO ()) where
    preHookOld f hook = \packageDescription localBuildInfo userHooks testFlags -> do
      f packageDescription localBuildInfo userHooks testFlags
      hook packageDescription localBuildInfo userHooks testFlags
  
  -- this instance is used for and after Cabal-1.22.0.0, when testHook took four five arguments
  instance OldHookable (Args -> PackageDescription -> LocalBuildInfo -> UserHooks -> TestFlags -> IO ()) where
    preHookOld f hook = \args packageDescription localBuildInfo userHooks testFlags -> do
      f packageDescription localBuildInfo userHooks testFlags
      hook args packageDescription localBuildInfo userHooks testFlags
  
  llvmProgram :: Program
  llvmProgram = (simpleProgram "llvm-config") {
    programFindLocation = programSearch (programFindLocation . simpleProgram),
    programFindVersion = 
      let
        stripSuffix suf str = let r = reverse in liftM r (stripPrefix (r suf) (r str))
        svnToTag v = maybe v (++"-svn") (stripSuffix "svn" v)
        trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse
      in
        \v p -> findProgramVersion "--version" (svnToTag . trim) v p
   }
  
  getLLVMConfig :: ConfigFlags -> IO (String -> IO String)
  getLLVMConfig configFlags = do
    let verbosity = fromFlag $ configVerbosity configFlags
    (program, _, _) <- requireProgramVersion verbosity llvmProgram
                       (withinVersion llvmVersion)
                       (configPrograms configFlags)
    return $ getProgramOutput verbosity program . return
  
  addToLdLibraryPath :: String -> IO ()
  addToLdLibraryPath path = do
    let (ldLibraryPathVar, ldLibraryPathSep) =
          case buildOS of
            OSX -> ("DYLD_LIBRARY_PATH",":")
            _ -> ("LD_LIBRARY_PATH",":")
    v <- try $ getEnv ldLibraryPathVar :: IO (Either SomeException String)
    setEnv ldLibraryPathVar (path ++ either (const "") (ldLibraryPathSep ++) v)
  
  addLLVMToLdLibraryPath :: ConfigFlags -> IO ()
  addLLVMToLdLibraryPath configFlags = do
    llvmConfig <- getLLVMConfig configFlags
    [libDir] <- liftM lines $ llvmConfig "--libdir"
    addToLdLibraryPath libDir
                             
  main = do
    let origUserHooks = simpleUserHooks
                    
    defaultMainWithHooks origUserHooks {
      hookedPrograms = [ llvmProgram ],
  
      confHook = \(genericPackageDescription, hookedBuildInfo) configFlags -> do
        llvmConfig <- getLLVMConfig configFlags
  
        llvmCppFlags <- do
          l <- llvmConfig "--cppflags"
          return $ (filter ("-D" `isPrefixOf`) $ words l) \\ (map ("-D"++) uncheckedHsFFIDefines)
        includeDirs <- liftM lines $ llvmConfig "--includedir"
        libDirs@[libDir] <- liftM lines $ llvmConfig "--libdir"
        [llvmVersion] <- liftM lines $ llvmConfig "--version"
        let sharedLib = case llvmVersion of
                          "3.2" -> "LLVM-3.2svn"
                          x -> "LLVM-" ++ x
        staticLibs <- liftM (map (fromJust . stripPrefix "-l") . words) $ llvmConfig "--libs"
        systemLibs <- liftM (map (fromJust . stripPrefix "-l") . words) $ llvmConfig "--system-libs"
  
        let genericPackageDescription' = genericPackageDescription {
              condLibrary = do
                libraryCondTree <- condLibrary genericPackageDescription
                return libraryCondTree {
                  condTreeData = condTreeData libraryCondTree <> mempty {
                      libBuildInfo = mempty { ccOptions = llvmCppFlags }
                    },
                  condTreeComponents = condTreeComponents libraryCondTree ++ [
                    (
                      Var (Flag (FlagName "shared-llvm")),
                      CondNode (mempty { libBuildInfo = mempty { extraLibs = [sharedLib] ++ systemLibs } }) [] [],
                      Just (CondNode (mempty { libBuildInfo = mempty { extraLibs = staticLibs ++ systemLibs } }) [] [])
                    )
                  ] 
                }
             }
            configFlags' = configFlags {
              configExtraLibDirs = libDirs ++ configExtraLibDirs configFlags,
              configExtraIncludeDirs = includeDirs ++ configExtraIncludeDirs configFlags
             }
        addLLVMToLdLibraryPath configFlags'
        confHook simpleUserHooks (genericPackageDescription', hookedBuildInfo) configFlags',
  
      hookedPreProcessors =
        let origHookedPreprocessors = hookedPreProcessors origUserHooks
            newHsc buildInfo localBuildInfo =
              maybe ppHsc2hs id (lookup "hsc" origHookedPreprocessors) buildInfo' localBuildInfo
                where buildInfo' = buildInfo { ccOptions = ccOptions buildInfo \\ ["-std=c++11"] }
        in [("hsc", newHsc)] ++ origHookedPreprocessors,
  
      buildHook = \packageDescription localBuildInfo userHooks buildFlags -> do
            addLLVMToLdLibraryPath (configFlags localBuildInfo)
            buildHook origUserHooks packageDescription localBuildInfo userHooks buildFlags,
  
      testHook = preHookOld (\_ localBuildInfo _ _ -> addLLVMToLdLibraryPath (configFlags localBuildInfo))
                 (testHook origUserHooks)
     }
  