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

module Rasa.Internal.Action where

import Control.Lens
import Control.Concurrent.Async
import Control.Monad.State
import Control.Monad.Reader
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 a

-- | 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.eventListener'
--
-- 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' and 'Rasa.Internal.Ext.Directive.focusDo'
--      * 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 (ReaderT Hooks IO) a
  } deriving (Functor, Applicative, Monad, MonadState ActionState, MonadReader Hooks, MonadIO)


-- | Unwrap and execute an Action (returning the editor state)
execAction :: ActionState -> Hooks -> Action () -> IO ActionState
execAction actionState hooks action = flip runReaderT hooks . execStateT (runAct action) $ actionState

-- | Unwrap and evaluate an Action (returning the value)
evalAction :: ActionState -> Hooks -> Action a -> IO a
evalAction actionState hooks action  = flip runReaderT hooks $ evalStateT (runAct action) actionState

type AsyncAction = Async (Action ())
data ActionState = ActionState
  { _ed :: Editor
  , _asyncs :: [AsyncAction]
  }
makeClassy ''ActionState

instance HasEditor ActionState where
  editor = ed

instance Default ActionState where
  def = ActionState
    { _ed=def
    , _asyncs=def
    }

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

-- | This is a monad-transformer stack for performing actions on a specific buffer.
-- You register BufActions to be run by embedding them in a scheduled 'Action' via 'bufferDo' or 'focusDo'
--
-- Within a BufAction you can:
--
--      * Use liftIO for IO
--      * Access/edit buffer extensions; see 'bufExt'
--      * Embed and sequence any 'BufAction's from other extensions
--      * Access/Edit the buffer's 'text'
--
newtype BufAction a = BufAction
  { getBufAction::StateT Buffer (ReaderT Hooks IO) a
  } deriving (Functor, Applicative, Monad, MonadState Buffer, MonadReader Hooks, MonadIO)

-- | 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 bufAct (BufRef bufRef) = do
  mBuf <- use (buffers.at bufRef)
  case mBuf of
    Nothing -> return Nothing
    Just buf -> do
      hooks <- ask
      (val, newBuf) <- liftIO $ flip runReaderT hooks . flip runStateT buf . getBufAction $ bufAct
      buffers.at bufRef ?= newBuf
      return . Just $ val