    -- | This module provides parsers for mouse events for both "normal" and
    -- "extended" modes. This implementation was informed by
    --
    -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
    module Graphics.Vty.Input.Mouse
      ( requestMouseEvents
      , disableMouseEvents
      , isMouseEvent
      , classifyMouseEvent
      )
    where

    import Graphics.Vty.Input.Events
    import Graphics.Vty.Input.Classify.Types
    import Graphics.Vty.Input.Classify.Parse

    import Control.Monad.State
    import Data.List (isPrefixOf)
    import Data.Maybe (catMaybes)
    import Data.Bits ((.&.))

    -- A mouse event in SGR extended mode is
    --
    -- '\ESC' '[' '<' B ';' X ';' Y ';' ('M'|'m')
    --
    -- where
    --
    -- * B is the number with button and modifier bits set,
    -- * X is the X coordinate of the event starting at 1
    -- * Y is the Y coordinate of the event starting at 1
    -- * the final character is 'M' for a press, 'm' for a release

    -- | These sequences set xterm-based terminals to send mouse event
    -- sequences.
    requestMouseEvents :: String
    requestMouseEvents = "\ESC[?1000h\ESC[?1002h\ESC[?1006h"

    -- | These sequences disable mouse events.
    disableMouseEvents :: String
    disableMouseEvents = "\ESC[?1000l\ESC[?1002l\ESC[?1006l"

    -- | Does the specified string begin with a mouse event?
    isMouseEvent :: String -> Bool
    isMouseEvent s = isSGREvent s || isNormalEvent s

    isSGREvent :: String -> Bool
    isSGREvent = isPrefixOf sgrPrefix

    sgrPrefix :: String
    sgrPrefix = "\ESC[M"

    isNormalEvent :: String -> Bool
    isNormalEvent = isPrefixOf normalPrefix

    normalPrefix :: String
    normalPrefix = "\ESC[<"

    -- Modifier bits:
    shiftBit :: Int
    shiftBit = 4

    metaBit :: Int
    metaBit = 8

    ctrlBit :: Int
    ctrlBit = 16

    -- These bits indicate the buttons involved:
    buttonMask :: Int
    buttonMask = 67

    leftButton :: Int
    leftButton = 0

    middleButton :: Int
    middleButton = 1

    rightButton :: Int
    rightButton = 2

    scrollUp :: Int
    scrollUp = 64

    scrollDown :: Int
    scrollDown = 65

    hasBitSet :: Int -> Int -> Bool
    hasBitSet val bit = val .&. bit > 0

    -- | Attempt to lassify an input string as a mouse event.
    classifyMouseEvent :: String -> KClass
    classifyMouseEvent s = runParser s $ do
        when (not $ isMouseEvent s) failParse

        expectChar '\ESC'
        expectChar '['
        ty <- readChar
        case ty of
            '<' -> classifySGRMouseEvent
            'M' -> classifyNormalMouseEvent
            _   -> failParse

    -- Given a modifer/button value, determine which button was indicated
    getSGRButton :: Int -> Parser Button
    getSGRButton mods =
        let buttonMap = [ (leftButton,   BLeft)
                        , (middleButton, BMiddle)
                        , (rightButton,  BRight)
                        , (scrollUp,     BScrollUp)
                        , (scrollDown,   BScrollDown)
                        ]
        in case lookup (mods .&. buttonMask) buttonMap of
            Nothing -> failParse
            Just b -> return b

    getModifiers :: Int -> [Modifier]
    getModifiers mods =
        catMaybes [ if mods `hasBitSet` shiftBit then Just MShift else Nothing
                  , if mods `hasBitSet` metaBit  then Just MMeta  else Nothing
                  , if mods `hasBitSet` ctrlBit  then Just MCtrl  else Nothing
                  ]

    -- Attempt to classify a control sequence as a "normal" mouse event. To
    -- get here we should have already read "\ESC[M" so that will not be
    -- included in the string to be parsed.
    classifyNormalMouseEvent :: Parser Event
    classifyNormalMouseEvent = do
        statusChar <- readChar
        xCoordChar <- readChar
        yCoordChar <- readChar

        let xCoord = fromEnum xCoordChar - 32
            yCoord = fromEnum yCoordChar - 32
            status = fromEnum statusChar
            modifiers = getModifiers status

        let press = status .&. buttonMask /= 3
        case press of
                True -> do
                    button <- getSGRButton status
                    return $ EvMouseDown (xCoord-1) (yCoord-1) button modifiers
                False -> return $ EvMouseUp (xCoord-1) (yCoord-1) Nothing

    -- Attempt to classify a control sequence as an SGR mouse event. To
    -- get here we should have already read "\ESC[<" so that will not be
    -- included in the string to be parsed.
    classifySGRMouseEvent :: Parser Event
    classifySGRMouseEvent = do
        mods <- readInt
        expectChar ';'
        xCoord <- readInt
        expectChar ';'
        yCoord <- readInt
        final <- readChar

        let modifiers = getModifiers mods
        button <- getSGRButton mods
        case final of
            'M' -> return $ EvMouseDown (xCoord-1) (yCoord-1) button modifiers
            'm' -> return $ EvMouseUp   (xCoord-1) (yCoord-1) (Just button)
            _ -> failParse