{-# LANGUAGE DeriveDataTypeable #-}

-- | Stores the various types needed by memcache. Mostly concerned with the
-- representation of the protocol.
module Database.Memcache.Types (
        Q(..), K(..), Key, Value, Extras, Initial, Delta, Expiration, Flags, Version,
        mEMCACHE_HEADER_SIZE, Header(..),
        Request(..), OpRequest(..), SESet(..), SEIncr(..), SETouch(..), emptyReq,
        Response(..), OpResponse(..), Status(..),
        ProtocolError(..), IncorrectResponse(..)
    ) where

import Control.Exception
import Data.ByteString (ByteString)
import Data.Typeable
import Data.Word

{- MEMCACHE MESSAGE:

    header {
        magic    :: Word8
        op       :: Word8
        keyLen   :: Word16
        extraLen :: Word8
        datatype :: Word8
        status / reserved :: Word16
        bodyLen  :: Word32 (total body length)
        opaque   :: Word32
        cas      :: Word64
    }
    extras :: ByteString
    key    :: ByteString
    value  :: ByteString
 -}

mEMCACHE_HEADER_SIZE :: Int
mEMCACHE_HEADER_SIZE = 24

data Q          = Loud  | Quiet      deriving (Eq, Show, Typeable)
data K          = NoKey | IncludeKey deriving (Eq, Show, Typeable)
type Key        = ByteString
type Value      = ByteString
type Extras     = ByteString
type Initial    = Word64
type Delta      = Word64
type Expiration = Word32
type Flags      = Word32
type Version    = Word64

-- XXX: Which ones care about version? (Encode?)
data OpRequest
    = ReqGet       Q K Key
    | ReqSet       Q   Key Value SESet
    | ReqAdd       Q   Key Value SESet
    | ReqReplace   Q   Key Value SESet
    | ReqDelete    Q   Key
    | ReqIncrement Q   Key       SEIncr
    | ReqDecrement Q   Key       SEIncr
    | ReqAppend    Q   Key Value
    | ReqPrepend   Q   Key Value
    | ReqTouch         Key       SETouch
    | ReqGAT       Q K Key       SETouch
    | ReqFlush     Q             (Maybe SETouch)
    | ReqNoop
    | ReqVersion
    | ReqStat          (Maybe Key)
    | ReqQuit      Q
    | ReqSASLList
    | ReqSASLStart     Key Value -- key: auth method, value: auth data
    | ReqSASLStep      Key Value -- key: auth method, value: auth data (continued)
    deriving (Eq, Show, Typeable)

data SESet   = SESet   Flags Expiration         deriving (Eq, Show, Typeable)
data SEIncr  = SEIncr  Initial Delta Expiration deriving (Eq, Show, Typeable)
data SETouch = SETouch Expiration               deriving (Eq, Show, Typeable)

data Request = Req {
        reqOp     :: OpRequest,
        reqOpaque :: Word32,
        reqCas    :: Version
    } deriving (Eq, Show, Typeable)

emptyReq :: Request
emptyReq = Req { reqOp = ReqNoop, reqOpaque = 0, reqCas = 0 }

data OpResponse
    = ResGet       Q     Value Flags
    | ResGetK      Q Key Value Flags
    | ResSet       Q
    | ResAdd       Q
    | ResReplace   Q
    | ResDelete    Q
    | ResIncrement Q     Word64
    | ResDecrement Q     Word64
    | ResAppend    Q
    | ResPrepend   Q
    | ResTouch
    | ResGAT       Q     Value Flags
    | ResGATK      Q Key Value Flags
    | ResFlush     Q
    | ResNoop
    | ResVersion         Value
    | ResStat        Key Value
    | ResQuit      Q
    | ResSASLList           Value
    | ResSASLStart
    | ResSASLStep
    deriving (Eq, Show, Typeable)

data Status
    = NoError             -- All
    | ErrKeyNotFound      -- Get, GAT, Touch, Replace, Del, Inc, Dec, App, Pre, Set (key not there and version specified...)
    | ErrKeyExists        -- Add, (version): Set, Rep, Del, Inc, Dec, App, Pre
    | ErrValueTooLarge    -- Set, Add, Rep, Pre, App
    | ErrInvalidArgs      -- All
    | ErrItemNotStored    -- ?
    | ErrValueNonNumeric  -- Incr, Decr
    | ErrUnknownCommand   -- All
    | ErrOutOfMemory      -- All
    | SaslAuthFail        -- SASL
    | SaslAuthContinue    -- SASL
    deriving (Eq, Show, Typeable)

data Response = Res {
        resOp     :: OpResponse,
        resStatus :: Status,
        resOpaque :: Word32,
        resCas    :: Version
    } deriving (Eq, Show, Typeable)

data Header = Header {
        op       :: Word8,
        keyLen   :: Word16,
        extraLen :: Word8,
        status   :: Status,
        bodyLen  :: Word32,
        opaque   :: Word32,
        cas      :: Version
    } deriving (Eq, Show, Typeable)

data ProtocolError = ProtocolError {
        protocolMessage :: String,
        protocolHeader  :: Maybe Header,
        protocolParams  :: [String]
    } deriving (Eq, Show, Typeable)

instance Exception ProtocolError

data IncorrectResponse = IncorrectResponse {
        increspMessage :: String,
        increspActual  :: Response
    } deriving (Eq, Show, Typeable)

instance Exception IncorrectResponse