module System.Terminal.MonadInput where

import           Control.Applicative ((<|>))
import           Control.Monad.IO.Class
import           Control.Monad.STM
import           Data.Bits
import           Data.List

import           System.Terminal.MonadScreen

-- | This monad describes an environment that maintains a stream of `Event`s
--   and offers out-of-band signaling for interrupts.
--
--  * An interrupt shall occur if the user either presses CTRL+C
--    or any other mechanism the environment designates for that purpose.
--  * Implementations shall maintain an interrupt flag that is set
--    when an interrupt occurs. Computations in this monad shall check and
--    reset this flag regularly. If the execution environment finds this
--    flag still set when trying to signal another interrupt, it shall
--    throw `Control.Exception.AsyncException.UserInterrupt` to the
--    seemingly unresponsive computation.
class (MonadIO m) => MonadInput m where
    -- | Wait for the next interrupt or next event transformed by a given mapper.
    --
    -- * The first mapper parameter is a transaction that succeeds as
    --   soon as an interrupt occurs. Executing this transaction
    --   resets the interrupt flag. When a second interrupt occurs before
    --   the interrupt flag has been reset, the current thread shall
    --   receive an `Control.Exception.AsyncException.UserInterrupt`.
    -- * The second mapper parameter is a transaction that succeeds as
    --   as soon as the next event arrives and removes that event from the
    --   stream of events. It shall be executed at most once within a single
    --   transaction or the transaction would block until the requested number
    --   of events is available.
    -- * The mapper may also be used in order to additionally wait on external
    --   events (like an `Control.Monad.Async.Async` to complete).
    awaitWith :: (STM Interrupt -> STM Event -> STM a) -> m a

-- | Wait for the next event.
--
-- * Returns as soon as an interrupt or a regular event occurs.
-- * This operation resets the interrupt flag, signaling responsiveness to
--   the execution environment.
awaitEvent :: MonadInput m => m (Either Interrupt Event)
awaitEvent = awaitWith$ \intr ev ->
    (Left <$> intr) <|> (Right <$> ev)

-- | Check whether an interrupt is pending.
--
-- * This operation resets the interrupt flag, signaling responsiveness
--   to the execution environment.
checkInterrupt :: MonadInput m => m Bool
checkInterrupt = awaitWith $ \intr _ ->
    (intr >> pure True) <|> pure False

-- | Events emitted by the terminal.
--
-- * Event decoding might be ambique. In case of ambiqueness all
--   possible meaning shall be emitted. The user is advised to only
--   match on events expected in a certain context and ignore all
--   others.
-- * Key events are highly ambique: I.e. when the user presses @space@
--   it might either be meant as a regular text element (like @a@,@b@,@c@)
--   or the focus is on the key itself (like in "Press space to continue...").
-- * The story is even more complicated: Depending on terminal type and
--   @termios@ settings, certain control codes have special meaning or not
--   (@Ctrl+C@ sometimes means interrupt, but not if the environment supports
--   delivering it as a signal). Don't wait for @Ctrl+C@ when you mean `Interrupt`!
--   Example: The tab key will likely emit @KeyEvent (CharKey 'I') ctrlKey@ and
--   @KeyEvent TabKey mempty@ in most settings.
data Event
    = KeyEvent Key Modifiers
    | MouseEvent MouseEvent
    | WindowEvent WindowEvent
    | DeviceEvent DeviceEvent
    | OtherEvent String
    deriving (Eq,Ord,Show)

-- | Events triggered by key press.
data Key
    = CharKey Char
    | TabKey
    | SpaceKey
    | BackspaceKey
    | EnterKey
    | InsertKey
    | DeleteKey
    | HomeKey
    | BeginKey
    | EndKey
    | PageUpKey
    | PageDownKey
    | EscapeKey
    | PrintKey
    | PauseKey
    | ArrowKey Direction
    | FunctionKey Int
    deriving (Eq,Ord,Show)

-- | Modifier keys.
newtype Modifiers = Modifiers Int
    deriving (Eq, Ord, Bits)

instance Semigroup Modifiers where
    Modifiers a <> Modifiers b = Modifiers (a .|. b)

instance Monoid Modifiers where
    mempty = Modifiers 0

instance Show Modifiers where
    show (Modifiers 0) = "mempty"
    show (Modifiers 1) = "shiftKey"
    show (Modifiers 2) = "ctrlKey"
    show (Modifiers 4) = "altKey"
    show (Modifiers 8) = "metaKey"
    show i = "(" ++ intercalate " <> " ls ++ ")"
        where
        ls = foldl (\acc x-> if x .&. i /= mempty then show x:acc else acc) []
                    [metaKey, altKey, ctrlKey, shiftKey]

shiftKey, ctrlKey, altKey, metaKey :: Modifiers
shiftKey = Modifiers 1
ctrlKey  = Modifiers 2
altKey   = Modifiers 4
metaKey  = Modifiers 8

-- | Events triggered by mouse action.
--
-- * Mouse event reporting must be activated before (TODO).
data MouseEvent
    = MouseMoved          Position
    | MouseButtonPressed  Position MouseButton
    | MouseButtonReleased Position MouseButton
    | MouseButtonClicked  Position MouseButton
    | MouseWheeled        Position Direction
    deriving (Eq,Ord,Show)

data MouseButton
    = LeftMouseButton
    | RightMouseButton
    | OtherMouseButton
    deriving (Eq,Ord,Show)

data Direction
    = Upwards
    | Downwards
    | Leftwards
    | Rightwards
    deriving (Eq,Ord,Show)

data WindowEvent
    = WindowLostFocus
    | WindowGainedFocus
    | WindowSizeChanged
    deriving (Eq, Ord, Show)

data DeviceEvent
    = DeviceAttributesReport String
    | CursorPositionReport Position
    deriving (Eq, Ord, Show)

-- | Interrupt is a special type of event that needs
--   to be treated with priority. It is therefor not
--   included in the regular event stream.
data Interrupt
    = Interrupt
    deriving (Eq, Ord, Show)