module Sound.JACK.Exception (
    ToString(toString), toStringWithHead,
    All,
    ThrowsStatus       (status),       Status(..),
    ThrowsPortRegister (portRegister), PortRegister(..),
    ThrowsPortMismatch (portMismatch), PortMismatch(..), PortMismatchKind(..),
    ThrowsErrno        (errno),        Errno(..),
    ) where

import qualified Sound.JACK.FFI as JackFFI

import qualified Foreign.C.Error as FE
import qualified Data.EnumSet as ES
import Data.Bits (Bits, )



type All = Status (PortRegister (PortMismatch FE.Errno))

toStringWithHead ::
    All -> String
toStringWithHead e =
    "JACK exception: " ++ toString e

class ToString e where
    toString :: e -> String

instance (Bits w, Enum a, Show a) => ToString (ES.T w a) where
    toString = show . ES.toEnums


class ThrowsStatus e where
    status :: JackFFI.StatusSet -> e

data Status e =
     Status JackFFI.StatusSet
   | NoStatus e

instance ToString e => ToString (Status e) where
    toString e0 =
        case e0 of
            Status st -> toString st
            NoStatus e1 -> toString e1


instance ThrowsStatus (Status e) where
    status = Status

instance ThrowsPortRegister e => ThrowsPortRegister (Status e) where
    portRegister = NoStatus portRegister

instance ThrowsPortMismatch e => ThrowsPortMismatch (Status e) where
    portMismatch = NoStatus . portMismatch

instance ThrowsErrno e => ThrowsErrno (Status e) where
    errno = NoStatus . errno

{-
instance ThrowsStatus (ES.T w a) where
    status = id
-}


class ThrowsPortRegister e where
    portRegister :: e

data PortRegister e =
     PortRegister
   | NoPortRegister e

instance ToString e => ToString (PortRegister e) where
    toString e0 =
        case e0 of
          PortRegister -> "could not register port"
          NoPortRegister e1 -> toString e1

instance ThrowsStatus e => ThrowsStatus (PortRegister e) where
    status = NoPortRegister . status

instance ThrowsPortRegister (PortRegister e) where
    portRegister = PortRegister

instance ThrowsPortMismatch e => ThrowsPortMismatch (PortRegister e) where
    portMismatch = NoPortRegister . portMismatch

instance ThrowsErrno e => ThrowsErrno (PortRegister e) where
    errno = NoPortRegister . errno



class ThrowsPortMismatch e where
    portMismatch :: PortMismatchKind -> e

data PortMismatch e =
     PortMismatch PortMismatchKind
   | NoPortMismatch e

data PortMismatchKind = TypeMismatch | DirectionMismatch

instance ToString e => ToString (PortMismatch e) where
    toString e0 =
        case e0 of
          PortMismatch TypeMismatch -> "port not of excpected type"
          PortMismatch DirectionMismatch -> "port does not support excpected direction"
          NoPortMismatch e1 -> toString e1

instance ThrowsStatus e => ThrowsStatus (PortMismatch e) where
    status = NoPortMismatch . status

instance ThrowsPortRegister e => ThrowsPortRegister (PortMismatch e) where
    portRegister = NoPortMismatch portRegister

instance ThrowsPortMismatch (PortMismatch e) where
    portMismatch = PortMismatch

instance ThrowsErrno e => ThrowsErrno (PortMismatch e) where
    errno = NoPortMismatch . errno



class ThrowsErrno e where
    errno :: FE.Errno -> e

data Errno e =
     Errno FE.Errno
   | NoErrno e

instance ToString e => ToString (Errno e) where
    toString e0 =
        case e0 of
          Errno en -> toString en
          NoErrno e1 -> toString e1

instance ToString FE.Errno where
    toString (FE.Errno en) = "error code " ++ show en

instance ThrowsStatus e => ThrowsStatus (Errno e) where
    status = NoErrno . status

instance ThrowsPortRegister e => ThrowsPortRegister (Errno e) where
    portRegister = NoErrno portRegister

instance ThrowsPortMismatch e => ThrowsPortMismatch (Errno e) where
    portMismatch = NoErrno . portMismatch

instance ThrowsErrno (Errno e) where
    errno = Errno

instance ThrowsErrno FE.Errno where
    errno = id