-- -----------------------------------------------------------------------------
-- 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 (
    Handle,
    start,
    stop,
    log,
   ) where

import Network.MoHWS.Utility (dirname, )

import qualified Control.Exception as Exception
import Control.Exception (SomeException(SomeException), )
import Control.Concurrent (Chan, ThreadId, newChan, forkIO, writeChan, readChan, )
import System.Directory (createDirectoryIfMissing, )
import System.IO (IOMode(AppendMode), hPutStrLn, stderr, hClose, hFlush, )
import qualified System.IO as IO

import Prelude hiding (log, )


data Handle a = Handle
    {
     handleChan     :: Chan (Command a),
     handleThreadId :: ThreadId
    }

data T a = Cons
    {
     chan     :: Chan (Command a),
     format   :: (a -> IO String),
     file     :: FilePath
    }

data Command a = Stop | Log a

start ::
      (a -> IO String) -- ^ Message formatting function
   -> FilePath         -- ^ log file path
   -> IO (Handle a)
start format0 file0 =
    do chan0 <- newChan
       createDirectoryIfMissing True (dirname file0)
       let l = Cons {
                chan = chan0,
                format = format0,
                file = file0
               }
       t <- forkIO $
          run l
          `Exception.catch`
          \(SomeException e) ->
              hPutStrLn stderr
                 ("Error starting logger: " ++ show e)
       return $
          Handle {
             handleChan = chan0,
             handleThreadId = t
          }

stop :: Handle a -> IO ()
stop l = writeChan (handleChan l) Stop

log :: Handle a -> a -> IO ()
log l x = writeChan (handleChan l) (Log x)

-- Internals

run :: T a -> IO ()
run l =
   run1 l
   `Exception.catch`
   \(SomeException e) ->
      do hPutStrLn stderr ("Logger died: " ++ show e)
         run l

run1 :: T a -> IO ()
run1 l =
    Exception.bracket
      (openFile (file l))
      (\hdl -> hClose hdl)
      (\hdl -> handleCommands l hdl)
  where
    openFile :: FilePath -> IO IO.Handle
    openFile f =
        IO.openFile f AppendMode
        `Exception.catch`
        \(SomeException e) ->
           do hPutStrLn stderr ("Failed to open log file: " ++ show e)
              Exception.throw e

handleCommands :: T a -> IO.Handle -> IO ()
handleCommands l hdl =
    do comm <- readChan (chan l)
       case comm of
         Stop -> return ()
         Log x ->
            do writeLine hdl =<< format l x
               handleCommands l hdl
  where
    writeLine :: IO.Handle -> String -> IO ()
    writeLine hndl str =
       do hPutStrLn hndl str
          hFlush hndl