content: module GhcMod.FileMapping
      ( loadMappedFile
      , loadMappedFileSource
      , unloadMappedFile
      , mapFile
      , fileModSummaryWithMapping
      ) where
  
  import GhcMod.Types
  import GhcMod.Monad.Types
  import GhcMod.Gap
  import GhcMod.HomeModuleGraph
  import GhcMod.Utils
  
  import System.IO
  import System.FilePath
  import System.Directory
  
  import Control.Monad.Trans.Maybe
  import GHC
  import Control.Monad
  
  {- | maps 'FilePath', given as first argument to take source from
  'FilePath' given as second argument. Works exactly the same as
  first form of `--map-file` CLI option.
  
  \'from\' can be either full path, or path relative to project root.
  \'to\' has to be either relative to project root, or full path (preferred)
  -}
  loadMappedFile :: IOish m
                 => FilePath -- ^ \'from\', file that will be mapped
                 -> FilePath -- ^ \'to\', file to take source from
                 -> GhcModT m ()
  loadMappedFile from to = loadMappedFile' from to False
  
  {- |
  maps 'FilePath', given as first argument to have source as given
  by second argument.
  
  \'from\' may or may not exist, and should be either full path,
  or relative to project root.
  -}
  loadMappedFileSource :: IOish m
                       => FilePath -- ^ \'from\', file that will be mapped
                       -> String -- ^ \'src\', source
                       -> GhcModT m ()
  loadMappedFileSource from src = do
    tmpdir <- cradleTempDir `fmap` cradle
    enc <- liftIO . mkTextEncoding . optEncoding =<< options
    to <- liftIO $ do
      (fn, h) <- openTempFile tmpdir (takeFileName from)
      hSetEncoding h enc
      hPutStr h src
      hClose h
      return fn
    loadMappedFile' from to True
  
  loadMappedFile' :: IOish m => FilePath -> FilePath -> Bool -> GhcModT m ()
  loadMappedFile' from to isTemp = do
    cfn <- getCanonicalFileNameSafe from
    unloadMappedFile' cfn
    crdl <- cradle
    let to' = makeRelative (cradleRootDir crdl) to
    addMMappedFile cfn (FileMapping to' isTemp)
  
  mapFile :: (IOish m, GmState m) => HscEnv -> Target -> m Target
  mapFile _ (Target tid@(TargetFile filePath _) taoc _) = do
    mapping <- lookupMMappedFile filePath
    return $ mkMappedTarget (Just filePath) tid taoc mapping
  mapFile env (Target tid@(TargetModule moduleName) taoc _) = do
    (fp, mapping) <- do
      filePath <- fmap (fmap mpPath) (liftIO $ findModulePath env moduleName)
      mmf <- runMaybeT $ MaybeT (return filePath) >>= MaybeT . lookupMMappedFile
      return (filePath, mmf)
    return $ mkMappedTarget fp tid taoc mapping
  
  mkMappedTarget :: Maybe FilePath -> TargetId -> Bool -> Maybe FileMapping -> Target
  mkMappedTarget _ _ taoc (Just to) =
    mkTarget (TargetFile (fmPath to) Nothing) taoc Nothing
  mkMappedTarget _ tid taoc _ =
    mkTarget tid taoc Nothing
  
  {-|
  unloads previously mapped file \'file\', so that it's no longer mapped,
  and removes any temporary files created when file was
  mapped.
  
  \'file\' should be either full path, or relative to project root.
  -}
  unloadMappedFile :: IOish m
                   => FilePath -- ^ \'file\', file to unmap
                   -> GhcModT m ()
  unloadMappedFile = getCanonicalFileNameSafe >=> unloadMappedFile'
  
  unloadMappedFile' :: IOish m => FilePath -> GhcModT m ()
  unloadMappedFile' cfn = void $ runMaybeT $ do
    fm <- MaybeT $ lookupMMappedFile cfn
    liftIO $ when (fmTemp fm) $ removeFile (fmPath fm)
    delMMappedFile cfn
  
  fileModSummaryWithMapping :: (IOish m, GmState m, GhcMonad m, GmEnv m) =>
                              FilePath -> m ModSummary
  fileModSummaryWithMapping fn =
    withMappedFile fn $ \fn' -> fileModSummary fn'
  