{-|
    Module      :  Numeric.CollectErrors
    Description :  A type of numeric errors to be collected
    Copyright   :  (c) Michal Konecny
    License     :  BSD3

    Maintainer  :  mikkonecny@gmail.com
    Stability   :  experimental
    Portability :  portable

    A type of numeric errors to be collected.
-}
module Numeric.CollectErrors
(
  -- * Type of numeric errors
  ErrorCertaintyLevel(..), NumError(..), NumErrors, sample_NumErrors
  -- * Specialisation to numeric errors
, CN
, hasCertainError, hasCertainErrorCN
, noValueCN
, noValueNumErrorCertainCN, noValueNumErrorPotentialCN
, getMaybeValueCN, getErrorsCN, prependErrorsCN
, CanEnsureCN, EnsureCN, EnsureNoCN
, ensureCN, deEnsureCN, ensureNoCN
, noValueECN, prependErrorsECN
, noValueNumErrorCertainECN, noValueNumErrorPotentialECN
, CanExtractCN, extractCN
  -- ** More compact synonyms
, cn, deCN, (~!)
)
where

import Prelude
  (Show(..), Eq(..), Bool, String, Maybe(..), Either(..), (++), (.), or, map, fst, ($))

import Control.CollectErrors

data NumError =
    DivByZero | OutOfRange String | NumError String
    deriving (Eq)

instance Show NumError where
  show DivByZero = "division by 0"
  show (OutOfRange s) = "out of range: " ++ s
  show (NumError s) = "numeric error: " ++ s

data ErrorCertaintyLevel =
  ErrorCertain | ErrorPotential
  deriving (Eq)

instance Show ErrorCertaintyLevel where
  show ErrorCertain = "ERROR"
  show ErrorPotential = "POTENTIAL ERROR"

type NumErrors = [(ErrorCertaintyLevel, NumError)]

instance CanTestErrorsCertain NumErrors where
  hasCertainError es =
    or $ map ((== ErrorCertain) . fst) es

hasCertainErrorCN :: CN v -> Bool
hasCertainErrorCN = hasCertainErrorCE

sample_NumErrors :: Maybe [(ErrorCertaintyLevel, NumError)]
sample_NumErrors = Nothing

type CN = CollectErrors NumErrors
type CanEnsureCN = CanEnsureCE NumErrors
type EnsureCN a = EnsureCE NumErrors a
type EnsureNoCN a = EnsureNoCE NumErrors a

type CanExtractCN f = CanExtractCE NumErrors f
extractCN ::
  (CanEnsureCN c, CanExtractCN f) =>
  f c -> CN (f (EnsureNoCN c))
extractCN = extractCE sample_NumErrors

{-|
  Translate a value of a type @a@
  to a value of a type @CollectNumErrors a@ except when @a@
  already is a @CollectNumErrors@ type, in which case the value is left as is.
-}
ensureCN :: (CanEnsureCN v) => v -> EnsureCN v
ensureCN = ensureCE sample_NumErrors

{-|
  Translate a value of a type @EnsureCN es a@ to @a@,
  throwing an exception if there was an error.
  If @a@ is a @CollectNumErrors@ type, then this is just an identity.
-}
deEnsureCN :: (CanEnsureCN v) => EnsureCN v -> Either NumErrors v
deEnsureCN = deEnsureCE sample_NumErrors

{-|
  Translate a value of a type @a@
  to a value of a type @CollectNumErrors a@ except when @a@
  already is a @CollectNumErrors@ type, in which case the value is left as is.
-}
ensureNoCN :: (CanEnsureCN v) => v -> (Maybe (EnsureNoCN v), NumErrors)
ensureNoCN = ensureNoCE sample_NumErrors

noValueECN :: (CanEnsureCN v) => Maybe v -> NumErrors -> EnsureCN v
noValueECN = noValueECE

prependErrorsECN :: (CanEnsureCN v) => Maybe v -> NumErrors -> EnsureCN v -> EnsureCN v
prependErrorsECN = prependErrorsECE

{-| Construct an empty wrapper indicating that given error has certainly occurred. -}
noValueNumErrorCertainECN :: (CanEnsureCN v) => Maybe v -> NumError -> EnsureCN v
noValueNumErrorCertainECN sample_v e = noValueECE sample_v [(ErrorCertain, e)]

{-| Construct an empty wrapper indicating that given error may have occurred. -}
noValueNumErrorPotentialECN :: (CanEnsureCN v) => Maybe v -> NumError -> EnsureCN v
noValueNumErrorPotentialECN sample_v e = noValueECE sample_v [(ErrorPotential, e)]

getErrorsCN :: CN v -> NumErrors
getErrorsCN = getErrorsCE

getMaybeValueCN :: CN v -> Maybe v
getMaybeValueCN = getMaybeValueCE

noValueCN :: NumErrors -> CN v
noValueCN = noValueCE

{-| Construct an empty wrapper indicating that given error has certainly occurred. -}
noValueNumErrorCertainCN :: NumError -> CN v
noValueNumErrorCertainCN e = noValueCN [(ErrorCertain, e)]

{-| Construct an empty wrapper indicating that given error may have occurred. -}
noValueNumErrorPotentialCN :: NumError -> CN v
noValueNumErrorPotentialCN e = noValueCN [(ErrorPotential, e)]

prependErrorsCN :: NumErrors -> CN v -> CN v
prependErrorsCN = prependErrorsCE

-- more compact synonyms:

{-| Wrap a value in the 'CollectNumErrors' wrapper. -}
cn :: (CanEnsureCN v) => v -> EnsureCN v
cn = ensureCN

{-| An unsafe way to get a value out of the CollectNumErrors wrapper. -}
deCN :: (CanEnsureCN v) => EnsureCN v -> Either NumErrors v
deCN = deEnsureCN

{-| An unsafe way to get a value out of the CollectNumErrors wrapper. -}
(~!) :: (CanEnsureCN v, Show v) => v -> EnsureNoCN v
(~!) = getValueOrThrowErrorsNCE sample_NumErrors