{- |
Module      : ./PGIP/XMLparsing.hs
Description : XML processing for the CMDL interface
Copyright   : uni-bremen and DFKI
License     : GPLv2 or higher, see LICENSE.txt
Maintainer  : r.pascanu@jacobs-university.de
Stability   : provisional
Portability : portable

PGIP.XMLparsing contains commands for parsing or creating XML messages
-}

module PGIP.XMLparsing where

import PGIP.XMLstate

import CMDL.DataTypes
import CMDL.DataTypesUtils
import CMDL.DgCommands (cUse)
import CMDL.ProcessScript
import CMDL.Interface (cmdlRunShell)

import Interfaces.DataTypes
import Interfaces.Command
import Interfaces.Utils (emptyIntIState)

import Driver.Options
import Driver.ReadFn

import qualified Static.ToXml as ToXml
import Static.DevGraph

import Common.LibName
import Common.ToXml

import Text.XML.Light as XML

import Network (connectTo, PortID (PortNumber), accept, listenOn)

import System.IO

import Data.List (isInfixOf)

{- | Generates the XML packet that contains information about what
commands can the interface respond to -}
addPGIPHandshake :: CmdlPgipState -> CmdlPgipState
addPGIPHandshake pgipData = if useXML pgipData
       then addPGIPElement pgipData
            $ add_attr (mkAttr "version" "2.0")
            $ unode "acceptedpgipelems" $ map genPgipElem
             [ "askpgip"
             , "askpgml"
             , "askprefs"
             , "getprefs"
             , "setprefs"
             , "proverinit"
             , "proverexit"
             , "startquiet"
             , "stopquiet"
             , "pgmlsymbolon"
             , "pgmlsymboloff"
             , "dostep"
             , "undostep"
             , "redostep"
             , "abortgoal"
             , "forget"
             , "restoregoal"
             , "askids"
             , "showid"
             , "askguise"
             , "parsescript"
             , "showproofstate"
             , "showctxt"
             , "searchtheorems"
             , "setlinewidth"
             , "viewdoc"
             , "doitem"
             , "undoitem"
             , "redoitem"
             , "aborttheory"
             , "retracttheory"
             , "loadfile"
             , "openfile"
             , "closefile"
             , "abortfile"
             , "changecwd"
             , "systemcmd"]
       else pgipData

