module Hasql.Core.Request where

import Hasql.Prelude
import Hasql.Core.Model
import qualified ByteString.StrictBuilder as B
import qualified BinaryParser as D
import qualified Hasql.Core.InterpretResponses as A
import qualified Hasql.Core.Protocol.Encoding as K
import qualified Hasql.Core.Protocol.Model as C
import qualified Data.Vector as G


{-|
A builder of concatenated outgoing messages and
a parser of the stream of incoming messages.
-}
data Request result =
  Request !B.Builder !(A.InterpretResponses result)

instance Functor Request where
  {-# INLINE fmap #-}
  fmap mapping (Request builder parse) =
    Request builder (fmap mapping parse)

instance Applicative Request where
  {-# INLINE pure #-}
  pure =
    Request mempty . return
  {-# INLINE (<*>) #-}
  (<*>) (Request leftBuilder leftParse) (Request rightBuilder rightParse) =
    Request (leftBuilder <> rightBuilder) (leftParse <*> rightParse)

{-# INLINE simple #-}
simple :: B.Builder -> A.InterpretResponses result -> Request result
simple builder ir =
  Request builder ir

{-# INLINE parse #-}
parse :: ByteString -> ByteString -> Vector Word32 -> Request ()
parse preparedStatementName query oids =
  simple (K.parseMessage preparedStatementName query oids) A.parseComplete

{-# INLINE bind #-}
bind :: ByteString -> ByteString -> Vector (Maybe B.Builder) -> Request ()
bind portalName preparedStatementName parameters =
  simple (K.binaryFormatBindMessage portalName preparedStatementName parameters) A.bindComplete

{-# INLINE bindEncoded #-}
bindEncoded :: ByteString -> ByteString -> Int -> B.Builder -> Request ()
bindEncoded portalName preparedStatementName paramsAmount paramsBuilder =
  simple
    (K.binaryFormatBindMessageWithEncodedParams portalName preparedStatementName (fromIntegral paramsAmount) paramsBuilder)
    A.bindComplete

{-# INLINE execute #-}
execute :: ByteString -> A.InterpretResponses result -> Request result
execute portalName pms =
  simple (K.unlimitedExecuteMessage portalName) pms

{-# INLINE sync #-}
sync :: Request TransactionStatus
sync =
  simple K.syncMessage A.readyForQuery

{-# INLINE startUp #-}
startUp :: ByteString -> Maybe ByteString -> [(ByteString, ByteString)] -> Request AuthenticationResult
startUp username databaseMaybe runtimeParameters =
  simple 
    (K.startUpMessage 3 0 username databaseMaybe runtimeParameters)
    (A.authenticationResult)

{-# INLINE clearTextPassword #-}
clearTextPassword :: ByteString -> Request AuthenticationResult
clearTextPassword password =
  simple
    (K.clearTextPasswordMessage password)
    (A.authenticationResult)

{-# INLINE md5Password #-}
md5Password :: ByteString -> ByteString -> ByteString -> Request AuthenticationResult
md5Password username password salt =
  simple
    (K.md5PasswordMessage username password salt)
    (A.authenticationResult)

{-# INLINE unparsedStatement #-}
unparsedStatement :: ByteString -> ByteString -> Vector Word32 -> B.Builder -> A.InterpretResponses result -> Request result
unparsedStatement name template oidVec bytesBuilder parseMessageStream =
  parse name template oidVec *>
  parsedStatement name template (G.length oidVec) bytesBuilder parseMessageStream

{-# INLINE parsedStatement #-}
parsedStatement :: ByteString -> ByteString -> Int -> B.Builder -> A.InterpretResponses result -> Request result
parsedStatement name template paramsAmount bytesBuilder parseMessageStream =
  bindEncoded "" name paramsAmount bytesBuilder *>
  execute "" parseMessageStream