-------------------------------------------------------------------------------
-- |
-- Module      :  CCO.Feedback
-- Copyright   :  (c) 2008 Utrecht University
-- License     :  All rights reserved
--
-- Maintainer  :  stefan@cs.uu.nl
-- Stability   :  provisional
-- Portability :  portable
--
-- A monad for keeping track of log, warning, and error messages.
--
-------------------------------------------------------------------------------

module CCO.Feedback (
    -- * Messages
    Message (Log, Warning, Error)
  , isError                          -- :: Message -> Bool
  , fromMessage                      -- :: Message -> Doc

    -- * The Feedback monad
  , Feedback                         -- abstract, instances: Functor, Monad
  , trace                            -- :: Int -> String -> Feedback ()
  , trace_                           -- :: String -> Feedback ()
  , warn                             -- :: Int -> String -> Feedback ()
  , warn_                            -- :: String -> Feedback ()
  , errorMessage                     -- :: Doc -> Feedback ()
  , message                          -- :: Message -> Feedback ()
  , messages                         -- :: [Message] -> Feedback ()
  , wError                           -- :: Feedback a -> Feedback a
  , succeeding                       -- :: Feedback a -> Bool
  , failing                          -- :: Feedback a -> Bool
  , runFeedback                      -- :: Feedback a -> Handle -> IO (Maybe a)
) where

import CCO.Feedback.Message
import CCO.Printing          (Doc, text)
import System.IO             (Handle)
import Control.Monad
import Control.Applicative

-------------------------------------------------------------------------------
-- The Feedback monad
-------------------------------------------------------------------------------

-- | The @Feedback@ monad.
-- Keeps track of 'Message's, failing if an 'Error' message is encountered.
data Feedback a
  = Succeed [Message] a
  | Fail [Message]

instance Functor Feedback where
  fmap f (Succeed msgs x) = Succeed msgs (f x)
  fmap _ (Fail msgs)      = Fail msgs

instance Monad Feedback where
  return x = Succeed [] x

  Succeed msgs x >>= f = case f x of
                           Succeed msgs' y -> Succeed (msgs ++ msgs') y
                           Fail msgs'      -> Fail (msgs ++ msgs')
  Fail msgs      >>= _ = Fail msgs

  fail msg             = Fail [Error (text msg)]

instance Applicative Feedback where
  pure = return
  (<*>) = ap

-- | Issues a list of 'Message's.
-- Fails if the list contains an 'Error' message.
messages :: [Message] -> Feedback ()
messages msgs | any isError msgs = Fail msgs
              | otherwise        = Succeed msgs ()

-- | Issues a 'Message'.
-- Fails if an 'Error' message is issued.
message :: Message -> Feedback ()
message msg = messages [msg]

-- | Issues an 'Error' message.
errorMessage :: Doc -> Feedback a
errorMessage doc = Fail [Error doc]

-- | Issues a 'Log' message at a specified verbosity level containing a
-- specified text.
trace :: Int -> String -> Feedback ()
trace v = message . Log v . text

-- | Issues a 'Log' message at the default verbosity level 1 containing a
-- specified text.
trace_ :: String -> Feedback ()
trace_ = trace 1

-- | Issues a 'Warning' message at a specified severity level containing a
-- specified text.
warn :: Int -> String -> Feedback ()
warn w = message . Warning w . text

-- | Issues a 'Warning' message at the default severity level 1 containing a 
-- specified text.
warn_ :: String -> Feedback ()
warn_ = warn 1

-- | Turns all 'Warning' messages into 'Error' messages.
wError :: Feedback a -> Feedback a
wError (Fail msgs)      = Fail (fatalizeWarnings msgs)
wError (Succeed msgs x) = let msgs' = fatalizeWarnings msgs
                          in  if   any isError msgs'
                              then Fail msgs'
                              else Succeed msgs' x

-- | Retrieves whether a 'Feedback' computation will succeed.
succeeding :: Feedback a -> Bool
succeeding (Succeed _ _) = True
succeeding _             = False

-- | Retrieves whether a 'Feedback' computation will fail.
failing :: Feedback a -> Bool
failing (Fail _) = True
failing _        = False

-- | Runs a 'Feedback' computation at a specified verbosity and severity level,
-- pretty printing messages onto a specified
-- 'Handle'.
runFeedback :: Feedback a -> Int -> Int -> Handle -> IO (Maybe a)
runFeedback (Succeed msgs x) v w h = do let msgs' = filterMessages v w msgs
                                        putMessages h msgs'
                                        return (Just x)
runFeedback (Fail msgs)      v w h = do let msgs' = filterMessages v w msgs
                                        putMessages h msgs'
                                        return Nothing