{-# LANGUAGE DeriveDataTypeable, ExistentialQuantification #-}

-- |Monadic and General Iteratees:
-- Messaging and exception handling.
--
-- Iteratees use an internal exception handling mechanism that is parallel to
-- that provided by 'Control.Exception'.  This allows the iteratee framework
-- to handle its own exceptions outside @IO@.
--
-- Iteratee exceptions are divided into two categories, 'IterException' and
-- 'EnumException'.  @IterExceptions@ are exceptions within an iteratee, and
-- @EnumExceptions@ are exceptions within an enumerator.
--
-- Enumerators can be constructed to handle an 'IterException' with
-- @Data.Iteratee.Iteratee.enumFromCallbackCatch@.  If the enumerator detects
-- an @iteratee exception@, the enumerator calls the provided exception handler.
-- The enumerator is then able to continue feeding data to the iteratee,
-- provided the exception was successfully handled.  If the handler could
-- not handle the exception, the 'IterException' is converted to an
-- 'EnumException' and processing aborts.
--
-- Exceptions can also be cleared by @Data.Iteratee.Iteratee.checkErr@,
-- although in this case the iteratee continuation cannot be recovered.
--
-- When viewed as Resumable Exceptions, iteratee exceptions provide a means
-- for iteratees to send control messages to enumerators.  The @seek@
-- implementation provides an example.  @Data.Iteratee.Iteratee.seek@ stores
-- the current iteratee continuation and throws a 'SeekException', which
-- inherits from 'IterException'.  @Data.Iteratee.IO.enumHandleRandom@ is
-- constructed with @enumFromCallbackCatch@ and a handler that performs
-- an @hSeek@.  Upon receiving the 'SeekException', @enumHandleRandom@ calls
-- the handler, checks that it executed properly, and then continues with
-- the stored continuation.
--
-- As the exception hierarchy is open, users can extend it with custom
-- exceptions and exception handlers to implement sophisticated messaging
-- systems based upon resumable exceptions.


module Data.Iteratee.Exception (
  -- * Exception types
  IFException (..)
  ,Exception (..)             -- from Control.Exception
  -- ** Enumerator exceptions
  ,EnumException (..)
  ,DivergentException (..)
  ,EnumStringException (..)
  ,EnumUnhandledIterException (..)
  -- ** Iteratee exceptions
  ,IException (..)
  ,IterException (..)
  ,SeekException (..)
  ,EofException (..)
  ,IterStringException (..)
  -- * Functions
  ,enStrExc
  ,iterStrExc
  ,wrapIterExc
  ,iterExceptionToException
  ,iterExceptionFromException
)
where

import Data.Iteratee.IO.Base

import Control.Exception
import Data.Data


-- ----------------------------------------------
-- create exception type hierarchy

-- |Root of the Iteratee exception hierarchy.  @IFException@ derives from
-- @Control.Exception.SomeException@.  'EnumException', 'IterException',
-- and all inheritants are descendents of 'IFException'.
data IFException = forall e . Exception e => IFException e
  deriving Typeable

instance Show IFException where
  show (IFException e) = show e

instance Exception IFException

ifExceptionToException :: Exception e => e -> SomeException
ifExceptionToException = toException . IFException

ifExceptionFromException :: Exception e => SomeException -> Maybe e
ifExceptionFromException x = do
  IFException a <- fromException x
  cast a

-- Root of enumerator exceptions.
data EnumException = forall e . Exception e => EnumException e
  deriving Typeable

instance Show EnumException where
  show (EnumException e) = show e

instance Exception EnumException where
  toException   = ifExceptionToException
  fromException = ifExceptionFromException

enumExceptionToException :: Exception e => e -> SomeException
enumExceptionToException = toException . IterException

enumExceptionFromException :: Exception e => SomeException -> Maybe e
enumExceptionFromException x = do
  IterException a <- fromException x
  cast a

-- |The @iteratee@ diverged upon receiving 'EOF'.
data DivergentException = DivergentException
  deriving (Show, Typeable)

instance Exception DivergentException where
  toException   = enumExceptionToException
  fromException = enumExceptionFromException

-- |Create an enumerator exception from a @String@.
data EnumStringException = EnumStringException String
  deriving (Show, Typeable)

instance Exception EnumStringException where
  toException   = enumExceptionToException
  fromException = enumExceptionFromException

-- |Create an 'EnumException' from a string.
enStrExc :: String -> EnumException
enStrExc = EnumException . EnumStringException

-- |The enumerator received an 'IterException' it could not handle.
data EnumUnhandledIterException = EnumUnhandledIterException IterException
  deriving (Show, Typeable)

instance Exception EnumUnhandledIterException where
  toException   = enumExceptionToException
  fromException = enumExceptionFromException

-- |Convert an 'IterException' to an 'EnumException'.  Meant to be used
-- within an @Enumerator@ to signify that it could not handle the
-- @IterException@.
wrapIterExc :: IterException -> EnumException
wrapIterExc = EnumException . EnumUnhandledIterException

-- iteratee exceptions

-- |A class for @iteratee exceptions@.  Only inheritants of @IterException@
-- should be instances of this class.
class Exception e => IException e where
  toIterException   :: e -> IterException
  toIterException   = IterException
  fromIterException :: IterException -> Maybe e
  fromIterException = fromException . toException

-- |Root of iteratee exceptions.
data IterException = forall e . Exception e => IterException e
  deriving Typeable

instance Show IterException where
  show (IterException e) = show e

instance Exception IterException where
  toException   = ifExceptionToException
  fromException = ifExceptionFromException

iterExceptionToException :: Exception e => e -> SomeException
iterExceptionToException = toException . IterException

iterExceptionFromException :: Exception e => SomeException -> Maybe e
iterExceptionFromException x = do
  IterException a <- fromException x
  cast a

instance IException IterException where
  toIterException   = id
  fromIterException = Just

-- |A seek request within an @Iteratee@.
data SeekException = SeekException FileOffset
  deriving (Typeable, Show)

instance Exception SeekException where
  toException   = iterExceptionToException
  fromException = iterExceptionFromException

instance IException SeekException where

-- |The @Iteratee@ needs more data but received @EOF@.
data EofException = EofException
  deriving (Typeable, Show)

instance Exception EofException where
  toException   = iterExceptionToException
  fromException = iterExceptionFromException

instance IException EofException where

-- |An @Iteratee exception@ specified by a @String@.
data IterStringException = IterStringException String deriving (Typeable, Show)

instance Exception IterStringException where
  toException   = iterExceptionToException
  fromException = iterExceptionFromException

instance IException IterStringException where

-- |Create an @iteratee exception@ from a string.
-- This convenience function wraps 'IterStringException' and 'toException'.
iterStrExc :: String -> SomeException
iterStrExc= toException . IterStringException