    {- |
    Parser which handles the running state
    that is used in MIDI messages in realtime and files.
    The running state consists of a message code and the message channel.
    -}
    module Sound.MIDI.Parser.Status
       (T, Status, set, get, run, lift,
        Channel, fromChannel,   toChannel, ) where

    import qualified Sound.MIDI.Parser.Class as Parser

    import qualified Control.Monad.Exception.Synchronous  as Sync
    import Control.Monad.Trans.State (StateT, evalStateT, )
    import qualified Control.Monad.Trans.State as State
    import qualified Control.Monad.Trans.Class as Trans

    import Sound.MIDI.Utility (checkRange, )
    import Data.Ix (Ix)

    {- |
    The 'T' monad parses a track of a MIDI File.
    In MIDI, a shortcut is used for long strings of similar MIDI events:
    If a stream of consecutive events all have the same type and channel,
    the type and channel can be omitted for all but the first event.
    To implement this /feature/,
    the parser must keep track of the type and channel of the most recent MIDI Event.
    This is done by managing a 'Status' in the parser.
    -}
    type T parser = StateT Status parser

    type Status = Maybe (Int,Channel)

    set :: Monad parser => Status -> Parser.Fallible (T parser) ()
    set = Trans.lift . State.put

    get :: Monad parser => Parser.Fallible (T parser) Status
    get = Trans.lift State.get

    run :: Monad parser => T parser a -> parser a
    run = flip evalStateT Nothing

    lift :: Monad parser => Parser.Fallible parser a -> Parser.Fallible (T parser) a
    lift = Sync.mapExceptionalT Trans.lift

    -- * Channel definition

    {- |
    This definition should be in Message.Channel,
    but this results in a cyclic import.
    -}
    newtype Channel     = Channel  {fromChannel  :: Int} deriving (Show, Eq, Ord, Ix)

    toChannel :: Int -> Channel
    toChannel = checkRange "Channel" Channel

    instance Enum Channel where
       toEnum   = toChannel
       fromEnum = fromChannel

    instance Bounded Channel where
       minBound = Channel  0
       maxBound = Channel 15