{- |
Parse primitive types contained in MIDI files.
-}
module Sound.MIDI.Parser.Primitive
   (getByte,
    getN, getString, getBigN, getNByteInt,
    get1, get2, get3, get4,
    getNByteCardinal,
    getVar, getVarBytes,
    getEnum, makeEnum, ) where

import qualified Sound.MIDI.Parser.Class as Parser
import Control.Monad (replicateM, liftM, )

import Sound.MIDI.IO (ByteList, listCharFromByte, )
import qualified Sound.MIDI.Bit as Bit
import Data.Bits (testBit, clearBit)
import Data.Word (Word8)
import qualified Numeric.NonNegative.Wrapper as NonNeg



{- |
'getByte' gets a single byte from the input.
-}
getByte :: Parser.C parser => Parser.Fallible parser Word8
getByte = Parser.getByte


{- |
@getN n@ returns n characters (bytes) from the input.
-}
getN :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser ByteList
getN n = replicateM (NonNeg.toNumber n) getByte

getString :: Parser.C parser => NonNeg.Integer -> Parser.Fallible parser String
getString n = liftM listCharFromByte (getBigN n)

getBigN :: Parser.C parser => NonNeg.Integer -> Parser.Fallible parser ByteList
getBigN n =
   sequence $
   Bit.replicateBig
      (1 + fromIntegral (maxBound :: NonNeg.Int))
      (NonNeg.toNumber n)
      getByte


{- |
'get1', 'get2', 'get3', and 'get4' take 1-, 2-, 3-, or
4-byte numbers from the input (respectively), convert the base-256 data
into a single number, and return.
-}
get1 :: Parser.C parser => Parser.Fallible parser Int
get1 = liftM fromIntegral getByte

getNByteInt :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser Int
getNByteInt n =
   liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) get1)

get2, get3, get4 :: Parser.C parser => Parser.Fallible parser Int
get2 = getNByteInt 2
get3 = getNByteInt 3
get4 = getNByteInt 4

getByteAsCardinal :: Parser.C parser => Parser.Fallible parser NonNeg.Integer
getByteAsCardinal = liftM fromIntegral getByte

getNByteCardinal :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser NonNeg.Integer
getNByteCardinal n =
   liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) getByteAsCardinal)

{- |
/Variable-length quantities/ are used often in MIDI notation.
They are represented in the following way:
Each byte (containing 8 bits) uses the 7 least significant bits to store information.
The most significant bit is used to signal whether or not more information is coming.
If it's @1@, another byte is coming.
If it's @0@, that byte is the last one.
'getVar' gets a variable-length quantity from the input.
-}
getVar :: Parser.C parser => Parser.Fallible parser NonNeg.Integer
getVar =
   liftM (Bit.fromBase (2^(7::Int)) . map fromIntegral) getVarBytes

{- |
The returned list contains only bytes with the most significant bit cleared.
These are digits of a 128-ary number.
-}
getVarBytes :: Parser.C parser => Parser.Fallible parser [Word8]
getVarBytes =
   do
      digit <- getByte
      if flip testBit 7 digit            -- if it's the last byte
        then liftM (flip clearBit 7 digit :) getVarBytes
        else return [digit]


getEnum :: (Parser.C parser, Enum enum, Bounded enum) => Parser.Fallible parser enum
getEnum = makeEnum =<< get1

makeEnum :: (Parser.C parser, Enum enum, Bounded enum) => Int -> Parser.Fallible parser enum
makeEnum n =
   let go :: (Parser.C parser, Enum a) => a -> a -> Parser.Fallible parser a
       go lower upper =
          if fromEnum lower <= n && n <= fromEnum upper
            then return (toEnum n)
            else Parser.giveUp ("value " ++ show n ++ " is out of range for enumeration")
   in  go minBound maxBound