-- Copyright (c) 2009 Nicolas Pouillard
{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
module Yi.Mode.Abella
  ( abellaModeVim, abellaModeEmacs, abella
  , abellaEval, abellaEvalFromProofPoint, abellaUndo, abellaGet, abellaSend)
where

import Prelude ()
import Control.Monad (replicateM_, join)
import Data.Char (isSpace)
import Data.Binary
import Data.Maybe (isJust)
import Data.List (isInfixOf)
import Yi.Core
import qualified Yi.Mode.Interactive as Interactive
import Yi.Modes (TokenBasedMode, linearSyntaxMode, anyExtension)
import qualified Yi.Lexer.Abella as Abella
import Yi.Syntax.Tree
import Yi.MiniBuffer (CommandArguments(..))
import Yi.Lexer.Alex
import Yi.Keymap.Vim (savingCommandY)

abellaModeGen :: (Char -> [Event]) -> TokenBasedMode Abella.Token
abellaModeGen abellaBinding =
  (linearSyntaxMode Abella.initState Abella.alexScanToken Abella.tokenToStyle)
  { modeName = "abella"
  , modeApplies = anyExtension ["thm"]
  , modeGetAnnotations = tokenBasedAnnots (sequenceA . tokToSpan . fmap Abella.tokenToText)
  , modeToggleCommentSelection = toggleCommentSelectionB "% " "%"
  , modeKeymap = topKeymapA ^: ((<||)
     (choice
      [ abellaBinding 'p' ?*>>! sav abellaUndo
      , abellaBinding 'e' ?*>>! sav abellaEval
      , abellaBinding 'n' ?*>>! sav abellaNext
      , abellaBinding 'a' ?*>>! sav abellaAbort
      , abellaBinding '\r' ?*>>! sav abellaEvalFromProofPoint
      ]))
  }
  where sav f = savingCommandY (flip replicateM_ f) 1

abellaModeVim :: TokenBasedMode Abella.Token
abellaModeVim = abellaModeGen (\ch -> [char '\\', char ch])

abellaModeEmacs :: TokenBasedMode Abella.Token
abellaModeEmacs = abellaModeGen (\ch -> [ctrlCh 'c', ctrlCh ch])

newtype AbellaBuffer = AbellaBuffer {_abellaBuffer :: Maybe BufferRef}
    deriving (Initializable, Typeable, Binary)
instance YiVariable AbellaBuffer

getProofPointMark :: BufferM Mark
getProofPointMark = getMarkB $ Just "p"

getTheoremPointMark :: BufferM Mark
getTheoremPointMark = getMarkB $ Just "t"

abellaEval :: YiM ()
abellaEval = do reg <- withBuffer . savingPointB $ do
                          join $ setMarkPointB <$> getProofPointMark <*> pointB
                          leftB
                          readRegionB =<< regionOfNonEmptyB unitSentence
                abellaSend reg

abellaEvalFromProofPoint :: YiM ()
abellaEvalFromProofPoint = abellaSend =<< withBuffer f
  where f = do mark <- getProofPointMark
               p <- getMarkPointB mark
               cur <- pointB
               setMarkPointB mark cur
               readRegionB $ mkRegion p cur

abellaNext :: YiM ()
abellaNext = do reg <- withBuffer $ rightB >> (readRegionB =<< regionOfNonEmptyB unitSentence)
                abellaSend reg
                withBuffer $ do moveB unitSentence Forward
                                rightB
                                untilB_ (not . isSpace <$> readB) rightB
                                untilB_ ((/= '%') <$> readB) $ moveToEol >> rightB >> firstNonSpaceB
                                join $ setMarkPointB <$> getProofPointMark <*> pointB

abellaUndo :: YiM ()
abellaUndo = do abellaSend "undo."
                withBuffer $ do moveB unitSentence Backward
                                join $ setMarkPointB <$> getProofPointMark <*> pointB

abellaAbort :: YiM ()
abellaAbort = do abellaSend "abort."
                 withBuffer $ do moveTo =<< getMarkPointB =<< getTheoremPointMark
                                 join $ setMarkPointB <$> getProofPointMark <*> pointB

-- | Start Abella in a buffer
abella :: CommandArguments -> YiM BufferRef
abella (CommandArguments args) = do
    b <- Interactive.interactive "abella" args
    withEditor . setDynamic . AbellaBuffer $ Just b
    return b

-- | Return Abella's buffer; create it if necessary.
-- Show it in another window.
abellaGet :: YiM BufferRef
abellaGet = withOtherWindow $ do
    AbellaBuffer mb <- withEditor $ getDynamic
    case mb of
        Nothing -> abella (CommandArguments [])
        Just b -> do
            stillExists <- withEditor $ isJust <$> findBuffer b
            if stillExists
                then do withEditor $ switchToBufferE b
                        return b
                else abella (CommandArguments [])

-- | Send a command to Abella
abellaSend :: String -> YiM ()
abellaSend cmd = do
    when ("Theorem" `isInfixOf` cmd) $
      withBuffer $ join $ setMarkPointB <$> getTheoremPointMark <*> pointB
    b <- abellaGet
    withGivenBuffer b botB
    sendToProcess b (cmd ++ "\n")
