{-# LANGUAGE OverloadedStrings #-}
module Emacs.NAdvice where

import Prelude()
import Protolude
import Emacs.Core

-- Emacs 24 からアドバイスの機構が一新された(nadvice.el)。以前より大幅
-- にシンプルになっている。必要な関数は `advice-add` と
-- `advice-remove` の二つ。

-- (advice-add SYMBOL WHERE FUNCTION &optional PROPS)
--
-- Like ‘add-function’ but for the function named SYMBOL.
-- Contrary to ‘add-function’, this will properly handle the cases where SYMBOL
-- is defined as a macro, alias, command, ...
--
-- TODO: PROPSについては `add-function` のヘルプを参照

-- 基本的に Arround 一つで全て実装できる。
--
--  `:before'       (lambda (&rest r) (apply FUNCTION r) (apply OLDFUN r))
--  `:after'        (lambda (&rest r) (prog1 (apply OLDFUN r) (apply FUNCTION r)))
--  `:around'       (lambda (&rest r) (apply FUNCTION OLDFUN r))
--  `:override'     (lambda (&rest r) (apply FUNCTION r))
--  `:before-while' (lambda (&rest r) (and (apply FUNCTION r) (apply OLDFUN r)))
--  `:before-until' (lambda (&rest r) (or  (apply FUNCTION r) (apply OLDFUN r)))
--  `:after-while'  (lambda (&rest r) (and (apply OLDFUN r) (apply FUNCTION r)))
--  `:after-until'  (lambda (&rest r) (or  (apply OLDFUN r) (apply FUNCTION r)))
--  `:filter-args'  (lambda (&rest r) (apply OLDFUN (funcall FUNCTION r)))
--  `:filter-return'(lambda (&rest r) (funcall FUNCTION (apply OLDFUN r)))
--
data Where
  = Around
  | Before
  | After
  | Override
  | BeforeWhile
  | BeforeUntil
  | AfterWhile
  | AfterUntil
  | FilterArgs
  | FIlterReturn

whereToKeyword :: Where -> Keyword
whereToKeyword Around = Keyword "around"
whereToKeyword Before = Keyword "before"

-- TODO: FUNCTION は シンボルでないと駄目? -> いや、関数でもOK
-- 存在しないシンボルに対しても設定できる。
adviceAdd'
  :: (ToEmacsSymbol s, ToEmacsFunction f)
  => s
  -> Where
  -> f
  -> EmacsM ()
adviceAdd' target where' func =
  void $ funcall3 "advice-add" target (whereToKeyword where') func

-- 基本的にこの関数さえあれば何でもできる。
-- TODO: アドバイス外せるように
around' :: Callable f => Text -> (EmacsFunction -> f) -> EmacsM ()
around' name ff = do
  adviceAdd' (Symbol name) Around ff

-- aroundアドバイスの場合、大抵は引数は弄らない。ショートカット的。
-- TODO:
around :: Callable f => Text -> (EmacsM EmacsValue -> f) -> EmacsM ()
around name ff = do
  adviceAdd' (Symbol name) Around =<< wrap ff
  where
    wrap :: Callable f => (EmacsM EmacsValue -> f) -> EmacsM EmacsFunction
    wrap newf =
      let wf :: [EmacsValue] -> EmacsM EmacsValue
          wf (func:args) = do
            res <- call (newf (funcall func args)) args
            case res of
              Right ev -> return ev
              Left   _ -> undefined
      in EmacsFunction <$> mkFunction wf 0 1000 "around advice"