-- | The main Robot interface.

module Test.Robot
    (
      -- * Running your robot
      Robot()  -- hide implementation
    , runRobot

      -- * Key and button constants
    , module Test.Robot.Types

      -- * Doing things
    , Pressable(press, release, hold)
    , moveBy
    , moveTo
    , tap

      -- * Miscellaneous
    , sleep
    , module Test.Robot.Connection

    ) where


import Control.Applicative
import Control.Concurrent (threadDelay)
import Control.Monad.Catch
import Control.Monad.IO.Class

import Test.Robot.Connection
import Test.Robot.Internal
import Test.Robot.Types


infixr 4 `hold`  -- Allow e.g. xs ++ ys `hold` m


-- | Represents things that can be pressed: either a single 'Switch' or
-- a list of 'Switch'es.
class Pressable x where

    -- | Press a key or button.
    press :: x -> Robot ()

    -- | Release a key or button.
    release :: x -> Robot ()

    -- | @hold x act@ holds down @x@ while executing @act@. It is
    -- equivalent to:
    --
    -- @
    -- press x >> act >> release x
    -- @
    --
    -- except @hold@ ensures that the argument is released in the event
    -- of an exception.
    --
    hold :: x -> Robot a -> Robot a
    hold = bracket_ <$> press <*> release

instance Pressable Switch where
    press = switch True
    release = switch False

-- | Press items from left-to-right, but release from right-to-left.
--
-- This behavior ensures the following equivalence holds:
--
-- @
-- press xs >> act >> release xs
--   === xs \`hold\` act
--   === x1 \`hold\` x2 \`hold\` ... xn \`hold\` act
-- @
--
instance Pressable x => Pressable [x] where
    press = mapM_ press
    release = mapM_ release . reverse

    hold = foldr (.) id . map hold
    --hold [] = id
    --hold (x:xs) = hold x . hold xs


-- | Move the pointer by an offset.
moveBy :: Int -> Int -> Robot ()
moveBy = motion True

-- | Move the pointer to a point on the screen.
moveTo :: Int -> Int -> Robot ()
moveTo = motion False


-- | Press the argument, then release it.
--
-- Note that the underlying events are fired very quickly; much faster
-- than some applications (such as Xmonad) can handle. If this becomes
-- an issue, you may introduce a delay using 'sleep':
--
-- @
-- slowTap x = x \`hold\` sleep 0.1
-- @
--
tap :: Pressable x => x -> Robot ()
tap = (`hold` return ())


-- | Do nothing for the specified number of seconds.
sleep :: Rational -> Robot ()
sleep = liftIO . threadDelay . round . (* 1000000)