module Network.Wai.Middleware.RequestLogger
    ( logStdout
    , logCallback
    , logStdoutDev
    , logCallbackDev
    
    , logHandle
    , logHandleDev
    ) where
import System.IO (stdout, hFlush)
import qualified Data.ByteString as BS
import Data.ByteString.Char8 (pack, unpack)
import Control.Monad.IO.Class (liftIO)
import Network.Wai (Request(..), Middleware, responseStatus, Response)
import System.Log.FastLogger
import Network.HTTP.Types as H
import Data.Maybe (fromMaybe)
import Network.Wai.Parse (sinkRequestBody, lbsBackEnd, fileName, Param, File, getRequestBodyType)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Conduit as C
import qualified Data.Conduit.List as CL
import System.Console.ANSI
import Data.IORef
import System.IO.Unsafe
logHandle :: (BS.ByteString -> IO ()) -> Middleware
logHandle = logCallback
logHandleDev :: (BS.ByteString -> IO ()) -> Middleware
logHandleDev = logCallbackDev
logStdout :: Middleware
logStdout = logCallback $ \bs -> hPutLogStr stdout [LB bs]
logStdoutDev :: Middleware
logStdoutDev = logCallbackDev $ \bs -> hPutLogStr stdout [LB bs] >> hFlush stdout
logCallback :: (BS.ByteString -> IO ()) 
            -> Middleware
logCallback cb app req = do
    rsp <- app req
    liftIO $ cb $ BS.concat
        [ requestMethod req
        , " "
        , rawPathInfo req
        , rawQueryString req
        , " "
        , "Accept: "
        , maybe "" toBS $ lookup "Accept" $ requestHeaders req
        , "\n"
        , "Status: "
        , statusBS rsp
        , " "
        , msgBS rsp
        ]
    return rsp
toBS :: H.Ascii -> BS.ByteString
toBS = id
colors :: IORef [Color]
colors = unsafePerformIO $ newIORef [
    Red 
  , Green 
  , Yellow 
  , Blue 
  , Magenta 
  , Cyan
  ]
rotateColors :: [Color] -> ([Color], Color)
rotateColors [] = error "Impossible! There must be colors!"
rotateColors (c:cs) = (cs ++ [c], c)
logCallbackDev :: (BS.ByteString -> IO ()) 
               -> Middleware
logCallbackDev cb app req = do
    let mlen = lookup "content-length" (requestHeaders req) >>= readInt
    (req', body) <-
        case mlen of
            
            Just len | len <= 2048 -> do
                 body <- requestBody req C.$$ CL.consume
                 
                 
                 let req' = req { requestBody = CL.sourceList body }
                 return (req', body)
            _ -> return (req, [])
    postParams <- if requestMethod req `elem` ["GET", "HEAD"]
      then return []
      else do postParams <- liftIO $ allPostParams body
              return $ collectPostParams postParams
    let getParams = map emptyGetParam $ queryString req
    color <- liftIO $ atomicModifyIORef colors rotateColors
    
    liftIO $ cb $ BS.concat $ ansiColor color (requestMethod req) ++
        [ " "
        , rawPathInfo req
        , "\n"
        , "Accept: "
        , fromMaybe "" $ lookup "Accept" $ requestHeaders req
        , paramsToBS  "GET " getParams
        , paramsToBS "POST " postParams
        , "\n"
        ]
    rsp <- app req'
    
    
    
    liftIO $ cb $ BS.concat $ ansiColor color "Status: " ++ [
          statusBS rsp
        , " "
        , msgBS rsp
        , ". "
        , rawPathInfo req 
        , "\n"
      ]
    return rsp
  where
    ansiColor color bs = [
        pack $ setSGRCode [SetColor Foreground Vivid color]
      , bs
      , pack $ setSGRCode [Reset]
      ]
    paramsToBS prefix params =
      if null params then ""
        else BS.concat ["\n", prefix, pack (show params)]
    allPostParams body =
        case getRequestBodyType req of
            Nothing -> return ([], [])
            Just rbt -> C.runResourceT $ CL.sourceList body C.$$ sinkRequestBody lbsBackEnd rbt
    emptyGetParam :: (BS.ByteString, Maybe BS.ByteString) -> (BS.ByteString, BS.ByteString)
    emptyGetParam (k, Just v) = (k,v)
    emptyGetParam (k, Nothing) = (k,"")
    collectPostParams :: ([Param], [File LBS.ByteString]) -> [Param]
    collectPostParams (postParams, files) = postParams ++
      map (\(k,v) -> (k, BS.append "FILE: " (fileName v))) files
    readInt bs =
        case reads $ unpack bs of
            (i, _):_ -> Just (i :: Int)
            [] -> Nothing
statusBS :: Response -> BS.ByteString
statusBS = pack . show . statusCode . responseStatus
msgBS :: Response -> BS.ByteString
msgBS = statusMessage . responseStatus