{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE BangPatterns #-}

module Database.VCache.VCacheable
    ( VCacheable(..)
    , module Database.VCache.VGet
    , module Database.VCache.VPut
    ) where

import Control.Applicative
import Control.Monad

import Data.Word
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS

import Database.VCache.VGet
import Database.VCache.VPut
import Database.VCache.Types

-- VCacheable defined in Database.VCache.Types

instance VCacheable Int where
    get = fromIntegral <$> getVarInt
    put = putVarInt . fromIntegral
    {-# INLINE get #-}
    {-# INLINE put #-}

instance VCacheable Integer where
    get = getVarInt
    put = putVarInt
    {-# INLINE get #-}
    {-# INLINE put #-}

instance VCacheable Bool where
    get = getWord8 >>= \ n -> case n of
        0 -> return False
        1 -> return True
        _ -> fail "Boolean expects a 0 or 1 byte"
    put False = putWord8 0
    put True  = putWord8 1

instance VCacheable Char where 
    get = getc
    put = putc
    {-# INLINE get #-}
    {-# INLINE put #-}

instance VCacheable Word8 where
    get = getWord8
    put = putWord8
    {-# INLINE get #-}
    {-# INLINE put #-}

instance VCacheable BS.ByteString where
    get = getVarNat >>= getByteString . fromIntegral
    put s = putVarNat (fromIntegral $ BS.length s) >> putByteString s 
    {-# INLINE get #-}
    {-# INLINE put #-}

instance VCacheable LBS.ByteString where
    get = getVarNat >>= getByteStringLazy . fromIntegral
    put s = putVarNat (fromIntegral $ LBS.length s) >> putByteStringLazy s
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a) => VCacheable (VRef a) where
    get = getVRef
    put = putVRef
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a) => VCacheable (PVar a) where
    get = getPVar
    put = putPVar
    {-# INLINE get #-}
    {-# INLINE put #-}

-- unit is not actually serialized.
instance VCacheable () where
    get = return ()
    put () = return ()
    {-# INLINE get #-}
    {-# INLINE put #-}

-- `Maybe a` may be upgraded transparently to [a], and may share
-- structure with single element lists.
instance (VCacheable a) => VCacheable (Maybe a) where
    get = getWord8 >>= \ n -> case n of 
        0 -> return Nothing
        1 -> Just <$> get
        _ -> fail "Type `Maybe a` expects prefix byte 0 or 1"
    put Nothing  = putWord8 0
    put (Just a) = putWord8 1 >> put a

instance (VCacheable a, VCacheable b) => VCacheable (Either a b) where
    get = getWord8 >>= \ lr -> case lr of
        0 -> Left <$> get
        1 -> Right <$> get
        _ -> fail "Type `Either a b` expects prefix byte 0 or 1"
    put (Left a) = putWord8 0 >> put a
    put (Right b) = putWord8 1 >> put b

-- NOTE: lists are stored in *reverse* order, such that when read
-- the nodes can be directly constructed into normal order without
-- reversing the list, i.e. thus optimizing for read.
instance (VCacheable a) => VCacheable [a] where
    get = do
        nCount <- liftM fromIntegral getVarNat
        replicateReversed [] nCount get
    put ls = do
        let (nCount, lsr) = countAndReverse ls
        putVarNat (fromIntegral nCount)
        mapM_ put lsr
    {-# INLINE get #-}
    {-# INLINE put #-}

-- replicate an operation and build a reversed list of results.
replicateReversed :: (Monad m) => [a] -> Int -> m a -> m [a]
replicateReversed xs 0 _ = return xs
replicateReversed xs n op = op >>= \ x -> replicateReversed (x:xs) (n-1) op

-- single pass to count and reverse list
countAndReverse :: [a] -> (Int, [a])
countAndReverse = cr [] 0 where
    cr l !n (x:xs) = cr (x:l) (n+1) xs
    cr l !n [] = (n, l)


-- note that ((a,b),c) and (a,(b,c)) share serialized structure.
-- So does ((a,b),(c,d)) and (a,(b,c),d), etc.
instance (VCacheable a, VCacheable b) => VCacheable (a,b) where
    get = liftM2 (,) get get
    put (a,b) = do { put a; put b }
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a, VCacheable b, VCacheable c) => VCacheable (a,b,c) where
    get = liftM3 (,,) get get get
    put (a,b,c) = do { put a; put b; put c }
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a, VCacheable b, VCacheable c, VCacheable d) 
    => VCacheable (a,b,c,d) where
    get = liftM4 (,,,) get get get get
    put (a,b,c,d) = do { put a; put b; put c; put d }
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a, VCacheable b, VCacheable c, VCacheable d, VCacheable e) 
    => VCacheable (a,b,c,d,e) where
    get = liftM5 (,,,,) get get get get get
    put (a,b,c,d,e) = do { put a; put b; put c; put d; put e }
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a, VCacheable b, VCacheable c, VCacheable d, VCacheable e
         , VCacheable f) => VCacheable (a,b,c,d,e,f) where
    get = 
        do a <- get; b <- get; c <- get
           d <- get; e <- get; f <- get
           return (a,b,c,d,e,f)
    put (a,b,c,d,e,f) = do { put a; put b; put c; put d; put e; put f }
    {-# INLINE get #-}
    {-# INLINE put #-}

instance (VCacheable a, VCacheable b, VCacheable c, VCacheable d, VCacheable e
         , VCacheable f, VCacheable g) => VCacheable (a,b,c,d,e,f,g) where
    get = 
        do a <- get; b <- get; c <- get
           d <- get; e <- get; f <- get; g <- get
           return (a,b,c,d,e,f,g)
    put (a,b,c,d,e,f,g) = do { put a; put b; put c; put d; put e; put f; put g }
    {-# INLINE get #-}
    {-# INLINE put #-}