    -- | Parsec parsers and a general parsing interface for IRC messages
    module Network.IRC.Parser (
        -- * Parsing and Formatting Functions
        decode -- :: String -> Maybe Message

        -- * Parsec Combinators for Parsing IRC messages
      , prefix         -- :: Parser Prefix
      , serverPrefix   -- :: Parser Prefix
      , nicknamePrefix -- :: Parser Prefix
      , command        -- :: Parser Command
      , parameter      -- :: Parser Parameter
      , message        -- :: Parser Message
      , crlf           -- :: Parser ()
      , spaces         -- :: Parser ()

        -- * Deprecated Functions
      , parseMessage
      ) where

    import Network.IRC.Base

    import Data.Char
    import Data.Word
    import Data.ByteString hiding (elem, map, empty)

    import Control.Monad (void)
    import Control.Applicative
    import Data.Attoparsec.ByteString

    -- | Casts a character (assumed to be ASCII) to its corresponding byte.
    asciiToWord8 :: Char -> Word8
    asciiToWord8 = fromIntegral . ord

    wSpace :: Word8
    wSpace = asciiToWord8 ' '

    wTab :: Word8
    wTab = asciiToWord8 '\t'

    wBell :: Word8
    wBell = asciiToWord8 '\b'

    wDot :: Word8
    wDot = asciiToWord8 '.'

    wExcl :: Word8
    wExcl = asciiToWord8 '!'

    wAt :: Word8
    wAt = asciiToWord8 '@'

    wCR :: Word8
    wCR = asciiToWord8 '\r'

    wLF :: Word8
    wLF = asciiToWord8 '\n'

    wColon :: Word8
    wColon = asciiToWord8 ':'

    -- | Parse a String into a Message.
    decode :: ByteString    -- ^ Message string
           -> Maybe Message -- ^ Parsed message
    decode str = case parseOnly message str of
      Left _ -> Nothing
      Right r -> Just r

    -- | The deprecated version of decode
    parseMessage :: ByteString -> Maybe Message
    parseMessage  = decode

    -- | Convert a parser that consumes all space after it
    tokenize  :: Parser a -> Parser a
    tokenize p = p <* spaces

    -- | Consume only spaces, tabs, or the bell character
    spaces :: Parser ()
    spaces  = skip (\w -> w == wSpace ||
                          w == wTab ||
                          w == wBell)

    -- | Parse a Prefix
    prefix :: Parser Prefix
    prefix  = word8 wColon *> (try nicknamePrefix <|> serverPrefix)
              <?> "prefix"

    -- | Parse a Server prefix
    serverPrefix :: Parser Prefix
    serverPrefix  = Server <$> takeTill (== wSpace)
                    <?> "serverPrefix"

    -- | optionMaybe p tries to apply parser p. If p fails without consuming input,
    -- | it return Nothing, otherwise it returns Just the value returned by p.
    optionMaybe :: Parser a -> Parser (Maybe a)
    optionMaybe p = option Nothing (Just <$> p)

    -- | Parse a NickName prefix
    nicknamePrefix :: Parser Prefix
    nicknamePrefix  = do
      n <- takeTill (inClass " .!@\r\n")
      p <- peekWord8
      case p of
        Just c | c == wDot -> empty
        _                  -> NickName n <$>
                                    optionMaybe (word8 wExcl *> takeTill (\w -> w == wSpace ||
                                                                                w == wAt ||
                                                                                w == wCR ||
                                                                                w == wLF))
                                <*> optionMaybe (word8 wAt *> takeTill (\w -> w == wSpace ||
                                                                              w == wCR ||
                                                                              w == wLF))
      <?> "nicknamePrefix"

    isWordAsciiUpper :: Word8 -> Bool
    isWordAsciiUpper w = asciiToWord8 'A' <= w && w <= asciiToWord8 'Z'

    digit :: Parser Word8
    digit = satisfy (\w -> asciiToWord8 '0' <= w && w <= asciiToWord8 '9')

    -- | Parse a command.  Either a string of capital letters, or 3 digits.
    command :: Parser Command
    command  = takeWhile1 isWordAsciiUpper
            <|> digitsToByteString <$>
                       digit
                   <*> digit
                   <*> digit
            <?> "command"
        where digitsToByteString x y z = pack [x,y,z]

    -- | Parse a command parameter.
    parameter :: Parser Parameter
    parameter  =  (word8 wColon *> takeTill (\w -> w == wCR ||
                                                   w == wLF))
              <|> takeTill (\w -> w == wSpace ||
                                  w == wCR ||
                                  w == wLF)
              <?> "parameter"

    -- | Parse a cr lf
    crlf :: Parser ()
    crlf =  void (word8 wCR *> optional (word8 wLF))
        <|> void (word8 wLF)

    -- | Parse a Message
    message :: Parser Message
    message  = Message <$>
          optionMaybe (tokenize prefix)
      <*> command
      <*> many (spaces *> parameter)
      <*  optional crlf
      <*  endOfInput
      <?> "message"