{-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification, TemplateHaskell, StandaloneDeriving,
   MultiParamTypeClasses, FlexibleInstances #-}

module Rasa.Internal.Action where

import Control.Lens
import Control.Concurrent.Async
import Control.Monad.State
import Data.Dynamic
import Data.Map
import Data.Default

import Rasa.Internal.Buffer
import Rasa.Internal.Editor


-- | A wrapper around event listeners so they can be stored in 'Hooks'.
data Hook = forall a. Hook HookId (a -> Action ())
data HookId =
  HookId Int TypeRep

instance Eq HookId where
  HookId a _ == HookId b _ = a == b

-- | A map of Event types to a list of listeners for that event
type Hooks = Map TypeRep [Hook]

-- | This is a monad-transformer stack for performing actions against the editor.
-- You register Actions to be run in response to events using 'Rasa.Internal.Scheduler.onEveryTrigger'
--
-- Within an Action you can:
--
--      * Use liftIO for IO
--      * Access/edit extensions that are stored globally, see 'ext'
--      * Embed any 'Action's exported other extensions
--      * Embed buffer actions using 'Rasa.Internal.Ext.Directive.bufDo' or 'Rasa.Internal.Ext.Directive.buffersDo'
--      * Add\/Edit\/Focus buffers and a few other Editor-level things, see the 'Rasa.Internal.Ext.Directive' module.

newtype Action a = Action
  { runAct :: StateT ActionState IO a
  } deriving (Functor, Applicative, Monad, MonadState ActionState, MonadIO)

-- | Execute an Action (returning the editor state)
execAction :: ActionState ->  Action () -> IO ActionState
execAction actionState action = execStateT (runAct action) actionState

-- | Evaluate an Action (returning the value)
evalAction :: ActionState -> Action a -> IO a
evalAction actionState (Action action)  = evalStateT action actionState

type AsyncAction = Async (Action ())
-- | This contains all data representing the editor's state. It acts as the state object for an 'Action
data ActionState = ActionState
  { _ed :: Editor
  , _asyncs :: [AsyncAction]
  , _hooks :: Hooks
  , _nextHook :: Int
  }
makeLenses ''ActionState

-- | Allows polymorphic lenses which need to access something in ActionState
class HasActionState a where
  actionState :: Lens' a ActionState

instance HasActionState ActionState where
  actionState = lens id (flip const)

instance HasEditor ActionState where
  editor = ed

instance Default ActionState where
  def = ActionState
    { _ed=def
    , _asyncs=def
    , _hooks=def
    , _nextHook=0
    }

-- | Contains all data about the editor; as well as a buffer which is in 'focus'.
-- We keep the full ActionState here too so that 'Action's may be lifted inside a 'BufAction'
data BufActionState = BufActionState
  { _actState :: ActionState
  , _buf :: Buffer
  }
makeLenses ''BufActionState

instance Show ActionState where
  show as = show (_ed as)

-- | This is a monad-transformer stack for performing actions on a specific buffer.
-- You run 'BufAction's by embedding them in a 'Action' via 'bufferDo' or 'buffersDo'
--
-- Within a BufAction you can:
--
--      * Use 'liftAction' to run an 'Action'; It is your responsibility to ensure that any nested 'Action's don't edit
  --      the Buffer which the current 'BufAction' is editing; behaviour is undefined if this occurs.
--      * Use liftIO for IO
--      * Access/Edit the buffer's 'text'
--      * Access/edit buffer extensions; see 'bufExt'
--      * Embed and sequence 'BufAction's from other extensions

newtype BufAction a = BufAction
  { runBufAct :: StateT BufActionState IO a
  } deriving (Functor, Applicative, Monad, MonadState BufActionState, MonadIO)

instance HasBuffer BufActionState where
  buffer = buf

instance HasActionState BufActionState where
  actionState = actState

-- | This lifts up a bufAction into an Action which performs the 'BufAction'
-- over the referenced buffer and returns the result (if the buffer existed)
liftBuf :: BufAction a -> BufRef -> Action (Maybe a)
liftBuf (BufAction bufAct) (BufRef bufInd) = do
  actionState' <- get
  mBuf <- getBuffer
  case mBuf of
    Nothing -> return Nothing
    Just b -> do
      let bufActSt = BufActionState actionState' b
      (val, newBufActState) <- liftIO $ runStateT bufAct bufActSt
      put (newBufActState^.actionState)
      setBuffer (newBufActState^.buf)
      return (Just val)
  where
    getBuffer = use (buffers.at bufInd)
    setBuffer b = buffers.at bufInd ?= b

-- | This lifts up an 'Action' to be run inside a 'BufAction'
--
-- it is your responsibility to ensure that any nested 'Action's don't edit
-- the Buffer which the current 'BufAction' is editing; behaviour is undefined if this occurs.
liftAction :: Action a -> BufAction a
liftAction (Action action) = BufAction $ zoom actionState action