{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module Utils.File where
import System.Posix.Files(fileExist)
import System.Posix.Directory(readDirStream,openDirStream)
import System.FilePath.Posix
import System.Cmd
import System.Directory(createDirectoryIfMissing
                       ,setCurrentDirectory
                       ,getCurrentDirectory)
import Control.Exception as E(bracket_,catch,evaluate) 
import qualified System.FilePath.Posix as Posix
import System.Directory
import System.IO

import Control.Parallel.Strategies
import Control.Exception
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as BS
import Codec.Compression.GZip

import Data.Binary

-- | Cache results of operation op under name 'fn'. 
--   if correct file is found, its contents are used, otherwise 'op' is performed and it's results
--   are saved to the filed and returned from the call. 

class Cacheable a where
    writeCache :: FilePath -> a -> IO ()
    readCache  :: FilePath -> IO a

box x = [x]

instance (Binary a) => Cacheable a
    where
     readCache  fn   = do
                        (B.readFile fn >>= E.evaluate . decode . decompress . BS.fromChunks . box ) 
                            `E.catch` (\err -> error $"Error reading cache "++fn++": "
                                                        ++show (err:: IOException))
     writeCache fn x = (BS.writeFile fn . compress . encode $ x)
                            `E.catch` (\err -> error $"Error writing cache "++fn++": "
                                                        ++show (err:: IOException))

cached :: (Cacheable a) => FilePath -> IO a -> IO a
cached fn op = do
        let fn' = fn++".CACHE"
        e <- fileExist fn'
        if e
            then readCache fn'
            else do
                    x <- op
                    writeCache fn' x
                    return x


-- Check that file is not . or ..
notABackLink = (not . (flip elem) [".",".."] . takeFileName)

-- Get directory contents with path appended to them
getDirectoryContentsWithPath path = do
  dc <- getDirectoryContents path
  return $ map (path Posix.</>) dc

-- Return a filename that does not exists
genFileName base ext = genFileName' names
            where 
                genFileName' :: [FilePath] -> IO FilePath
                genFileName' (n:ns) = do 
                                          exists <- fileExist n
                                          if exists
                                           then genFileName' ns
                                           else return n 
                names :: [FilePath]
                names = [base ++ show x++ext | x <- [1..]]

-- return files from directory
getDirectoryList fp = openDirStream fp >>= getDSContent 
                where 
                 getDSContent ds = do 
                                 x <- readDirStream ds
                                 if x == "" then return []
                                  else do 
                                        xs <- getDSContent ds
                                        return ((fp++x):xs)

-- Does file have extension `ext`  
hasExt ext fp = all (uncurry (==)) $ zip (reverse ext) (reverse fp)

-- Retreive files from `path` that have extension `ext`
getFilesOfExt ext path = getDirectoryList path >>= return.filter (hasExt ext)

-- Perform operation  `op` in directory `dir`. If directory does not exist
-- It will be created
inDirectory :: FilePath -> IO a -> IO a 
inDirectory dir op = do
                createDirectoryIfMissing True dir
                currDir <- getCurrentDirectory
                Control.Exception.bracket_ 
                    (setCurrentDirectory dir)
                    (setCurrentDirectory currDir)
                    op

-- Append text to `file` strictly and hax it so that parallel writes are forcefully done 
strictPersistentAppendFile p file string = B.appendFile file (B.pack string)
    where 
     catch :: Int -> IOException -> IO ()
     catch 0 e = Control.Exception.throw e
     catch n e = strictPersistentAppendFile (n-1) file string

strictAppendFile file string = B.appendFile file (B.pack string)
--strictAppendFile file text = strictAppendFile' file text `demanding` rnf text
--strictAppendFile' outputfile text = Control.Exception.catch 
--        (do 
--            appendhandle <- openFile outputfile (AppendMode)
--            hPutStr appendhandle text
--            hFlush appendhandle
--            hClose appendhandle)
--        (\e ->fail  "")