module TH.RelativePaths where
import           Control.Exception (IOException, catch)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import           Data.List (find)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.IO as LT
import           Language.Haskell.TH (Q, Loc(loc_filename), location, runIO, reportWarning)
import           Language.Haskell.TH.Syntax (addDependentFile)
import           System.Directory (getDirectoryContents, getCurrentDirectory, setCurrentDirectory, canonicalizePath)
import           System.FilePath
qReadFileBS :: FilePath -> Q BS.ByteString
qReadFileBS fp = do
    fp' <- pathRelativeToCabalPackage fp
    addDependentFile fp'
    runIO $ BS.readFile fp'
qReadFileLBS :: FilePath -> Q LBS.ByteString
qReadFileLBS fp = do
    fp' <- pathRelativeToCabalPackage fp
    addDependentFile fp'
    runIO $ LBS.readFile fp'
qReadFileText :: FilePath -> Q T.Text
qReadFileText fp = do
    fp' <- pathRelativeToCabalPackage fp
    addDependentFile fp'
    runIO $ T.readFile fp'
qReadFileLazyText :: FilePath -> Q LT.Text
qReadFileLazyText fp = do
    fp' <- pathRelativeToCabalPackage fp
    addDependentFile fp'
    runIO $ LT.readFile fp'
qReadFileString :: FilePath -> Q String
qReadFileString fp = do
    fp' <- pathRelativeToCabalPackage fp
    addDependentFile fp'
    runIO $ readFile fp'
withCabalPackageWorkDir :: Q a -> Q a
withCabalPackageWorkDir f = do
    cwd' <- pathRelativeToCabalPackage "."
    cwd <- runIO $ getCurrentDirectory
    runIO $ setCurrentDirectory cwd'
    x <- f
    runIO $ setCurrentDirectory cwd
    return x
pathRelativeToCabalPackage :: FilePath -> Q FilePath
pathRelativeToCabalPackage fp = do
    loc <- location
    parent <-
        if loc_filename loc == "<interactive>"
            then runIO getCurrentDirectory
            else do
                mcanonical <- runIO $ fmap Just (canonicalizePath (loc_filename loc))
                   `catch` \(_err :: IOException) -> return Nothing
                mcabalFile <- runIO $ maybe (return Nothing) findCabalFile mcanonical
                case mcabalFile of
                    Just cabalFile -> return (takeDirectory cabalFile)
                    Nothing -> do
                        reportWarning "Failed to find cabal file, in order to resolve relative paths in TH.  Using current working directory instead."
                        runIO getCurrentDirectory
    return (parent </> fp)
findCabalFile :: FilePath -> IO (Maybe FilePath)
findCabalFile dir = do
    let parent = takeDirectory dir
    contents <- getDirectoryContents parent
    case find (\fp -> takeExtension fp == ".cabal") contents of
        Nothing
            | parent == dir -> return Nothing
            | otherwise -> findCabalFile parent
        Just fp -> return (Just (parent </> fp))