-- | The main Robot interface.

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

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

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

      -- * Miscellaneous
    , sleep

    ) where


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

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 key on a keyboard
-- or a button on a mouse.
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 = bracketRobot_ <$> press <*> release

instance Pressable Key where
    press = keyboard True
    release = keyboard False

instance Pressable Button where
    press = button True
    release = button 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.
tap :: Pressable x => x -> Robot ()
tap = (`hold` return ())


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