{-# LANGUAGE CPP #-}
-- | It is recommended to write
--
-- import Prelude hiding (writeFile)
--
-- when importing this module.
module System.IO.Cautious
  ( writeFile
  , writeFileWithBackup
  ) where

import Prelude hiding (writeFile)

import System.Directory (renameFile)
import System.FilePath (splitFileName)
import System.IO (openTempFile)
#ifdef _POSIX
import Control.Monad (when)
import Data.Function (fix)
import Data.List (genericDrop)
import System.Posix.IO (closeFd, FdOption (SynchronousWrites), fdWrite, handleToFd, setFdOption)
import System.Posix.Types (Fd)

-- | Don't bother to split into two writes if the string to write is shorter than this
splitLimit :: Int
splitLimit = 65536

-- | Write the entire contents of a string to a file descriptor. Assumes blocking mode.
writeAll :: Fd -> String -> IO ()
writeAll fd = fix $ \me s -> when (not $ null s) $ do
    count <- fdWrite fd s
    me $ genericDrop count s
#else
import System.IO (hPutStr, hClose)
#endif

writeFile :: FilePath -> String -> IO ()
writeFile = writeFileWithBackup $ return ()

-- | Backs up the old version of the file with "backup". "backup" must not fail if there is no
-- old version of the file.
writeFileWithBackup :: IO () -> FilePath -> String -> IO ()
writeFileWithBackup backup fp text = do
    (tempFP, handle) <- uncurry openTempFile $ splitFileName fp
#ifdef _POSIX
    fd <- handleToFd handle
    let writeSync = (setFdOption fd SynchronousWrites True >>) . writeAll fd
    if null $ drop splitLimit text
      then writeSync text
      else writeAll fd (init text) >> writeSync [last text]
    closeFd fd
#else
    hPutStr handle text
    hClose handle
#endif
    backup
    renameFile tempFP fp