-- -----------------------------------------------------------------------------
-- Copyright 2002, Simon Marlow.
-- Copyright 2006, Bjorn Bringert.
-- Copyright 2009, Henning Thielemann.
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
--  * Redistributions of source code must retain the above copyright notice,
--    this list of conditions and the following disclaimer.
--
--  * Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
--
--  * Neither the name of the copyright holder(s) nor the names of
--    contributors may be used to endorse or promote products derived from
--    this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- -----------------------------------------------------------------------------

module Network.MoHWS.Logger.Access (
    Handle,
    Request(..),
    start,
    stop,
    mkRequest,
    log,
  ) where

import qualified Network.MoHWS.Logger as Logger
import qualified Network.MoHWS.HTTP.Header as Header
import qualified Network.MoHWS.HTTP.Response as Response
import qualified Network.MoHWS.Server.Request as ServerRequest
import Network.MoHWS.Utility (formatTimeSensibly, )

import Network.BSD (HostEntry, hostName, )
import qualified Network.Socket as Socket
import System.Time (ClockTime, toUTCTime, getClockTime, TimeDiff, timeDiffToString, )
import Control.Monad (liftM, liftM2, )

import Prelude hiding (log, )


type Handle = Logger.Handle Request

{-
FIXME:
Instead of using body type ()
we should have data structures for the Response and Request headers
without the body,
like ResponseData and RequestData that are internally used in Network.HTTP.
-}
data Request = Request
   {
      request     :: ServerRequest.T (),
      response    :: Response.T (),
      serverHost  :: HostEntry,
      time        :: ClockTime,
      delay       :: TimeDiff
   }


start :: String -> FilePath -> IO Handle
start format file = Logger.start (mkLine format) file

{-
Instead of the class we could just use IO monad,
but I like to make explicit,
what are the functions that force us to do IO.
-}
class Monad m => Help m where
   inet_ntoa :: Socket.HostAddress -> m String

instance Help IO where
   inet_ntoa = Socket.inet_ntoa

infixr 5 +^+, ^:

(+^+) :: Monad m => m [a] -> m [a] -> m [a]
(+^+) = liftM2 (++)

(^:) :: Monad m => a -> m [a] -> m [a]
(^:) x = liftM (x:)

mkLine :: Help m => String -> Request -> m String
mkLine "" _ = return ""
mkLine ('%':'{':rest) r =
    case span (/= '}') rest of
      (str, '}':c:rest1) -> expand (Just str) c r +^+ mkLine rest1 r
      _                  -> '%' ^: '{' ^: mkLine rest r
mkLine ('%':c:rest) r = expand Nothing c r +^+ mkLine rest r
mkLine (c:rest) r = c ^: mkLine rest r

expand :: Help m => Maybe String -> Char -> Request -> m String
expand arg c info =
   let resp = response info
       sreq = request info
       req  = ServerRequest.clientRequest sreq
       -- host = clientName (log_request info)
       header _ Nothing  = ""
       header x (Just n) = unwords (Header.lookupMany (Header.makeName n) x)
       addr = inet_ntoa (ServerRequest.clientAddress sreq)
   in  case c of
         'b' -> return $ maybe "unknown" show $ Response.size (Response.body resp)
         'f' -> return $ ServerRequest.serverFilename sreq

         -- %h is the hostname if hostnameLookups is on, otherwise the
         -- IP address.
         'h' -> maybe addr (return . hostName) (ServerRequest.clientName sreq)
         'a' -> addr
         'l' -> return "-" -- FIXME: does anyone use identd these days?
         'r' -> return $ show req
         -- ToDo: 'p' -> canonical port number of server
         's' -> return $ show (Response.code resp)
         't' -> return $ formatTimeSensibly (toUTCTime (time info))
         'T' -> return $ timeDiffToString (delay info)
         'v' -> return $ hostName (serverHost info)
         'u' -> return "-" -- FIXME: implement HTTP auth

         'i' -> return $ header req arg
         'o' -> return $ header resp arg

         -- ToDo: other stuff
         _ -> return ['%',c]

stop :: Handle -> IO ()
stop l = Logger.stop l

mkRequest :: ServerRequest.T body -> Response.T body -> HostEntry -> TimeDiff -> IO Request
mkRequest req resp host delay0 =
    do time0 <- getClockTime
       return $
          Request {
             request     = fmap (const ()) req,
             response    = fmap (const ()) resp,
             serverHost  = host,
             time        = time0,
             delay       = delay0
          }

log :: Handle -> Request -> IO ()
log l r = Logger.log l r