content: {-# LANGUAGE CPP #-}
  import Data.Maybe(isJust, fromMaybe, maybeToList)
  import Distribution.Simple.Compiler
  import Distribution.Simple.LocalBuildInfo
  import Distribution.Types.LocalBuildInfo
  import Distribution.Types.TargetInfo
  import Distribution.PackageDescription
  import Distribution.Simple
  import Distribution.System
  import Distribution.Simple.Setup
  import Distribution.Simple.Utils
  import Distribution.Verbosity
  import Distribution.Text ( display )
  import Control.Monad
  import Data.List
  import Control.Applicative((<$>))
  import Distribution.Simple.Configure(findDistPrefOrDefault)
  import Distribution.Simple.Register ( generateRegistrationInfo, registerPackage )
  import qualified Distribution.Simple.Register as Register
  import Distribution.Simple.InstallDirs (fromPathTemplate)
  import System.IO.Unsafe (unsafePerformIO)
  import System.IO.Error
  import System.Directory(getCurrentDirectory, copyFile, createDirectoryIfMissing, listDirectory, withCurrentDirectory, doesDirectoryExist, makeAbsolute, doesFileExist)
  import System.FilePath ( (</>), (<.>), takeDirectory)
  import Distribution.PackageDescription as PD
  import Distribution.InstalledPackageInfo (ldOptions, extraGHCiLibraries, showInstalledPackageInfo, libraryDynDirs, libraryDirs,InstalledPackageInfo)
  import System.Environment (getEnv, setEnv)
  
  ----------------------------------------
  -- compatibility between Cabal 1 and Cabal 2
  
  -- "If you have a custom-setup stanza, you should be able to use the MIN_VERSION_Cabal macro in your setup script."
  
  -- cabal >=2.0.0.2
  _FlagName :: String -> FlagName
  #if defined(MIN_VERSION_Cabal) && MIN_VERSION_Cabal(2,0,0)
  _FlagName = mkFlagName
  #else
  _FlagName = FlagName
  #endif
  
  -- cabal >=2.0.1.1
  _registerPackage :: Verbosity -> LocalBuildInfo -> PackageDBStack -> InstalledPackageInfo -> IO ()
  #if defined(MIN_VERSION_Cabal) && MIN_VERSION_Cabal(2,0,1)
  _registerPackage verbosity lbi packageDbs installedPkgInfo
    = registerPackage verbosity (compiler lbi) (withPrograms lbi) packageDbs installedPkgInfo Register.defaultRegisterOptions
  #else
  _registerPackage verbosity lbi packageDbs installedPkgInfo
    = registerPackage verbosity (compiler lbi) (withPrograms lbi) False {- multiinstance -} packageDbs installedPkgInfo
  #endif
  
  ----------------------------------------
  
  main :: IO ()
  main = defaultMainWithHooks autoconfUserHooks {
    preConf = myPreConf,
    postConf = myPostConf,
    buildHook = myBuildHook,
    cleanHook = myCleanHook,
    copyHook = copyCBindingsAndBundledExecutables,
    regHook = registerHook
    }
  
  fltkSource = "fltk-master"
  temporaryWorkaround = "temporary-workaround"
  
  runMake :: FilePath -> [String] -> IO ()
  runMake p args =
    withCurrentDirectory p
      (case buildOS of
      Windows -> rawSystemExit normal "make" args
      os | os `elem` [FreeBSD, OpenBSD, NetBSD, DragonFly]
        -> rawSystemExit normal "gmake" args
      _ -> rawSystemExit normal "make" args)
  
  buildFltk :: IO FilePath -> Bool -> IO ()
  buildFltk prefix openGL = do
    rawSystemExit normal "rm" ["-rf", fltkSource]
    rawSystemExit normal "unzip" [fltkSource ++ ".zip"]
    projectRoot <- getCurrentDirectory
    prefix' <- prefix
    let fltkDir = projectRoot </>  fltkSource
        fltkFlags = (if (openGL) then ["--enable-gl"] else ["--disable-gl"]) ++
                    [
                     "--enable-shared",
                     "--enable-localjpeg",
                     "--enable-localzlib",
                     "--enable-localpng",
                     "--enable-xft",
                     ("--prefix=" ++ prefix')
                    ]
    withCurrentDirectory
      fltkDir
      (
        let make = runMake fltkDir [] >> runMake fltkDir ["install"]
        in
        case buildOS of
          Windows -> do
            rawSystemExit normal "sh" ([(fltkDir </> "autogen.sh")] ++ fltkFlags)
            rawSystemExit normal "sh" ([(fltkDir </> "configure")] ++ fltkFlags)
            make
          _ -> do
            updateEnv "PATH" (projectRoot </> temporaryWorkaround)
            rawSystemExit normal (fltkDir </> "autogen.sh") fltkFlags
            rawSystemExit normal (fltkDir </> "configure") fltkFlags
            make
      )
  
  myPreConf :: Args -> ConfigFlags -> IO HookedBuildInfo
  myPreConf args flags = do
     if (bundledBuild flags)
      then do
        putStrLn "Building bundled FLTK"
        prefix <- bundlePrefix flags ""
        fltkBuilt <- doesDirectoryExist prefix
        if (not fltkBuilt)
          then buildFltk (case buildOS of { Windows -> cygpath "-u" prefix; _ -> return prefix;}) (openGLSupport flags)
          else putStrLn "FLTK already built."
        case buildOS of
          Windows -> updateEnv "PATH" (windowsFriendlyPaths (prefix </> "bin"))
          _ -> updateEnv "PATH" (prefix </> "bin")
      else return ()
     putStrLn "Running autoconf ..."
     case buildOS of
       Windows -> rawSystemExit normal "sh" ["autoconf"]
       _ -> rawSystemExit normal "autoconf" []
     fltkPathAdded <-
       if (bundledBuild flags)
       then do
         prefix <- bundlePrefix flags ""
         return flags{ configProgramPaths = [("fltk-config", prefix </> "bin" </> "fltk-config")] ++ (configProgramPaths flags)}
       else return flags
     preConf autoconfUserHooks args fltkPathAdded
  
  myPostConf :: Args -> ConfigFlags -> PackageDescription -> LocalBuildInfo -> IO ()
  myPostConf args flags pd lbi = do
    (major, minor, patch) <- getFltkVersion
    let confFlags = if (openGLSupport flags)
                    then (flags{ configConfigureArgs = (configConfigureArgs flags) ++ ["--enable-gl"]})
                    else flags
        confFlagsWithVersion =
          confFlags
          {
            configConfigureArgs = (configConfigureArgs confFlags) ++ [
                     ("--with-fltk-major-version=" ++ major),
                     ("--with-fltk-minor-version=" ++ minor),
                     ("--with-fltk-patch-version=" ++ patch)
                   ]
          }
        baseDir lbi' =
          case (cabalFilePath lbi') of
            Nothing -> ""
            Just p -> takeDirectory p
        basedir = baseDir lbi
        csrc = basedir </> "c-src"
    distBuild <- fmap (\d -> d </> "build") (findDistPrefOrDefault (configDistPref flags))
    srcFiles <- listDirectory csrc
    createDirectoryIfMissing False (distBuild </> "c-src")
    mapM_ (\f -> copyFile (csrc </> f) (distBuild </> "c-src" </> f)) srcFiles
    postConf autoconfUserHooks args confFlagsWithVersion pd lbi
  
  fltkclib = "fltkc"
  
  getFltkVersion :: IO (String, String, String)
  getFltkVersion = do
    versionString <- (case buildOS of
                        Windows -> rawSystemStdout normal "sh" ["fltk-config", "--version"]
                        _ -> rawSystemStdout normal "fltk-config" ["--version"])
    return (fltkVersion versionString)
  
  fltkVersion :: String -> (String,String,String)
  fltkVersion version =
    let (major, minorVersion) = break ((==) '.') (unlines (lines version))
        (minor, patchVersion) = break ((==) '.') (drop 1 minorVersion)
        patch = drop 1 patchVersion
    in (major, minor, patch)
  
  flagIsSet :: PD.FlagName -> ConfigFlags -> Bool
  flagIsSet flag configFlags = maybe False id (lookupFlagAssignment flag (configConfigurationsFlags configFlags))
  
  
  updateEnv :: String -> String -> IO ()
  updateEnv env value = do
    old <- tryIOError (getEnv env)
    setEnv env ((either (const value)
                        (\old' -> value ++
                                 (case buildOS of
                                    Windows -> ";"
                                    _ -> ":") ++
                                 old'
                        )
                        old))
  
  
  replaceAllInfixes :: String -> String -> String -> String
  replaceAllInfixes needle subString haystack =
    concat
      (unfoldr (\str -> if (null str)
                        then Nothing
                        else if (needle `isPrefixOf` str)
                             then Just (subString, drop (length needle) str)
                             else Just ([head str], tail str))
               haystack)
  
  bundledBuild flags = flagIsSet (_FlagName "bundled") flags
  openGLSupport flags = flagIsSet (_FlagName "opengl") flags
  
  bundlePrefix flags dir =
    let (Distribution.Simple.Setup.Flag prefixTemplate) = libdir (configInstallDirs flags)
    in
    makeAbsolute ((fromPathTemplate prefixTemplate) </> "fltk-bundled" </> dir)
  
  cygpath o p =
    let removeTrailingNewline = head . lines  in
    removeTrailingNewline<$>(rawSystemStdout normal "cygpath" [o,  p])
  
  windowsFriendlyPaths s =
    if (isPrefixOf "/" s)
    then unsafePerformIO (cygpath "-m" s)
    else s
  
  getApiVersion :: IO Int
  getApiVersion = do
    (major, minor, patch) <- getFltkVersion
    return ((((read major) :: Int) * 10000) +
              (((read minor) :: Int) * 100) +
              ((read patch) :: Int))
  
  addApiVersion :: PackageDescription -> Int -> PackageDescription
  addApiVersion pkg_descr apiVersion =
    let fixedLib =
          do
            l <- library pkg_descr
            let cpp = cppOptions (libBuildInfo l)
                -- have to add to ccOptions as well otherwise c2hs won't see it.
                cc = ccOptions (libBuildInfo l)
                fltkApiFlag = "-DFLTK_API_VERSION=" ++ (show apiVersion)
            return (l {
                        libBuildInfo =
                          (libBuildInfo l) {
                             cppOptions = cpp ++ [fltkApiFlag],
                             ccOptions = cc ++ [fltkApiFlag]
                          }
                     })
    in
    pkg_descr{ library = fixedLib }
  
  getFltkcdir :: LocalBuildInfo -> FilePath
  getFltkcdir lbi = (buildDir lbi) </> fltkclib
  
  myBuildHook pkg_descr local_bld_info user_hooks bld_flags = do
       let confFlags = configFlags local_bld_info
           addFltkcDir :: FilePath -> PackageDescription -> PackageDescription
           addFltkcDir p pkg_descr =
             pkg_descr {
                library = fmap (\l' -> l' {
                                         libBuildInfo =
                                            (libBuildInfo l') {
                                               extraLibDirs = (extraLibDirs (libBuildInfo l')) ++ [p]
                                            }
                                       }
                               )
                               (library pkg_descr)
             }
       if (bundledBuild confFlags)
       then bundlePrefix confFlags "bin" >>=
            case buildOS of
              Windows -> updateEnv "PATH" . windowsFriendlyPaths
              _ -> updateEnv "PATH"
       else return ()
       let compileC = putStrLn "==Compiling C bindings==" >> runMake (buildDir local_bld_info) []
       let fltkcdir = (buildDir local_bld_info) </> "c-lib"
       cdirexists <- doesDirectoryExist fltkcdir
       if cdirexists
         then
         do
          clibraries <- listDirectory fltkcdir
          when (null $ filter (Data.List.isInfixOf "fltkc") clibraries) compileC
         else compileC
       apiVersion <- getApiVersion
       let apiVersionAddedPkgDescription = addApiVersion pkg_descr apiVersion
       case buildOS of
         Windows ->
           let rewritePaths :: PackageDescription -> IO PackageDescription
               rewritePaths pkg_descr =
                 let lib = library pkg_descr
                     ld_incdirs :: Maybe ([String], [String])
                     ld_incdirs = fmap (\lib' -> (PD.ldOptions (libBuildInfo lib'), includeDirs (libBuildInfo lib')))  lib
                     stdCppGccPthreadPaths ghcPath =
                        [
                          ((takeDirectory . takeDirectory) ghcPath) </> "mingw" </> "lib" </> "gcc" </> "x86_64-w64-mingw32" </> "5.2.0"
                        , ((takeDirectory . takeDirectory) ghcPath) </> "mingw" </> "x86_64-w64-mingw32" </> "lib"
                        ]
                 in
                 do
                   ghcPath <- rawSystemStdout normal "sh" ["-c", "which ghc"]
                   cppGccPthreadPaths <- mapM (cygpath "-w") (stdCppGccPthreadPaths ghcPath)
                   bundledInclude <- if (bundledBuild confFlags)
                                     then fmap (\p -> [p]) ((bundlePrefix confFlags) "include")
                                     else return []
                   bundledLib <- if (bundledBuild confFlags)
                                 then fmap (\p -> [p]) ((bundlePrefix confFlags) "lib")
                                 else return []
                   let fixedLib = maybe Nothing
                                       (\(ld, incDirs) ->
                                            fmap (\l' -> l' {
                                                     libBuildInfo =
                                                       (libBuildInfo l') {
                                                          PD.ldOptions = fmap windowsFriendlyPaths ld,
                                                          includeDirs = fmap windowsFriendlyPaths (incDirs ++ bundledInclude ++ [(buildDir local_bld_info) </> "c-src"]),
                                                          extraLibDirs = cppGccPthreadPaths ++ (extraLibDirs (libBuildInfo l')) ++ bundledLib,
                                                          extraLibs = ["stdc++", "gcc", "pthread"]
                                                       }
                                                  })
                                                  lib
                                       )
                                       ld_incdirs
                   return (pkg_descr {library = fixedLib})
           in do
             fixedPkgDescr <- rewritePaths apiVersionAddedPkgDescription >>= return . (addFltkcDir fltkcdir)
             buildHook autoconfUserHooks fixedPkgDescr local_bld_info user_hooks bld_flags
         Linux -> do
           updateEnv "C_INCLUDE_PATH" ((buildDir local_bld_info) </> "c-src")
           updateEnv "LIBRARY_PATH" fltkcdir
           buildHook autoconfUserHooks apiVersionAddedPkgDescription local_bld_info user_hooks bld_flags
         _ -> do
           updateEnv "C_INCLUDE_PATH" ((buildDir local_bld_info) </> "c-src")
           updateEnv "DYLD_LIBRARY_PATH" fltkcdir
           updateEnv "LIBRARY_PATH" fltkcdir 
           buildHook autoconfUserHooks apiVersionAddedPkgDescription local_bld_info user_hooks bld_flags
  
  copyCBindingsAndBundledExecutables :: PackageDescription -> LocalBuildInfo -> UserHooks -> CopyFlags -> IO ()
  copyCBindingsAndBundledExecutables pkg_descr lbi uhs flags = do
      (copyHook autoconfUserHooks) pkg_descr lbi uhs flags
      let installDirs = absoluteInstallDirs pkg_descr lbi
                        . fromFlag . copyDest
                        $ flags
          libPref = libdir installDirs
      let p = (buildDir lbi) </> "c-lib"
      rawSystemExit (fromFlag $ copyVerbosity flags) "cp"
          [p </> "libfltkc.a" , libPref]
      case buildOS of
       OSX -> do
         updateEnv "DYLD_LIBRARY_PATH" (takeDirectory libPref)
         rawSystemExit
           (fromFlag $ copyVerbosity flags)
            "cp"
            [
              p </> "libfltkc-dyn.dylib"
            , libPref
            ]
       _ -> do
         updateEnv "LIBRARY_PATH" (takeDirectory libPref)
         rawSystemExit
           (fromFlag $ copyVerbosity flags) "cp"
           [
             p </> "libfltkc-dyn.so"
           , libPref
           ]
      if (bundledBuild (configFlags lbi))
      then do
        executableDir <- (bundlePrefix (configFlags lbi)) "bin"
        let binPref = bindir installDirs
        fluidExists <- doesFileExist (binPref </> "fluid")
        fltkConfigExists <- doesFileExist (binPref </> "fltk-config")
        if (not fluidExists)
        then rawSystemExit
                 (fromFlag $ copyVerbosity flags) "cp"
                 [(executableDir </> "fluid"), binPref]
        else return ()
        if (not fltkConfigExists)
        then rawSystemExit
                 (fromFlag $ copyVerbosity flags) "cp"
                 [(executableDir </> "fltk-config"), binPref]
        else return ()
      else return ()
  
  myCleanHook pd x uh cf = do
    case buildOS of
     os | os `elem` [FreeBSD, OpenBSD, NetBSD, DragonFly]
       -> rawSystemExit normal "gmake" ["clean"]
     _ -> rawSystemExit normal "make" ["clean"]
    cleanHook autoconfUserHooks pd x uh cf
  
  -- Based on code in "Gtk2HsSetup.hs" from "gtk" package
  registerHook pkg_descr localbuildinfo _ flags =
      if hasLibs pkg_descr
      then register pkg_descr localbuildinfo flags
      else setupMessage verbosity
             "Package contains no library to register:" (packageId pkg_descr)
    where verbosity = fromFlag (regVerbosity flags)
  
  register :: PackageDescription -> LocalBuildInfo -> RegisterFlags -> IO ()
  register pkg@PackageDescription { library = Just lib } lbi regFlags = do
    -- Libraries can have more than one target now due to Backpack, but we don't
    -- use it so we only register the library with a single target.
    case componentNameTargets' pkg lbi (CLibName (libName lib)) of
      [targetInfo] -> do
        let clbi = targetCLBI targetInfo
        installedPkgInfoRaw' <- generateRegistrationInfo verbosity pkg lib lbi clbi inplace False distPref packageDb
        let installedPkgInfoRaw =
             case buildOS of
               Windows -> installedPkgInfoRaw' { Distribution.InstalledPackageInfo.ldOptions = fmap windowsFriendlyPaths (Distribution.InstalledPackageInfo.ldOptions installedPkgInfoRaw') }
               _ -> installedPkgInfoRaw'
        let installedPkgInfo = installedPkgInfoRaw {
                                    libraryDynDirs = (libraryDynDirs installedPkgInfoRaw) ++ (libraryDirs installedPkgInfoRaw),
                                    -- this is what this whole register code is all about
                                    extraGHCiLibraries =
                                      case buildOS of
                                        Windows -> ["libfltkc-dyn"]
                                        _ -> ["fltkc-dyn"]
                                    }
  
         -- Three different modes:
        case () of
         _ | modeGenerateRegFile   -> writeRegistrationFile installedPkgInfo
           | modeGenerateRegScript -> dieNoVerbosity "Generate Reg Script not supported"
           | otherwise             ->
              _registerPackage verbosity lbi packageDbs installedPkgInfo
      _ -> pure ()
    where
      modeGenerateRegFile = isJust (flagToMaybe (regGenPkgConf regFlags))
      regFile             = fromMaybe (display (packageId pkg) <.> "conf")
                                      (fromFlag (regGenPkgConf regFlags))
      modeGenerateRegScript = fromFlag (regGenScript regFlags)
      inplace   = fromFlag (regInPlace regFlags)
      packageDbs = nub $ withPackageDB lbi
                      ++ maybeToList (flagToMaybe  (regPackageDB regFlags))
      packageDb = registrationPackageDB packageDbs
      distPref  = fromFlag (regDistPref regFlags)
      verbosity = fromFlag (regVerbosity regFlags)
  
      writeRegistrationFile installedPkgInfo = do
        notice verbosity ("Creating package registration file: " ++ regFile)
        writeUTF8File regFile (showInstalledPackageInfo installedPkgInfo)
  