{-# LANGUAGE OverloadedStrings, CPP #-}

-- | Opening and reading a either normal or gzipped file in an efficient way -
-- either using strict 'ByteString' or mmap

module Bio.PDB.IO.OpenAnyFile(readFile, writeFile)

where

import Prelude hiding   (readFile, writeFile)
import System.Directory (doesFileExist, getPermissions, Permissions(..))
import System.IO.Error  (userError, IOError)
import System.IO        (withFile, IOMode(..))
import Control.Monad    (void)
-- if we have zlib:
import qualified Codec.Compression.GZip as GZip
import qualified Data.ByteString.Lazy   as BSL
import qualified Control.Exception      as Exc

-- if we have bzlib
--import qualified Codec.Compression.BZip as BZip

-- if we have MMap:
#ifdef HAVE_MMAP
import System.IO.MMap   (mmapFileByteString)
#endif

-- otherwise:
import qualified Data.ByteString.Char8 as BS

-- | Read file contents as strict 'ByteString'. Uses mmap if possible. May decompress file contents, if needed.
readFile fname = do r <- isReadable fname
                    if r
                      then
                        readFile' fname
                      else
                        throwNotFound fname

readFile' fname = do content <- simpleRead fname
                     let r = let codec = getCodec fname content
                             in BS.concat $ BSL.toChunks $ codec $ BSL.fromChunks [content]
                     return r

throwNotFound :: String -> IO a
throwNotFound fname = ioError $ userError $ concat ["Cannot read ", show fname, "!"]

getCodec fname c | (".gz" `BS.isSuffixOf` BS.pack fname) ||
                   (".Z"  `BS.isSuffixOf` BS.pack fname) = GZip.decompressWith (gzipParams c)
--getCodec fname c | (".bz2" `BS.isSuffixOf` (BS.pack fname)) = BZip.decompressWith (bzipParams c) -- DOESN'T WORK!!!
getCodec fname c                                         = id

gzipParams c = GZip.DecompressParams GZip.defaultWindowBits (fromIntegral (BS.length c * 5))
#if MIN_VERSION_zlib(0,5,4)
                                     Nothing
#endif
#if MIN_VERSION_zlib(0,6,1)
                                     True
#endif
  -- Upper bound: compression rate never exceeded 4.7 for big test files.

--bzipParams c = BZip.DecompressParams BZip.DefaultMemoryLevel (fromIntegral (BS.length c * 7 + 4*1024*1024)) -- Upper bound: compression rate never exceeded 6.7 for big test files + 4MiB buffering.

isReadable fname = do exists <- doesFileExist fname
                      if exists
                        then do perms <- getPermissions fname
                                return $! readable perms 
                        else return False

#ifndef HAVE_MMAP
simpleRead       = BS.readFile 
#else
simpleRead fname = mmapFileByteString fname Nothing `Exc.catch` \e -> do reportError (e :: IOError) -- cannot mmap
                                                                         BS.readFile fname
  where
    reportError e = do putStrLn $ concat [show e, "while trying to mmap('", fname, "')"]
#endif

-- | Write file contents as strict 'ByteString'.
writeFile fname writer = void $ withFile fname WriteMode writer