{-# OPTIONS_GHC -fglasgow-exts -fallow-overlapping-instances -fallow-undecidable-instances #-}
module Pugs.Prim.FileTest (
    isReadable, isWritable, isExecutable,
    exists, isFile, isDirectory,
    fileSize, sizeIsZero,
    fileMTime, fileCTime, fileATime,
    fileTestViaPerl5
) where
import Pugs.Internals
import Pugs.Embed
import Pugs.Types
import Pugs.AST hiding (isWritable)

-- filetest operators --

-- Officially, these should return a stat object, which sometimes pretends
-- to be a boolean, and may(?) return the filename in string context.
-- DARCS was working on stat, and we should perhaps grab their work:
--  http://www.abridgegame.org/pipermail/darcs-users/2005-February/005499.html
-- They currently (2004-04-05) seem to be using:
--  http://abridgegame.org/cgi-bin/darcs.cgi/darcs/win32/System/Posix.hs
-- For the moment, these return filename and false or undef.
-- Known Bugs: multiple stat()s are done, and filename isnt a boolean.

isReadable   :: Val -> Eval Val
isReadable   = fileTestIO fileTestIsReadable
isWritable   :: Val -> Eval Val
isWritable   = fileTestIO fileTestIsWritable
isExecutable :: Val -> Eval Val
isExecutable = fileTestIO fileTestIsExecutable
exists       :: Val -> Eval Val
exists       = fileTestIO fileTestExists
isFile       :: Val -> Eval Val
isFile       = fileTestIO fileTestIsFile
isDirectory  :: Val -> Eval Val
isDirectory  = fileTestIO fileTestIsDirectory
fileSize     :: Val -> Eval Val
fileSize     = fileTestIO fileTestFileSize
sizeIsZero   :: Val -> Eval Val
sizeIsZero   = fileTestIO fileTestSizeIsZero
fileMTime    :: Val -> Eval Val
fileMTime    = fileTime statFileMTime
fileCTime    :: Val -> Eval Val
fileCTime    = fileTime statFileCTime
fileATime    :: Val -> Eval Val
fileATime    = fileTime statFileATime

fileTestViaPerl5 :: String -> Val -> Eval Val
fileTestViaPerl5 testOp v = do
    env     <- ask
    envSV   <- io $ mkEnv env
    argSV   <- fromVal v
    subSV   <- io $ evalPerl5 ("sub { -" ++ testOp ++ " $_[0] }") envSV (enumCxt cxtItemAny)
    rv      <- runInvokePerl5 subSV nullSV [argSV]
    return $ case rv of
        VStr "" -> VBool False
        VNum 1  -> VBool True
        VInt 1  -> VBool True
        _       -> rv

fileTime :: (FilePath -> IO Integer) -> Val -> Eval Val
fileTime test f = do
    t <- fileTestIO (fileTestDo test) f
    if (t == undef) then return VUndef else do
    t' <- fromVal t
    b <- (readVar $ cast "$*BASETIME") >>= fromVal
    return $ VRat $ (b - (pugsTimeSpec $ TOD t' 0)) / 86400

fileTestIO :: (Value n) => (n -> IO Val) -> Val -> Eval Val
fileTestIO f v = do
    str <- fromVal =<< fromVal' v
    tryIO undef $ f str

valFromBool :: Value a => a -> Bool -> Val
valFromBool v b = if b then castV v else VBool False

testPerms :: (Permissions -> Bool) -> FilePath -> IO Val
testPerms t f = do
    p <- getPermissions f
    let b = t p
    return $ valFromBool f b

fileTestIsReadable :: FilePath -> IO Val
fileTestIsReadable = testPerms readable

fileTestIsWritable :: FilePath -> IO Val
fileTestIsWritable = testPerms writable

fileTestIsExecutable :: FilePath -> IO Val
fileTestIsExecutable = testPerms $ liftM2 (||) executable searchable

fileTestExists :: FilePath -> IO Val
fileTestExists f = doesExist f >>= return . (valFromBool f)

fileTestIsFile :: FilePath -> IO Val
fileTestIsFile f = doesFileExist f >>= return . (valFromBool f)

fileTestIsDirectory :: FilePath -> IO Val
fileTestIsDirectory f = doesDirectoryExist f >>= return . (valFromBool f)

fileTestFileSize :: FilePath -> IO Val
fileTestFileSize f = statFileSize f >>= return . VInt

fileTestSizeIsZero :: FilePath -> IO Val
fileTestSizeIsZero f = do
    n <- statFileSize f
    return $ if n == 0 then VBool True else VBool False

fileTestDo :: (FilePath -> IO Integer) -> FilePath -> IO Val
fileTestDo test f = test f >>= return . VInt
