{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable, CPP, ForeignFunctionInterface #-}
{-# OPTIONS_GHC -fno-warn-overlapping-patterns #-}

module Development.Shake.FileTime(
    FileTime,
    getModTimeError, getModTimeMaybe
    ) where

import Control.DeepSeq
import Data.Binary
import Data.Hashable
import Data.Typeable
import Data.Int
import qualified Data.ByteString.Char8 as BS

#ifdef PORTABLE

import System.IO.Error
import Control.Exception
import System.Directory

#if __GLASGOW_HASKELL__ >= 706
import Data.Time.Clock
#else
import System.Time
#endif

#elif defined mingw32_HOST_OS

import Foreign
import Foreign.C.Types

type WIN32_FILE_ATTRIBUTE_DATA = Ptr ()
type LPCSTR = Ptr CChar

foreign import stdcall "Windows.h GetFileAttributesExA" c_getFileAttributesEx :: LPCSTR -> Int32 -> WIN32_FILE_ATTRIBUTE_DATA -> IO Bool

size_WIN32_FILE_ATTRIBUTE_DATA = 36

index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime = 20

#else

import System.IO.Error
import Control.Exception
import System.Posix.Files.ByteString

#endif


newtype FileTime = FileTime Int32
    deriving (Typeable,Eq,Hashable,Binary,Show,NFData)


getModTimeMaybe :: BS.ByteString -> IO (Maybe FileTime)

#ifdef PORTABLE

-- Portable fallback
getModTimeMaybe x = handleJust (\e -> if isDoesNotExistError e then Just () else Nothing) (const $ return Nothing) $ do
    time <- getModificationTime $ BS.unpack x
#if __GLASGOW_HASKELL__ >= 706
    return $ Just $ FileTime $ floor $ utctDayTime time
#else
    let TOD t _ = time
    return $ Just $ FileTime $ fromIntegral t
#endif

#elif defined mingw32_HOST_OS

-- Directly against the Win32 API, twice as fast as the portable version
getModTimeMaybe x = BS.useAsCString x $ \file ->
    allocaBytes size_WIN32_FILE_ATTRIBUTE_DATA $ \info -> do
        res <- c_getFileAttributesEx file 0 info
        if not res then return Nothing else do
            -- Technically a Word32, but we can treak it as an Int32 for peek
            dword <- peekByteOff info index_WIN32_FILE_ATTRIBUTE_DATA_ftLastWriteTime_dwLowDateTime
            return $ Just $ FileTime dword

#else

-- Directly against the unix library
getModTimeMaybe x = handleJust (\e -> if isDoesNotExistError e then Just () else Nothing) (const $ return Nothing) $ do
    t <- fmap modificationTime $ getFileStatus x
    return $ Just $ FileTime $ fromIntegral $ fromEnum t

#endif


getModTimeError :: String -> BS.ByteString -> IO FileTime
getModTimeError msg x = do
    res <- getModTimeMaybe x
    case res of
        -- Make sure you raise an error in IO, not return a value which will error later
        Nothing -> error $ msg ++ "\n  " ++ BS.unpack x
        Just x -> return x