module Network.NSQ.Types
    ( MsgId
    , Topic
    , Channel
    , LogName

    , Message(..)
    , Command(..)

    -- TODO: probably don't want to export constructor here but want pattern matching
    , FrameType(..)
    , ErrorType(..)

    , OptionalSetting(..)
    , TLS(..)
    , Compression(..)
    , Identification(..)
    , IdentifyMetadata(..)

    -- Connection config/state
    , NSQConnection(..)

    ) where

import Data.Int
import Data.Word
import Network

import qualified Data.ByteString as BS
import qualified Data.Map.Strict as Map
import qualified Data.Text as T

-- High level arch:
--  * One queue per topic/channel
--  * This queue can be feed by multiple nsqd (load balanced/nsqlookup for ex)
--  * Probably will have one set of state/config per nsqd connection and per queue/topic/channel
--  * Can probably later on provide helpers for consuming the queue


-- | Per Connection configuration
--  * Per nsqd (connection) state (rdy, load balance, etc)
--  * Per topic state (channel related info and which nsqd connection)
--  * Global? state (do we have any atm? maybe configuration?)
-- TODO: consider using monad-journal logger for pure code tracing
data NSQConnection = NSQConnection
    { server :: String
    , port :: PortNumber
    , logName :: LogName

    , identConf :: IdentifyMetadata
    }

-- | Logger Name for a connection (hslogger format)
type LogName = String

-- | Message Id, it is a 16-byte hexdecimal string encoded as ASCII
type MsgId = BS.ByteString

-- | NSQ Topic, the only allowed character in a topic is @\\.a-zA-Z0-9_-@
type Topic = T.Text

-- | NSQ Channel, the only allowed character in a channel is @\\.a-zA-Z0-9_-@
-- A channel can be marked as ephemeral by toggling the 'Bool' value to
-- true in 'Sub'
type Channel = T.Text

-- | NSQ Command
data Command = Protocol -- ^ The protocol version
             | NOP -- ^ No-op, usually used in reply to a 'Heartbeat' request from the server.
             | Identify IdentifyMetadata -- ^ Client Identification + possible features negotiation.
             | Sub Topic Channel Bool -- ^ Subscribe to a specified 'Topic'/'Channel', use 'True' if its an ephemeral channel.
             | Pub Topic BS.ByteString -- ^ Publish a message to the specified 'Topic'.
             | MPub Topic [BS.ByteString] -- ^ Publish multiple messages to a specified 'Topic'.
             | Rdy Word64 -- ^ Update @RDY@ state (ready to recieve messages). Number of message you can process at once.
             | Fin MsgId -- ^ Finish a message.
             | Req MsgId Word64 -- ^ Re-queue a message (failure to process), Timeout is in milliseconds.
             | Touch MsgId -- ^ Reset the timeout for an in-flight message.
             | Cls -- ^ Cleanly close the connection to the NSQ daemon.
             | Command BS.ByteString -- ^ Catch-all command for future expansion/custom commands.
             deriving Show

-- | Frame Type of the incoming data from the NSQ daemon.
data FrameType = FTResponse -- ^ Response to a 'Command' from the server.
               | FTError -- ^ An error in response to a 'Command'.
               | FTMessage -- ^ Messages.
               | FTUnknown Int32 -- ^ For future extension for handling new Frame Types.
               deriving Show

-- | Types of error that the server can return in response to an 'Command'
data ErrorType = Invalid
               | BadBody
               | BadTopic
               | BadChannel
               | BadMessage
               | PubFailed
               | MPubFailed
               | FinFailed
               | ReqFailed
               | TouchFailed
               deriving Show

-- | The message and replies back from the server.
data Message = OK -- ^ Everything is allright.
             | Heartbeat -- ^ Heartbeat, reply with the 'NOP' 'Command'.
             | CloseWait -- ^ Server has closed the connection.

             -- TODO: BS.ByteString -> ErrorType
             | Error BS.ByteString -- ^ The server sent back an error.

             | Message Int64 Word16 MsgId BS.ByteString -- ^ A message to be processed. The values are: Nanosecond Timestamp, number of attempts, Message Id, and the content of the message to be processed.

             | CatchAllMessage FrameType BS.ByteString -- ^ Catch-all message for future expansion. This currently includes the reply from 'Identify' if feature negotiation is set.
             deriving Show


-- Feature and Identification
data OptionalSetting = Disabled | Custom Word64
    deriving Show

data TLS = NoTLS | TLSV1
    deriving Show

data Compression = NoCompression | Snappy | Deflate Word8
    deriving Show

data Identification = Identification
    { clientId :: T.Text
    , hostname :: T.Text
    , shortId :: Maybe T.Text -- Deprecated in favor of client_id
    , longId :: Maybe T.Text -- Deprecated in favor of hostname
    , userAgent :: Maybe T.Text -- Default (client_library_name/version)
    }
    deriving Show

-- feature_negotiation - set automatically if anything is set
data IdentifyMetadata = IdentifyMetadata
    { ident :: Identification
    , tls :: Maybe TLS
    , compression :: Maybe Compression
    , heartbeatInterval :: Maybe OptionalSetting -- disabled = -1
    , outputBufferSize :: Maybe OptionalSetting -- disabled = -1
    , outputBufferTimeout :: Maybe OptionalSetting -- disabled = -1
    , sampleRate :: Maybe OptionalSetting -- disabled = 0

    -- Map of possible json value for future compat
    , custom :: Maybe (Map.Map T.Text T.Text)
    , customNegotiation :: Bool
    }
    deriving Show