{-# LANGUAGE ForeignFunctionInterface,UndecidableInstances #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}

module Emacs.Core (
    module Emacs.Internal,
    defmodule,
    -- mk
    mkCons,
    -- funcall
    ToEmacsValue(..),
    ToEmacsSymbol(..),
    ToEmacsFunction(..),
    funcall1, funcall2, funcall3,
    mkFunctionFromCallable,
    Callable(..),
    --
    car,
    cdr,
    --
    evalString,
    provide,
    message,
    print,
    ) where

import Prelude()
import Protolude hiding (mkInteger,print)
import Foreign.C.Types
import Foreign.StablePtr
import Emacs.Type
import Emacs.Internal

-- emacsModuleInit に渡す関数
defmodule :: Text -> EmacsM a -> EmacsModule
defmodule name mod ert = do
  env <- getEmacsEnvFromRT ert
  errorHandle env $ do
    ctx <- initCtx env
    runEmacsM ctx $ mod >> funcall1 "provide" (Symbol name)
  return 0

-- 関数の引数に ToEmacsValue を受け取るようにすると便利なんだけど、問
-- 題はその引数の実際の値を取得するめに EmacsM の中で実行する必要があ
-- り、引数の実行で例外が発生するかもしれない、ということ。
--
-- ある関数の中でEmacsException例外が発生したときにどのタイミングでど
-- こまで進んだかの保証が得られない。
-- ああ、けど IO での例外でも同じことが言えるのか...
class ToEmacsValue h where
  toEv :: h -> EmacsM EmacsValue

-- misc
-- EmacsM EmacsValue はどうなんだ... いいのかな?いいのであれば、
-- EmacsM EmacsSymbol とかも許容するべきかな。
-- いや、やはりないほうがいいかな。
instance ToEmacsValue EmacsValue where
  toEv = pure
-- instance ToEmacsValue (EmacsM EmacsValue) where
--   toEv = identity

-- Integer
instance ToEmacsValue Int where
  toEv = mkInteger

-- String
instance ToEmacsValue Text where
  toEv = mkString

-- Symbol
instance ToEmacsValue EmacsSymbol where
  toEv = pure . asEmacsValue
instance ToEmacsValue Symbol where
  toEv = (asEmacsValue<$>) . toEmacsSymbol

-- Kwyword
instance ToEmacsValue EmacsKeyword where
  toEv = pure . asEmacsValue
instance ToEmacsValue Keyword where
  toEv = (asEmacsValue<$>) . toEmacsKeyword

-- Bool
instance ToEmacsValue Bool where
  toEv True  = mkT
  toEv False = mkNil

-- Nil
instance ToEmacsValue () where
  toEv _ = mkNil

-- List
instance ToEmacsValue EmacsList where
  toEv = pure . asEmacsValue
instance ToEmacsValue h => ToEmacsValue [h] where
  toEv = (asEmacsValue<$>) . toEmacsList

-- Cons
instance ToEmacsValue EmacsCons where
  toEv = pure . asEmacsValue
instance (ToEmacsValue a, ToEmacsValue b) => ToEmacsValue (a, b) where
  toEv = (asEmacsValue<$>) . toEmacsCons

-- Function
-- Can only handle function with no arguments.
-- Use mkFunctionFromCallable for no args.
instance ToEmacsValue EmacsFunction where
  toEv = pure . asEmacsValue
instance (FromEmacsValue a, Callable b) => ToEmacsValue (a -> b) where
  toEv = (asEmacsValue<$>) . toEmacsFunction

-- AsEmacsValue
-- これはderiveしたいところ...
class    AsEmacsValue s             where asEmacsValue :: s -> EmacsValue
instance AsEmacsValue EmacsSymbol   where asEmacsValue (EmacsSymbol ev) = ev
instance AsEmacsValue EmacsKeyword  where asEmacsValue (EmacsKeyword ev) = ev
instance AsEmacsValue EmacsCons     where asEmacsValue (EmacsCons ev) = ev
instance AsEmacsValue EmacsList     where asEmacsValue (EmacsList ev) = ev
instance AsEmacsValue EmacsFunction where asEmacsValue (EmacsFunction ev) = ev

-- それぞれの OpaqueType への変換

-- Symbol
class ToEmacsValue s => ToEmacsSymbol s where
  toEmacsSymbol :: s -> EmacsM EmacsSymbol
instance ToEmacsSymbol EmacsSymbol where
  toEmacsSymbol = pure
instance ToEmacsSymbol Symbol      where
  toEmacsSymbol (Symbol t) = EmacsSymbol <$> intern t

-- Keyword
class ToEmacsValue s => ToEmacsKeyword s where
  toEmacsKeyword :: s -> EmacsM EmacsKeyword
instance ToEmacsKeyword EmacsKeyword where
  toEmacsKeyword = pure
instance ToEmacsKeyword Keyword where
  toEmacsKeyword (Keyword t) = EmacsKeyword <$> intern (":" <> t)

-- Cons
class ToEmacsValue s => ToEmacsCons s where
  toEmacsCons :: s -> EmacsM EmacsCons
instance ToEmacsCons EmacsCons where
  toEmacsCons = pure
instance (ToEmacsValue a, ToEmacsValue b) => ToEmacsCons (a, b) where
  toEmacsCons (a,b) = do
    av <- toEv a
    bv <- toEv b
    mkCons av bv

-- List
class ToEmacsValue s => ToEmacsList s where
  toEmacsList :: s -> EmacsM EmacsList
instance ToEmacsList EmacsList where
  toEmacsList = pure
instance ToEmacsValue x => ToEmacsList [x] where
  toEmacsList xs = EmacsList <$> (join $ mkList <$> mapM toEv xs)

-- Function
-- tricky
-- 無引数関数は明示的にやる必要ある。
class (Callable s,ToEmacsValue s) => ToEmacsFunction s where
  toEmacsFunction :: s -> EmacsM EmacsFunction

instance ToEmacsFunction EmacsFunction where
  toEmacsFunction = pure

instance (FromEmacsValue a, Callable b) => ToEmacsFunction (a -> b) where
  toEmacsFunction f = EmacsFunction <$> mkFunctionFromCallable f

-- Function call Utilities
funcall1
  :: ToEmacsValue a
  => Text
  -> a
  -> EmacsM EmacsValue
funcall1 fname ev0 =
  join $ funcall <$> intern fname
                 <*> sequence [toEv ev0]

funcall2
  :: (ToEmacsValue a, ToEmacsValue b)
  => Text
  -> a
  -> b
  -> EmacsM EmacsValue
funcall2 fname ev0 ev1 =
  join $ funcall <$> intern fname
                 <*> sequence [toEv ev0, toEv ev1]

funcall3
  :: (ToEmacsValue a, ToEmacsValue b, ToEmacsValue c)
  => Text
  -> a
  -> b
  -> c
  -> EmacsM EmacsValue
funcall3 fname ev0 ev1 ev2 =
  join $ funcall <$> intern fname
                 <*> sequence [toEv ev0, toEv ev1, toEv ev2]

-- Emacs -> Haskell
-- 変換に失敗する場合は例外を飛ばすように
--
-- 現状 EmacsValue を返している関数を、 h を返すようにするのも便利かも
-- しれないが、明示的な型指定する必要が増えるかも。。。
class FromEmacsValue h where
  fromEv :: EmacsValue -> EmacsM h

instance FromEmacsValue Int where
  fromEv = extractInteger

instance FromEmacsValue Text where
  fromEv = extractString

instance FromEmacsValue EmacsValue where
  fromEv = pure

-- TODO: これいいのか?チェック必要ないか?
instance FromEmacsValue EmacsFunction where
  fromEv = pure . EmacsFunction


-- 多相的な関数は駄目らしい(具体的な関数ならokらしい)
-- TODO: optional, rest 引数に対応する。
class Callable a where
    call :: a -> [EmacsValue] -> EmacsM (Either Text EmacsValue)
    arity :: a -> Int

instance {-# OVERLAPPING #-} ToEmacsValue a => Callable a where
    call a [] = Right <$> toEv a
    call _ _  = pure $ Left "Too many arguments"
    arity _ = 0

instance {-# OVERLAPPING #-} ToEmacsValue a => Callable (IO a) where
    call a [] = do
      v <- liftIO a
      Right <$> toEv v
    call _ _  = pure $ Left "Too many arguments"
    arity _ = 0

instance {-# OVERLAPPING #-} ToEmacsValue a => Callable (EmacsM a) where
    call am [] = do
      a <- am
      Right <$> toEv a
    call _ _  = pure $ Left "Too many arguments"
    arity _ = 0

instance {-# OVERLAPPING #-} (FromEmacsValue a, Callable b) => Callable (a -> b) where
  call f (e:es) = do
    av <- fromEv e
    call (f av) es
  call _ [] = pure $ Left "Too less arguments"
  arity f = arity (f undefined) + 1

-- 多相的な関数は怒られるはず。
mkFunctionFromCallable :: Callable f => f -> EmacsM EmacsValue
mkFunctionFromCallable f = do
  let a = arity f
  mkFunction func a a ""
  where
    func :: [EmacsValue] -> EmacsM EmacsValue
    func es = do
      res <- call f es
      case res of
        Right ev -> return ev
        Left _   -> undefined

evalString :: Text -> EmacsM EmacsValue
evalString t =
  funcall1 "eval" =<< (car =<< funcall1 "read-from-string" t)

provide :: Text -> EmacsM ()
provide feature =
  void $ funcall1 "provide" (Symbol feature)

message :: Text -> EmacsM ()
message t =
  void $ funcall1 "message" t

print :: ToEmacsValue v => v -> EmacsM ()
print ev =
  void $ funcall1 "print" ev

mkCons
  :: (ToEmacsValue a, ToEmacsValue b)
  => a
  -> b
  -> EmacsM EmacsCons
mkCons a b =
  EmacsCons <$> funcall2 "cons" a b

car :: EmacsValue -> EmacsM EmacsValue
car = funcall1 "car"

cdr :: EmacsValue -> EmacsM EmacsValue
cdr = funcall1 "cdr"