{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}

module Factis.Haskoon.HttpSpec.WebHandler
    (ReqHandler(..), ReqHandlerT(..), ReqHandlerState(..)
    ,runWebReq, runWebReqHandlerT, runReqHandlerT, runIOReqHandlerT, match)
where

----------------------------------------
-- STDLIB
----------------------------------------
import Control.Monad (liftM)
import Control.Monad.Reader (ReaderT, runReaderT, ask, asks)
import Control.Monad.Error (MonadError(..), ErrorT(..))
import Control.Monad.Trans (MonadIO, MonadTrans, lift, liftIO)

----------------------------------------
-- SITE-PACKAGES
----------------------------------------
import Factis.Haskoon.Web (Web(..),WebIO,webBadRequest)
import Factis.Haskoon.WebTrans (WebTrans(..), liftWebRec)

----------------------------------------
-- LOCAL
----------------------------------------
import Data.HttpSpec (WebExc(..),WebComm(..),WebIn(..),WebErr(..))
import Data.HttpSpec.HttpTypes (ReqIn(..),ReqOut(..),ResIn(..),ResOut(..)
                               ,urlMatchPrefix')
import Factis.Haskoon.HttpSpec.SendRecv (getReqIn, sendResOut, sendReqOut)


class MonadError WebExc m => ReqHandler m where
    sendReq :: ReqOut -> m ResIn

data ReqHandlerState m
    = ReqHandlerState
      { rhst_sendReqOut :: ReqOut -> m (Either String ResIn)
      }

newtype ReqHandlerT m a
    = ReqHandlerT
      { unReqHandlerT ::  ReaderT (ReqHandlerState m) (ErrorT WebExc m) a }
    deriving (Monad)

instance Monad m => MonadError WebExc (ReqHandlerT m) where
    throwError err = ReqHandlerT (throwError err)
    catchError (ReqHandlerT action') handler =
        ReqHandlerT (catchError action' handler')
        where handler' err = let ReqHandlerT result  = handler err in result

instance MonadIO m => MonadIO (ReqHandlerT m) where
    liftIO m = ReqHandlerT (liftIO m)

instance MonadTrans ReqHandlerT where
    lift m = ReqHandlerT (lift (lift m))

instance WebTrans ReqHandlerT where
    liftWeb = ReqHandlerT . lift . lift
    liftWebFun f cont =
        ReqHandlerT $
        do st <- ask
           res <- lift (lift (f (runReqHandlerT cont st)))
           case res of
             Left msg -> lift (throwError msg)
             Right x -> return x

instance Web m => Web (ReqHandlerT m) where
    type WebRes (ReqHandlerT m) = WebRes m
    webRec = liftWebRec (liftM id) webRec

liftRdr ::  ReaderT (ReqHandlerState m) (ErrorT WebExc m) a -> ReqHandlerT m a
liftRdr rdr = ReqHandlerT rdr

instance MonadIO m => ReqHandler (ReqHandlerT m) where
    sendReq rout =
        do sro <- liftRdr (asks rhst_sendReqOut)
           res <- lift (sro rout)
           case res of
             Left err -> fail err
             Right result -> return result

runReqHandlerT :: Monad m =>
                  ReqHandlerT m a
               -> ReqHandlerState m
               -> m (Either WebExc a)
runReqHandlerT rh st = runErrorT $ flip runReaderT st $ unReqHandlerT rh

runWebReqHandlerT :: WebIO m => ReqHandlerT m a -> m (Either WebExc a)
runWebReqHandlerT = runIOReqHandlerT

runIOReqHandlerT :: MonadIO m => ReqHandlerT m a -> m (Either WebExc a)
runIOReqHandlerT = flip runReqHandlerT rhState
    where rhState = ReqHandlerState sendReqOut

runWebReq :: WebIO m => (ReqIn -> ReqHandlerT m ResOut) -> m (WebRes m)
runWebReq handler =
    do reqIn <- getReqIn
       case urlMatchPrefix' (reqIn_progUrl reqIn) (reqIn_fullUrl reqIn) of
         Just url ->
             do let reqIn' = reqIn { reqIn_fullUrl = url }
                res <- runWebReqHandlerT (handler reqIn')
                case res of
                  Left err -> webBadRequest (show err)
                  Right resOut -> sendResOut resOut
         Nothing -> webBadRequest $ "progUrl has to be prefix of fullUrl!"

match :: MonadError WebExc m => [ReqIn -> m ResOut] -> ReqIn -> m ResOut
match [] reqIn = throwError (WebExc comm err)
    where err = WebErrNoMatch reqIn
          comm = Just $ WebCommIn (WebIn (Just reqIn) Nothing)
match (f:fs) reqIn = catchError (f reqIn) handler
    where cont = match fs reqIn
          handler exc@(WebExc _ err) =
              case err of
                WebErrNoMatch {} -> cont
                WebErrInvalidUrl {} -> cont
                WebErrInvalidMethod {} -> cont
                _ -> throwError exc