import Control.Exception (throw)
import Control.Monad (forM_, when)
import Data.Maybe (isNothing)
import Distribution.PackageDescription
import Distribution.Simple
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..), localPkgDescr)
import Distribution.Simple.PreProcess
import Distribution.Simple.Program.Find
  ( defaultProgramSearchPath
  , findProgramOnSearchPath
  )
import Distribution.Simple.Setup
import Distribution.Simple.Utils
  ( IODataMode(IODataModeBinary)
  , maybeExit
  , rawSystemStdInOut
  )
import Distribution.Verbosity (Verbosity)
import qualified Distribution.Verbosity as Verbosity
import System.Directory
  ( XdgDirectory(..)
  , copyFile
  , createDirectory
  , createDirectoryIfMissing
  , doesDirectoryExist
  , doesFileExist
  , getCurrentDirectory
  , getDirectoryContents
  , getHomeDirectory
  , getXdgDirectory
  )
import System.Environment
import System.FilePath ((</>))
import Text.Regex
import Text.Regex.Base

main :: IO ()
main = defaultMainWithHooks hooks
  where
    hooks =
      simpleUserHooks
        { preConf =
            \_ flags -> do
              prepDeps (fromFlag $ configVerbosity flags)
              pure emptyHookedBuildInfo
        --, confHook = \a flags -> confHook simpleUserHooks a flags >>= rsAddDirs
        }

execCargo :: Verbosity -> String -> [String] -> IO ()
execCargo verbosity command args = do
  cargoPath <-
    findProgramOnSearchPath Verbosity.silent defaultProgramSearchPath "cargo"
  dir <- getCurrentDirectory
  let cargoExec =
        case cargoPath of
          Just (p, _) -> p
          Nothing -> "cargo"
      cargoArgs = command : args
      workingDir = Just (dir </> rsFolder)
      thirdComponent (_, _, c) = c
  maybeExit . fmap thirdComponent $
    rawSystemStdInOut
      verbosity
      cargoExec
      cargoArgs
      workingDir
      Nothing
      Nothing
      IODataModeBinary

rsMake :: Verbosity -> IO ()
rsMake verbosity = do
  execCargo verbosity "cbuild" []

prepDeps :: Verbosity -> IO ()
prepDeps verbosity = do
  ldPath <- lookupEnv "LD_LIBRARY_PATH"
  pkgPath <- lookupEnv "PKG_CONFIG_PATH"
  if maybe False (matchTest (mkRegex ".*zcash-haskell.*")) ldPath &&
     maybe False (matchTest (mkRegex ".*zcash-haskell.*")) pkgPath
    then do
      execCargo verbosity "cbuild" []
      localData <- getXdgDirectory XdgData "zcash-haskell"
      createDirectoryIfMissing True localData
      dir <- getCurrentDirectory
      let rustLibDir =
            dir </> rsFolder </> "target/x86_64-unknown-linux-gnu/debug"
      copyDir rustLibDir localData
    else throw $
         userError "Paths not set correctly, please run the 'configure' script."

rsFolder :: FilePath
rsFolder = "zcash-haskell/librustzcash-wrapper"

rsAddDirs :: LocalBuildInfo -> IO LocalBuildInfo
rsAddDirs lbi' = do
  dir <- getCurrentDirectory
  let rustIncludeDir =
        dir </> rsFolder </> "target/x86_64-unknown-linux-gnu/debug"
      rustLibDir = dir </> rsFolder </> "target/x86_64-unknown-linux-gnu/debug"
      updateLbi lbi = lbi {localPkgDescr = updatePkgDescr (localPkgDescr lbi)}
      updatePkgDescr pkgDescr =
        pkgDescr {library = updateLib <$> library pkgDescr}
      updateLib lib = lib {libBuildInfo = updateLibBi (libBuildInfo lib)}
      updateLibBi libBuild =
        libBuild
          { includeDirs = rustIncludeDir : includeDirs libBuild
          , extraLibDirs = rustLibDir : extraLibDirs libBuild
          }
  pure $ updateLbi lbi'

copyDir :: FilePath -> FilePath -> IO ()
copyDir src dst = do
  whenM (not <$> doesDirectoryExist src) $
    throw (userError "source does not exist")
  --whenM (doesFileOrDirectoryExist dst) $
    --throw (userError "destination already exists")
  createDirectoryIfMissing True dst
  content <- getDirectoryContents src
  let xs = filter (`notElem` [".", ".."]) content
  forM_ xs $ \name -> do
    let srcPath = src </> name
    let dstPath = dst </> name
    isDirectory <- doesDirectoryExist srcPath
    if isDirectory
      then copyDir srcPath dstPath
      else copyFile srcPath dstPath
  where
    doesFileOrDirectoryExist x = orM [doesDirectoryExist x, doesFileExist x]
    orM xs = or <$> sequence xs
    whenM s r = s >>= flip when r