{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
module Workflow.Execute where
import Workflow.Types

import Control.Monad.Trans.Free (iterT)

import Control.Monad.IO.Class
import Control.Concurrent (threadDelay)
import Numeric.Natural

{-| a function that can exectue any workflow, via IO.
-}
newtype ExecuteWorkflow = ExecuteWorkflow { getExecuteWorkflow ::
 (forall n x. (MonadIO n) => WorkflowT n x -> n x)
}

{-| An explicit "typeclass dictionary" for interpreting a 'MonadWorkflow'.

i.e. a generic handler/interpreter (product type)
for 'WorkflowF' effects (a sum type).

e.g.

@
WorkflowD IO
@

template:

@
myDictionary :: (MonadIO m) => WorkflowD m
myDictionary = WorkflowD{..}
 where
  _sendKeyChord =
  _sendText     =

  _sendMouseClick  =
  _sendMouseScroll =

  _getClipboard =
  _setClipboard =

  _currentApplication =
  _openApplication    =
  _openURL            =

runWorkflowByMy :: (MonadIO m) => WorkflowT m a -> m a
runWorkflowByMy = runWorkflowByT myDictionary
@

'Delay' is elided, as its implementation can use
cross-platform 'IO' ('threadDelay').

see 'runWorkflowByT'

-}
data WorkflowD m = WorkflowD
 { _sendKeyChord       :: [Modifier] -> Key -> m ()
 , _sendText           :: String            -> m ()

 , _sendMouseClick     :: [Modifier] -> Natural     -> MouseButton -> m ()
 , _sendMouseScroll    :: [Modifier] -> MouseScroll -> Natural     -> m ()

 , _getClipboard       :: m Clipboard
 , _setClipboard       :: (Clipboard -> m ())

 , _currentApplication :: m Application
 , _openApplication    :: Application -> m ()

 , _openURL            :: URL -> m ()

 -- , _delay :: MilliSeconds -> m ()
 } -- deriving (Functor)

--------------------------------------------------------------------------------

{-|

e.g.

@
shellDictionary :: WorkflowD IO
shellDictionary = WorkflowD{..}
 where
 '_getClipboard' = shell $ "pbpaste"
 '_setClipboard' s = shell $ "echo "++(shellEscape s)++"| pbcopy" >> return ()
 ...

runWorkflowByShell :: (MonadIO m) => 'WorkflowT' m a -> m a
runWorkflowByShell = runWorkflowByT shellDictionary

-- specializeable:
-- runWorkflowByShell :: 'Workflow' a -> IO a
@

-}
runWorkflowByT
  :: forall m a. (MonadIO m)
  -- => CoWorkflowT (m a)
  => WorkflowD m
  -> WorkflowT m a
  -> m a
-- runWorkflowByT CoWorkflowF{..} = iterT go
runWorkflowByT WorkflowD{..} = iterT go
 where

 go :: WorkflowF (m a) -> m a
 go = \case

  SendKeyChord    flags key k      -> _sendKeyChord flags key >> k
  SendText        s k              -> _sendText s             >> k

  SendMouseClick  flags n button k    -> _sendMouseClick flags n button     >> k
  SendMouseScroll flags scrolling n k -> _sendMouseScroll flags scrolling n >> k

  GetClipboard    f                -> _getClipboard   >>= f
  SetClipboard    s k              -> _setClipboard s >>  k

  CurrentApplication f             -> _currentApplication  >>= f
  OpenApplication app k            -> _openApplication app >>  k
  OpenURL         url k            -> _openURL url         >>  k

  Delay           t k              -> delayMilliseconds t >> k
 -- 1,000 µs is 1ms

delayMilliseconds :: (MonadIO m) => Int -> m ()
delayMilliseconds = liftIO . threadDelay . (*1000)

delaySeconds :: (MonadIO m) => Int -> m ()
delaySeconds =  delayMilliseconds . (*1000) --TODO rm