{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE RankNTypes #-}
{- |
Module: Porpoise
Description: A very minimal web framework wrapping wai
Copyright: (c) Samuel Schlesinger 2020
License: MIT
Maintainer: sgschlesinger@gmail.com
Stability: experimental
Portability: POSIX, Windows

A very minimal HTTP server framework wrapping wai.
-}
module Web.Porpoise
(
  -- ** Server Language
  Server(..)
, toApplication
, liftS
, serverIO
, Profunctor(..)
, Category(..)
, MonadUnliftIO(..)
, ResponseReceived
, Application
  -- ** Observing a 'Request' 
, Request
, requestMethod
, httpVersion
, rawPathInfo
, rawQueryString
, requestHeaders
, isSecure
, remoteHost
, pathInfo
, queryString
, getRequestBodyChunk
, vault
, requestBodyLength
, requestHeaderHost
, requestHeaderRange
, requestHeaderReferer
, requestHeaderUserAgent
, strictRequestBody
, lazyRequestBody
, RequestBodyLength(..)
  -- ** Building a 'Response'
, Response
, FilePart(..)
, responseLBS
, responseStream
, responseRaw
, responseBuilder
, responseFile
, StreamingBody
  -- ** Miscellaneous re-exports
, Status
, mkStatus
, Header
, HeaderName
, ResponseHeaders
, RequestHeaders
, hAccept
, hAcceptCharset
, hAcceptEncoding
, hAcceptLanguage
, hAcceptRanges
, hAge
, hAllow
, hAuthorization
, hCacheControl
, hConnection
, hContentEncoding
, hContentLanguage
, hContentLocation
, hContentMD5
, hContentRange
, hContentType
, hDate
, hETag
, hExpect
, hExpires
, hFrom
, hHost
, hIfMatch
, hIfModifiedSince
, hIfNoneMatch
, hIfRange
, hIfUnmodifiedSince
, hLastModified
, hLocation
, hMaxForwards
, hOrigin
, hPragma
, hPrefer
, hPreferenceApplied
, hProxyAuthenticate
, hRange
, hReferer
, hRetryAfter
, hServer
, hTE
, hTrailer
, hTransferEncoding
, hUpgrade
, hUserAgent
, hVary
, hVia
, hWWWAuthenticate
, hWarning
, hContentDisposition
, hMIMEVersion
, hCookie
, hSetCookie
, ByteRange(..)
, renderByteRangeBuilder
, renderByteRange
, ByteRanges
, renderByteRangesBuilder
, parseByteRanges
, HttpVersion(..)
, http09
, http10
, http11
, http20
, Method
, methodGet
, methodPost
, methodHead
, methodPut
, methodDelete
, methodTrace
, methodConnect
, methodOptions
, methodPatch
, StdMethod(..)
, parseMethod
, renderMethod
, renderStdMethod
, QueryItem
, Query
, urlEncode
, urlDecode
, urlEncodeBuilder
, extractPath
, decodePath
, encodePath
, SockAddr
, Vault
) where

import Prelude hiding ((.), id)
import Data.Coerce (coerce)
import Control.Arrow (Kleisli(Kleisli))
import Data.Profunctor (Profunctor(..))
import Control.Monad.Fail (MonadFail)
import Control.Monad.Reader (MonadReader, ReaderT(ReaderT))
import Control.Monad.Cont (MonadCont, ContT(ContT))
import Control.Category (Category((.), id))
import Network.Wai
import Network.Wai.Internal (ResponseReceived(ResponseReceived))
import UnliftIO (MonadIO(liftIO), MonadUnliftIO(withRunInIO))
import Network.HTTP.Types.Header
import Network.HTTP.Types.Version
import Network.HTTP.Types.Method
import Network.HTTP.Types.URI
import Network.HTTP.Types.Status
import Data.Vault.Lazy (Vault)
import Network.Socket (SockAddr)

{- |
A server application which receives a request and responds.
-}
newtype Server m request response = Server
  { unServer :: request -> (response -> m ResponseReceived) -> m ResponseReceived }
  deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadCont)
    via ReaderT request (ContT ResponseReceived m)

askRequest :: Monad m => Server m request request
askRequest = id

instance Functor m => Profunctor (Server m) where
  dimap f g (Server h) = Server \request respond -> h (f request) (respond . g)

instance Monad m => Category (Server m) where
  Server a . Server b = coerce $ Kleisli (fmap ContT a) . Kleisli (fmap ContT b)
  id :: forall a. Server m a a
  id = Server \request respond -> respond request

{- |
Compile a 'Server' a runnable wai application.
-}
{-# INLINE toApplication #-}
toApplication :: Server IO Request Response -> Application
toApplication = unServer

{- |
Lift a computation from the base monad into the 'Server' monad. This is
provided because this library prefers to use the second to last type variable
position for the contravariant component in the 'Profunctor' instance,
and so we are able to write a 'Category' instance.
-}
{-# INLINE liftS #-}
liftS :: Monad m => m response -> Server m request response
liftS r = Server $ const (r >>=)

{- |
In any monad that has an instance of 'MonadUnliftIO', we can retrieve a
function allowing our 'Server' to operate in 'IO'. The resulting function is
expected to be used to transform the 'Server' prior to calling 'toApplication'.
-}
{-# INLINE serverIO #-}
serverIO :: MonadUnliftIO m => m (Server m request response -> Server IO request response)
serverIO =
  withRunInIO
    \runner -> pure
      \(Server f) -> Server
        \request respond -> runner (f request (liftIO . respond))