{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}

module Data.Hadoop.Protobuf.Headers where

import Data.ByteString (ByteString)
import Data.ProtocolBuffers
import Data.ProtocolBuffers.Orphans ()
import Data.Text (Text)
import Data.Word (Word32, Word64)
import GHC.Generics (Generic)

------------------------------------------------------------------------

-- TODO From Hadoop source:
--  Spec for UserInformationProto is specified in ProtoUtil#makeIpcConnectionContext

-- | User information beyond what can be determined as part of the
-- security handshake at connection time (kerberos, tokens, etc)
data UserInformation = UserInformation
    { effectiveUser :: Optional 1 (Value Text)
    , realUser      :: Optional 2 (Value Text)
    } deriving (Generic, Show)

instance Encode UserInformation
instance Decode UserInformation

-- | The connection context is sent as part of the connection
-- establishment. It establishes the context for ALL Rpc calls
-- within the connection.
data IpcConnectionContext = IpcConnectionContext
    { ctxUserInfo :: Optional 2 (Message UserInformation)
    , ctxProtocol :: Optional 3 (Value Text) -- ^ Name of the next RPC layer.
    } deriving (Generic, Show)

instance Encode IpcConnectionContext
instance Decode IpcConnectionContext

------------------------------------------------------------------------

-- | Determines the RPC Engine and the serialization of the RPC request.
data RpcKind = Builtin        -- ^ Used for built-in calls by tests
             | Writable       -- ^ Use WritableRpcEngine
             | ProtocolBuffer -- ^ Use ProtobufRpcEngine
  deriving (Generic, Show, Enum)

data RpcOperation = FinalPacket        -- ^ The final RPC packet
                  | ContinuationPacket -- ^ Not implemented yet
                  | CloseConnection    -- ^ Close the RPC connection
  deriving (Generic, Show, Enum)

data RpcRequestHeader = RpcRequestHeader
    { reqKind       :: Optional 1 (Enumeration RpcKind)
    , reqOp         :: Optional 2 (Enumeration RpcOperation)
    , reqCallId     :: Required 3 (Value Word32)     -- ^ A sequence number that is sent back in the response

    -- Fields below don't apply until v9
    --, reqClientId   :: Required 4 (Value ByteString) -- ^ Globally unique client ID
    --, reqRetryCount :: Optional 5 (Value Int32)      -- ^ Retry count, 1 means this is the first retry
    } deriving (Generic, Show)

instance Encode RpcRequestHeader
instance Decode RpcRequestHeader

-- | This message is used for Protobuf RPC Engine.
-- The message is used to marshal a RPC request from RPC client to the
-- RPC server. The response to the RPC call (including errors) are handled
-- as part of the standard RPC response.
data RpcRequest = RpcRequest
    { reqMethodName      :: Required 1 (Value Text)       -- ^ Name of the RPC method
    , reqBytes           :: Optional 2 (Value ByteString) -- ^ Bytes corresponding to the client protobuf request
    , reqProtocolName    :: Required 3 (Value Text)       -- ^ Protocol name of class declaring the called method
    , reqProtocolVersion :: Required 4 (Value Word64)     -- ^ Protocol version of class declaring the called method
    } deriving (Generic, Show)

instance Encode RpcRequest
instance Decode RpcRequest

------------------------------------------------------------------------

-- | Success or failure. The reponse header's error detail, exception
-- class name and error message contains further details on the error.
data RpcStatus = Success -- ^ Succeeded
               | Error   -- ^ Non-fatal error, connection left open
               | Fatal   -- ^ Fatal error, connection closed
  deriving (Generic, Show, Eq, Enum)

-- | Note that RPC response header is also used when connection setup fails.
-- (i.e. the response looks like an RPC response with a fake callId)
--
-- For v7:
--  - If successfull then the Respose follows after this header
--      - length (4 byte int), followed by the response
--  - If error or fatal - the exception info follow
--      - length (4 byte int) Class name of exception - UTF-8 string
--      - length (4 byte int) Stacktrace - UTF-8 string
--      - if the strings are null then the length is -1
--
--  In case of Fatal error then the respose contains the Serverside's IPC version.

data RpcResponseHeader = RpcResponseHeader
    { rspCallId             :: Required 1 (Value Word32)          -- ^ Call ID used in request
    , rspStatus             :: Required 2 (Enumeration RpcStatus)
    , rspServerIpcVersion   :: Optional 3 (Value Word32)          -- ^ v7: Sent if fatal v9: Sent if success or fail

    -- Fields below don't apply until v9
    --, rspExceptionClassName :: Optional 4 (Value Text)            -- ^ If the request fails
    --, rspErrorMsg           :: Optional 5 (Value Text)            -- ^ If the request fails, often contains stack trace
    --, rspErrorDetail        :: Optional 6 (Enumeration Error)  -- ^ In case of error
    --, rspClientId           :: Optional 7 (Value ByteString)      -- ^ Globally unique client ID
    --, rspRetryCount         :: Optional 8 (Value Int32)
    } deriving (Generic, Show)

instance Encode RpcResponseHeader
instance Decode RpcResponseHeader

{-
-- Error doesn't apply until v9

-- | Describes why an RPC error occurred.
data Error = ErrorApplication         -- ^ RPC failed - RPC app threw exception
              | ErrorNoSuchMethod        -- ^ RPC error - no such method
              | ErrorNoSuchProtocol      -- ^ RPC error - no such protocol
              | ErrorRpcServer           -- ^ RPC error on server side
              | ErrorSerializingResponse -- ^ Error serializing response
              | ErrorRpcVersionMismatch  -- ^ RPC protocol version mismatch
              | ErrorCode Int            -- ^ RPC error that we don't know about

                -- starts at 10
              | FatalUnknown                  -- ^ Unknown fatal error
              | FatalUnsupportedSerialization -- ^ IPC layer serilization type invalid
              | FatalInvalidRpcHeader         -- ^ Fields of RPC header are invalid
              | FatalDeserializingRequest     -- ^ Could not deserialize RPC request
              | FatalVersionMismatch          -- ^ IPC layer version mismatch
              | FatalUnauthorized             -- ^ Auth failed
              | FatalCode Int                 -- ^ Fatal error that we don't know about
  deriving (Generic, Show)

instance Enum Error where
    toEnum n = case n of
      1  -> ErrorApplication
      2  -> ErrorNoSuchMethod
      3  -> ErrorNoSuchProtocol
      4  -> ErrorRpcServer
      5  -> ErrorSerializingResponse
      6  -> ErrorRpcVersionMismatch
      10 -> FatalUnknown
      11 -> FatalUnsupportedSerialization
      12 -> FatalInvalidRpcHeader
      13 -> FatalDeserializingRequest
      14 -> FatalVersionMismatch
      15 -> FatalUnauthorized
      _  -> if n < 10 then ErrorCode n else FatalCode n

    fromEnum e = case e of
      ErrorApplication              -> 1
      ErrorNoSuchMethod             -> 2
      ErrorNoSuchProtocol           -> 3
      ErrorRpcServer                -> 4
      ErrorSerializingResponse      -> 5
      ErrorRpcVersionMismatch       -> 6
      ErrorCode n                   -> n
      FatalUnknown                  -> 10
      FatalUnsupportedSerialization -> 11
      FatalInvalidRpcHeader         -> 12
      FatalDeserializingRequest     -> 13
      FatalVersionMismatch          -> 14
      FatalUnauthorized             -> 15
      FatalCode n                   -> n
-}