{- | The function executes a communication step, i.e. waits for input,
processes the message and outputs the answer -}
communicationStep :: CmdlPgipState -> CmdlState -> IO (CmdlPgipState, CmdlState)
communicationStep pgD st = do
   -- tries to read a packet from the input
  b <- hIsEOF (hin pgD)
  if b then return (pgD { stop = True }, st) else do
   tmp <- timeoutReadPacket (maxWaitTime pgD) pgD
   case tmp of
    Nothing -> if resendMsgIfTimeout pgD
      {- if the interface receives nothing in the given timeframe
      described by maxWaitTime and the flag resendMsgIfTimeout is
      set, that the interface resends last packet assuming that last
      send was a fail -}
                then do
                       nwpgD <- sendPGIPData (hetsOpts st) pgD
                       communicationStep nwpgD st
       {- if the flag is not set, that the network waits some more for the
       broker to respond or give a new command -}
                else communicationStep pgD st
    {- if something is received, that the commands are parsed and executed
    and a response is generated -}
    Just smtxt ->
      do
        let cmds = parseMsg pgD smtxt
            refseqNb = getRefseqNb smtxt
        (nwSt, nwPgD) <- processCmds cmds st $ resetPGIPData $
                           pgD { refSeqNb = refseqNb }
        if useXML pgD then do
                 nwPgipSt <- sendPGIPData (hetsOpts nwSt) nwPgD
                 return (nwPgipSt, nwSt)
          else do
                 nwPgD' <- sendMSGData (hetsOpts nwSt) nwPgD
                 return (nwPgD', nwSt)

-- | Comunicate over a port
cmdlListenOrConnect2Port :: HetcatsOpts -> CmdlState -> IO CmdlState
cmdlListenOrConnect2Port opts state = do
    let portNb = listen opts
        conPN = connectP opts
        hostName = connectH opts
        swXML = xmlFlag opts
    servH <- if portNb /= -1 then do
        putIfVerbose opts 1 $ "Starting hets. Listen to port " ++ show portNb
        servSock <- listenOn $ PortNumber $ fromIntegral portNb
        (servH, _, _) <- accept servSock
        return servH
      else if conPN /= -1 then do
        putIfVerbose opts 1 $ "Starting hets. Connecting to port "
          ++ show conPN ++ " on host " ++ hostName
        connectTo hostName $ PortNumber $ fromIntegral conPN
      else error "cmdlListenOrConnect2Port: missing port number"
    cmdlStartLoop swXML servH servH 1000 state

{- | Reads from a handle, it waits only for a certain amount of time,
if no input comes it will return Nothing -}
timeoutReadPacket :: Int -> CmdlPgipState -> IO (Maybe String)
timeoutReadPacket untilTimeout st = do
    let h = hin st
    smtmp <- hWaitForInput h untilTimeout
    if smtmp then do
            ms <- if useXML st
                    then readPacket [] h
                    else hGetLine h
            return $ Just ms
      else return Nothing

-- | Waits until it reads an entire XML packet
readPacket :: String -> Handle -> IO String
readPacket acc hf = do
    tmp <- hGetLine hf
    let str = acc ++ tmp ++ "\n"
    if isInfixOf "</pgip>" tmp
      then return str
      else readPacket str hf

cmdlStartLoop :: Bool -> Handle -> Handle -> Int -> CmdlState
              -> IO CmdlState
cmdlStartLoop swXML h_in h_out timeOut state = do
    pgData <- genCMDLPgipState swXML h_in h_out timeOut
    let pgD = addPGIPReady $ addPGIPHandshake $ resetPGIPData pgData
    pgD' <- sendPGIPData (hetsOpts state) pgD
    waitLoop pgD' state

waitLoop :: CmdlPgipState -> CmdlState -> IO CmdlState
waitLoop pgData state = do
  (pgData', state') <- communicationStep pgData state
  if stop pgData'
    then return state'
    else waitLoop pgData' state'

{- | Runs a shell in which the communication is expected to be
through XML packets -}
cmdlRunXMLShell :: CmdlState -> IO CmdlState
cmdlRunXMLShell = cmdlStartLoop True stdin stdout (-1)

-- | Processes a list of input files
processInput :: HetcatsOpts -> [FilePath] -> CmdlState -> IO CmdlState
processInput opts ls state = case ls of
    [] -> return state
    l : ll -> (case guess l GuessIn of
               ProofCommand -> cmdlProcessScriptFile True
               _ -> cUse) l state >>= processInput opts ll

cmdlRun :: HetcatsOpts -> IO CmdlState
cmdlRun opts =
  processInput opts (infiles opts) (emptyCmdlState opts) >>=
  if isRemote opts
    then cmdlListenOrConnect2Port opts
    else if interactive opts
           then if xmlFlag opts
                  then cmdlRunXMLShell
                  else cmdlRunShell
           else return

processString :: [CmdlXMLcommands] -> String -> CmdlState -> CmdlPgipState
  -> IO (CmdlState, CmdlPgipState)
processString pl str st pgSt = do
  (nwSt, mCmd) <- cmdlProcessString "" 0 str st
  postProcessCmd pl nwSt pgSt mCmd

-- copy messages to pgip state
processMsgs :: CmdlState -> CmdlPgipState -> (CmdlPgipState, String)
processMsgs nwSt pgSt =
  let o = output nwSt
      ms = outputMsg o
      ws = warningMsg o
      es = errorMsg o
      -- there should be at most one
  in (if null es then addPGIPAnswer ms ws pgSt else addPGIPError es pgSt, es)

processCommand :: [CmdlXMLcommands] -> Command -> CmdlState -> CmdlPgipState
  -> IO (CmdlState, CmdlPgipState)
processCommand pl cmd st pgSt = do
  nwSt <- cmdlProcessCmd cmd st
  postProcessCmd pl nwSt pgSt (Just cmd)

-- postprocess a previously run command and recurse
postProcessCmd :: [CmdlXMLcommands] -> CmdlState -> CmdlPgipState
  -> Maybe Command -> IO (CmdlState, CmdlPgipState)
postProcessCmd pl nwSt0 pgSt mCmd = let
  (pgSt1, es) = processMsgs nwSt0 pgSt
  nwSt = nwSt0 { output = emptyCmdlMessage } -- remove messages form cmdl state
  in if null es then processCmds pl nwSt $ informCmd nwSt mCmd pgSt1 else
  return (nwSt, addPGIPReady pgSt1)

informCmd :: CmdlState -> Maybe Command -> CmdlPgipState -> CmdlPgipState
informCmd nwSt mCmd pgSt1 = let opts = hetsOpts nwSt in
  case (getMaybeLib $ intState nwSt, mCmd) of
          (Just (lN, lEnv), Just cmd) -> case cmd of
            SelectCmd LibFile _ ->
              informDGraph opts lN lEnv $ addPGIPElement pgSt1
                $ add_attr (mkAttr "url" $ libNameToFile lN)
                $ unode "informfileloaded" ()
            GlobCmd g | g < ProveCurrent ->
              informDGraph opts lN lEnv pgSt1
            _ -> pgSt1
          _ -> pgSt1

informDGraph :: HetcatsOpts -> LibName -> LibEnv -> CmdlPgipState
  -> CmdlPgipState
informDGraph opts lN lEnv pgSt =
  addPGIPElement pgSt $ unode "informdevelopmentgraph"
    $ ToXml.dGraph opts lEnv lN
    $ lookupDGraph lN lEnv

-- | Executes given commands and returns output message and the new state
processCmds :: [CmdlXMLcommands] -> CmdlState -> CmdlPgipState
  -> IO (CmdlState, CmdlPgipState)
processCmds cmds state pgipSt = do
    let opts = hetsOpts state
    case cmds of
     [] -> return (state, addPGIPReady pgipSt)
            {- ensures that the response is ended with a ready element
            such that the broker does wait for more input -}
     XmlExecute str : l -> processString l str state (resetPGIPData pgipSt)
     XmlExit : l -> processCmds l state $
         addPGIPAnswer "Exiting prover" [] pgipSt { stop = True }
     XmlAskpgip : l -> processCmds l state $ addPGIPHandshake pgipSt
     XmlProverInit : l -> processCmds l (emptyCmdlState opts) $
         addPGIPAnswer "Prover state was reset" [] pgipSt
     XmlStartQuiet : l -> do
         {- To inform that quiet mode is enabled we need to send this with the
         old options. -}
         let pgD = addPGIPReady $ addPGIPAnswer "Quiet mode enabled" [] pgipSt
         pgipSt' <- if useXML pgD
                      then sendPGIPData opts pgD
                      else sendMSGData opts pgD
         processCmds l (state { hetsOpts = opts { verbose = 0 } }) pgipSt'
     XmlStopQuiet : l ->
                  processCmds l (state { hetsOpts = opts { verbose = 1 } }) $
                    addPGIPAnswer "Quiet mode disabled" [] pgipSt
     XmlOpenGoal str : l -> processCommand l (SelectCmd Goal str) state pgipSt
     XmlCloseGoal str : l -> processCommand (XmlGiveUpGoal str : l)
         (GlobCmd ProveCurrent) state pgipSt
     XmlGiveUpGoal str : l -> processString l ("del goals " ++ str) state pgipSt
     XmlUnknown str : l -> processCmds l state $
           addPGIPAnswer [] ("Unknown command: " ++ str) pgipSt
     XmlUndo : l -> processCommand l (GlobCmd UndoCmd) state pgipSt
     XmlRedo : l -> processCommand l (GlobCmd RedoCmd) state pgipSt
     XmlForget str : l -> processString l ("del axioms " ++ str) state pgipSt
     XmlOpenTheory str : l -> processString l str state pgipSt
     XmlCloseTheory _ : l -> let
         nwSt = case i_state $ intState state of
           Nothing -> state
           Just ist -> add2hist [IStateChange $ Just ist] $ state
             { intState = (intState state)
                 { i_state = Just $ emptyIntIState (i_libEnv ist)
                     $ i_ln ist }}
         in processCmds l nwSt $ addPGIPAnswer "Theory closed" [] pgipSt
     XmlCloseFile _ : l -> processCmds l (emptyCmdlState opts)
                   (addPGIPAnswer "File closed" [] pgipSt)
     XmlParseScript str : _ ->
         processCmds [] state . addPGIPElement pgipSt $ addPGIPMarkup str
     XmlLoadFile str : l ->
         processCommand l (SelectCmd LibFile str) state pgipSt

{- deleting axioms or goals should be implemented via a select command after
inspecting the current axioms or goals. The current strings do not work. -}