{-# LANGUAGE DeriveDataTypeable #-}
{-
	Copyright (C) 2018 Dr. Alistair Ward

	This file is part of BishBosh.

	BishBosh is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	BishBosh is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with BishBosh.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]

	* Exceptions customised for this application.

	* N.B.: IO-functions can merely forward any exceptions thrown by the underlying calls, so these exceptions are typically thrown from pure functions.

	* CAVEAT: though intended to be orthogonal, there's some inevitable overlap.
-}

module BishBosh.Data.Exception(
-- * Types
-- ** Data-types
	BadData(),
	BadRequest(),
	Exception(
--		MkException,
		getType
--		getDetails
	),
-- * Functions
-- ** Constructors
	mkDuplicateData,
	mkIncompatibleData,
	mkInsufficientData,
	mkInvalidDatum,
	mkNullDatum,
	mkOutOfBounds,
	mkRedundantData,
	mkParseFailure,
	mkRequestFailure,
	mkResultUndefined,
	mkSearchFailure,
-- ** Predicates
	isBadData,
	isBadRequest
) where

import qualified	Control.Exception
import qualified	Data.Typeable

-- | These types of exception may be thrown by any function which checks its parameters; typically either constructors or mutators.
data BadData
	= DuplicateData		-- ^ Some data is duplicated.
	| IncompatibleData	-- ^ Two or more data with valid values, are incompatible. cf. InvalidDatum.
	| InsufficientData	-- ^ More data is required to fulfill the request. cf. 'NullDatum'.
	| InvalidDatum		-- ^ A datum's value is invalid.
	| NullDatum		-- ^ An empty collection was unexpectedly received; a specialisation of either 'InsufficientData' or 'InvalidDatum'.
	| OutOfBounds		-- ^ Either underflow or overflow of numeric data; a specialisation of 'InvalidDatum'.
	| RedundantData		-- ^ Data superflous to requirements was provided; a specialisation of 'InvalidDatum'.
	deriving Show

-- | These types of exception may be thrown by any function which is unable to comply with a correctly formed request.
data BadRequest
	= ParseFailure		-- ^ An attempt to parse data failed.
	| RequestFailure	-- ^ A well-formed request couldn't be completed.
	| ResultUndefined	-- ^ More than one correct result is possible.
	| SearchFailure		-- ^ An attempt to find data failed.
	deriving Show

-- | Each type of exception includes a type & a details.
data Exception	= MkException {
	getType		:: Either BadData BadRequest,
	getDetails	:: String
} deriving Data.Typeable.Typeable

instance Control.Exception.Exception Exception

instance Show Exception where
	showsPrec _ MkException {
		getType		= eitherBadDataOrBadRequest,
		getDetails	= details
	} = either shows shows eitherBadDataOrBadRequest . showString "; " . showString details

-- | Constructor.
mkDuplicateData :: String -> Exception
mkDuplicateData	= MkException $ Left DuplicateData

-- | Constructor.
mkIncompatibleData :: String -> Exception
mkIncompatibleData	= MkException $ Left IncompatibleData

-- | Constructor.
mkInsufficientData :: String -> Exception
mkInsufficientData	= MkException $ Left InsufficientData

-- | Constructor.
mkInvalidDatum :: String -> Exception
mkInvalidDatum	= MkException $ Left InvalidDatum

-- | Constructor.
mkNullDatum :: String -> Exception
mkNullDatum	= MkException $ Left NullDatum

-- | Constructor.
mkOutOfBounds :: String -> Exception
mkOutOfBounds	= MkException $ Left OutOfBounds

-- | Constructor.
mkRedundantData :: String -> Exception
mkRedundantData	= MkException $ Left RedundantData

-- | Constructor.
mkParseFailure :: String -> Exception
mkParseFailure	= MkException $ Right ParseFailure

-- | Constructor.
mkRequestFailure :: String -> Exception
mkRequestFailure	= MkException $ Right RequestFailure

-- | Constructor.
mkResultUndefined :: String -> Exception
mkResultUndefined	= MkException $ Right ResultUndefined

-- | Constructor.
mkSearchFailure :: String -> Exception
mkSearchFailure	= MkException $ Right SearchFailure

-- | Predicate.
isBadData :: Exception -> Bool
isBadData MkException { getType = Left _ }	= True
isBadData _					= False

-- | Predicate.
isBadRequest :: Exception -> Bool
isBadRequest MkException { getType = Right _ }	= True
isBadRequest _					= False