{-|
Module      : Network.Pusher.Internal
Description : Pure functions called by the public interface
Copyright   : (c) Will Sewell, 2016
Licence     : MIT
Maintainer  : me@willsewell.com
Stability   : experimental
-}
module Network.Pusher.Internal
  ( mkTriggerRequest
  , mkChannelsRequest
  , mkChannelRequest
  , mkUsersRequest
  ) where

import Control.Monad (when)
import Data.Maybe (maybeToList)
import Data.Monoid ((<>))
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Aeson as A
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T

import Network.Pusher.Data
  ( Channel
  , ChannelType
  , Credentials(..)
  , Event
  , EventData
  , Pusher(..)
  , SocketID
  , renderChannel
  , renderChannelPrefix
  )
import Network.Pusher.Error(PusherError(..))
import Network.Pusher.Internal.Auth (makeQS)
import Network.Pusher.Internal.HTTP
  ( RequestBody
  , RequestParams(RequestParams)
  , RequestQueryString
  )
import Network.Pusher.Protocol
  ( ChannelInfoQuery
  , ChannelsInfoQuery
  , toURLParam
  )

mkTriggerRequest
  :: Pusher
  -> [Channel]
  -> Event
  -> EventData
  -> Maybe SocketID
  -> Int
  -> Either PusherError (RequestParams, RequestBody)
mkTriggerRequest pusher chans event dat socketId time = do
    when
      (length chans > 10)
      (Left $ PusherArgumentError "Must be less than 10 channels")
    let
      body = A.object $
        [ ("name", A.String event)
        , ("channels", A.toJSON (map (A.String . renderChannel) chans))
        , ("data", A.String dat)
        ] ++ maybeToList (fmap (\sID ->  ("socket_id", A.String sID)) socketId)
      bodyBS = BL.toStrict $ A.encode body
    when
      (B.length bodyBS > 10000)
      (Left $ PusherArgumentError "Body must be less than 10000KB")
    return (mkPostRequest pusher "events" [] bodyBS time, body)

mkChannelsRequest
  :: Pusher
  -> Maybe ChannelType
  -> T.Text
  -> ChannelsInfoQuery
  -> Int
  -> RequestParams
mkChannelsRequest pusher channelTypeFilter prefixFilter attributes time =
  let
    prefix = maybe "" renderChannelPrefix channelTypeFilter <> prefixFilter
    params =
      [ ("info", encodeUtf8 $ toURLParam attributes)
      , ("filter_by_prefix", encodeUtf8 prefix)
      ]
  in
    mkGetRequest pusher "channels" params time

mkChannelRequest
  :: Pusher
  -> Channel
  -> ChannelInfoQuery
  -> Int
  -> RequestParams
mkChannelRequest pusher chan attributes time =
  let
    params = [("info", encodeUtf8 $ toURLParam attributes)]
    subPath = "channels/" <> renderChannel chan
  in
    mkGetRequest pusher subPath params time

mkUsersRequest :: Pusher -> Channel -> Int -> RequestParams
mkUsersRequest pusher chan time =
  let
    subPath = "channels/" <> renderChannel chan <> "/users"
  in
    mkGetRequest pusher subPath [] time

mkGetRequest
  :: Pusher
  -> T.Text
  -> RequestQueryString
  -> Int
  -> RequestParams
mkGetRequest pusher subPath params time =
  let
    (ep, fullPath) = mkEndpoint pusher subPath
    qs = mkQS pusher "GET" fullPath params "" time
  in
    RequestParams ep qs

mkPostRequest
  :: Pusher
  -> T.Text
  -> RequestQueryString
  -> B.ByteString
  -> Int
  -> RequestParams
mkPostRequest pusher subPath params bodyBS time =
  let
    (ep, fullPath) = mkEndpoint pusher subPath
    qs = mkQS pusher "POST" fullPath params bodyBS time
  in
    RequestParams ep qs

-- |Build a full endpoint from the details in Pusher and the subPath.
mkEndpoint
  :: Pusher
  -> T.Text -- ^The subpath of the specific request, e.g "events/channel-name"
  -> (T.Text, T.Text) -- ^The full endpoint, and just the path component
mkEndpoint pusher subPath =
  let
    fullPath = pusherPath pusher <> subPath
    endpoint = pusherHost pusher <> fullPath
  in
    (endpoint, fullPath)

mkQS
  :: Pusher
  -> T.Text
  -> T.Text
  -> RequestQueryString
  -> B.ByteString
  -> Int
  -> RequestQueryString
mkQS pusher =
  let
    credentials = pusherCredentials pusher
  in
    makeQS
      (credentialsAppKey credentials)
      (credentialsAppSecret credentials)