{-# LANGUAGE CPP, ScopedTypeVariables #-}

-- | Mostly internal utilities for use by the rest of the library. Subject to
-- removal without further notice.
module Storage.Hashed.Utils where

import Prelude hiding ( lookup, catch )
import System.Mem( performGC )
import System.IO.MMap( mmapFileByteString )
import Bundled.Posix( getFileStatus, fileSize )
import System.Directory( getCurrentDirectory, setCurrentDirectory )
import System.FilePath( (</>), isAbsolute )
import Data.Int( Int64 )
import Data.Maybe( catMaybes )
import Control.Exception.Extensible( catch, bracket, SomeException(..) )
import Control.Monad( when )
import Control.Monad.Identity( runIdentity )
import Control.Applicative( (<$>) )

import Foreign.ForeignPtr( withForeignPtr )
import Foreign.Ptr( plusPtr )
import Data.ByteString.Internal( toForeignPtr, memcpy )
import System.IO (withFile, IOMode(ReadMode), hSeek, SeekMode(AbsoluteSeek))
import Data.Bits( Bits )
#ifdef BIGENDIAN
import Data.Bits( (.&.), (.|.), shift, shiftL, rotateR )
#endif

import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString as BS

import qualified Data.Set as S
import qualified Data.Map as M

-- | Pointer to a filesystem, possibly with start/end offsets. Supposed to be
-- fed to (uncurry mmapFileByteString) or similar.
type FileSegment = (FilePath, Maybe (Int64, Int))

-- | Read in a FileSegment into a Lazy ByteString. Implemented using mmap.
readSegment :: FileSegment -> IO BL.ByteString
readSegment (f,range) = do
    bs <- tryToRead
       `catch` (\(_::SomeException) -> do
                     size <- fileSize `fmap` getFileStatus f
                     if size == 0
                        then return BS8.empty
                        else performGC >> tryToRead)
    return $ BL.fromChunks [bs]
  where
    tryToRead = do 
        case range of
            Nothing -> BS.readFile f
            Just (off, size) -> withFile f ReadMode $ \h -> do
                hSeek h AbsoluteSeek $ fromIntegral off
                BS.hGet h size 
{-# INLINE readSegment #-}

-- | Run an IO action with @path@ as a working directory. Does neccessary
-- bracketing.
withCurrentDirectory :: FilePath -> IO a -> IO a
withCurrentDirectory name =
    bracket
        (do cwd <- getCurrentDirectory
            when (name /= "") (setCurrentDirectory name)
            return cwd)
        (\oldwd -> setCurrentDirectory oldwd
                     `catch` \(_::SomeException) -> return ())
        . const

makeAbsolute :: FilePath -> IO FilePath
makeAbsolute p = do
  cwd <- getCurrentDirectory
  return $! if isAbsolute p then p else cwd </> p

-- Wow, unsafe.
unsafePokeBS :: BS8.ByteString -> BS8.ByteString -> IO ()
unsafePokeBS to from =
    do let (fp_to, off_to, len_to) = toForeignPtr to
           (fp_from, off_from, len_from) = toForeignPtr from
       when (len_to /= len_from) $ fail $ "Length mismatch in unsafePokeBS: from = "
            ++ show len_from ++ " /= to = " ++ show len_to
       withForeignPtr fp_from $ \p_from ->
         withForeignPtr fp_to $ \p_to ->
           memcpy (plusPtr p_to off_to)
                  (plusPtr p_from off_from)
                  (fromIntegral len_to)

align :: Integral a => a -> a -> a
align boundary i = case i `rem` boundary of
                     0 -> i
                     x -> i + boundary - x
{-# INLINE align #-}

xlate32 :: (Bits a) => a -> a
xlate64 :: (Bits a) => a -> a

#ifdef LITTLEENDIAN
xlate32 = id
xlate64 = id
#endif

#ifdef BIGENDIAN
bytemask :: (Bits a) => a
bytemask = 255

xlate32 a = ((a .&. (bytemask `shift`  0)) `shiftL` 24) .|.
            ((a .&. (bytemask `shift`  8)) `shiftL`  8) .|.
            ((a .&. (bytemask `shift` 16)) `rotateR`  8) .|.
            ((a .&. (bytemask `shift` 24)) `rotateR` 24)

xlate64 a = ((a .&. (bytemask `shift`  0)) `shiftL` 56) .|.
            ((a .&. (bytemask `shift`  8)) `shiftL` 40) .|.
            ((a .&. (bytemask `shift` 16)) `shiftL` 24) .|.
            ((a .&. (bytemask `shift` 24)) `shiftL`  8) .|.
            ((a .&. (bytemask `shift` 32)) `rotateR`  8) .|.
            ((a .&. (bytemask `shift` 40)) `rotateR` 24) .|.
            ((a .&. (bytemask `shift` 48)) `rotateR` 40) .|.
            ((a .&. (bytemask `shift` 56)) `rotateR` 56)
#endif

-- | Find a monadic fixed point of @f@ that is the least above @i@. (Will
-- happily diverge if there is none.)
mfixFrom :: (Eq a, Functor m, Monad m) => (a -> m a) -> a -> m a
mfixFrom f i = do x <- f i
                  if x == i then return i
                            else mfixFrom f x

-- | Find a fixed point of @f@ that is the least above @i@. (Will happily
-- diverge if there is none.)
fixFrom :: (Eq a) => (a -> a) -> a -> a
fixFrom f i = runIdentity $ mfixFrom (return . f) i

-- | For a @refs@ function, a @map@ (@key@ -> @value@) and a @rootSet@, find a
-- submap of @map@ such that all items in @map@ are reachable, through @refs@
-- from @rootSet@.
reachable :: forall monad key value. (Functor monad, Monad monad, Ord key, Eq value) =>
              (value -> monad [key])
           -> (key -> monad (Maybe (key, value)))
           -> S.Set key -> monad (M.Map key value)
reachable refs lookup rootSet =
    do lookupSet rootSet >>= mfixFrom expand
    where lookupSet :: S.Set key -> monad (M.Map key value)
          expand :: M.Map key value -> monad (M.Map key value)

          lookupSet s = do list <- mapM lookup (S.toAscList s)
                           return $ M.fromAscList (catMaybes list)
          expand from = do refd <- concat <$> mapM refs (M.elems from)
                           M.union from <$> lookupSet (S.fromList refd)
