{-# LANGUAGE GADTs #-}
-----------------------------------------------------------------------------

-- Copyright 2019, Ideas project team. This file is distributed under the

-- terms of the Apache License 2.0. For more information, see the files

-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.

-----------------------------------------------------------------------------

-- |

-- Maintainer  :  bastiaan.heeren@ou.nl

-- Stability   :  provisional

-- Portability :  portable (depends on ghc)

--

-- Services using XML notation

--

-----------------------------------------------------------------------------


module Ideas.Encoding.EncoderXML
   ( XMLEncoder
   , xmlEncoder, encodeState
   ) where

import Data.Char
import Data.List
import Data.Maybe
import Data.Monoid
import Ideas.Common.Library hiding (exerciseId)
import Ideas.Encoding.Encoder
import Ideas.Encoding.OpenMathSupport
import Ideas.Encoding.Request hiding (XML)
import Ideas.Encoding.RulesInfo (rulesInfoXML)
import Ideas.Encoding.StrategyInfo
import Ideas.Service.Diagnose
import Ideas.Service.FeedbackScript.Syntax
import Ideas.Service.State
import Ideas.Service.Types
import Ideas.Text.OpenMath.Object
import Ideas.Text.XML
import Ideas.Utils.Prelude (munless)
import qualified Ideas.Service.FeedbackText as FeedbackText
import qualified Ideas.Service.ProblemDecomposition as PD

-----------------


type XMLEncoder a = EncoderX a XMLBuilder

xmlEncoder :: TypedEncoder a XMLBuilder
xmlEncoder =
   (encodeDiagnosis, tDiagnosis) <?>
   (encodeDecompositionReply, PD.tReply) <?>
   (encodeDerivation, tDerivation (tPair tRule tEnvironment) tContext) <?>
   (encodeDerivationText, tDerivation tString tContext) <?>
   (encodeDifficulty, tDifficulty) <?>
   (encodeMessage, FeedbackText.tMessage) <?>
   \(val ::: tp) ->
   case tp of
      -- meta-information

      Tag "RuleShortInfo" t ->
         case equal t (Const Rule) of
            Just f  -> ruleShortInfo (f val)
            Nothing -> fail "rule short info"
      Tag "RulesInfo" _ -> do
         ex    <- getExercise
         useOM <- useOpenMath <$> getRequest
         return (rulesInfoXML ex (buildExpression useOM ex))
      Tag "elem" t ->
         tag "elem" (xmlEncoder (val ::: t))
      -- special cases for lists

      List (Const Rule) ->
         encodeAsList [ ruleShortInfo r | r <- val ]
      List t ->
         encodeAsList [ xmlEncoder (a ::: t) | a <- val ]
      -- standard

      Tag _ t    -> xmlEncoder (val ::: t)
      Iso iso t  -> xmlEncoder (to iso val ::: t)
      Pair t1 t2 -> xmlEncoder (fst val ::: t1) <>
                    xmlEncoder (snd val ::: t2)
      t1 :|: t2  -> case val of
                       Left  a -> xmlEncoder (a ::: t1)
                       Right b -> xmlEncoder (b ::: t2)
      Unit       -> mempty
      Const t    -> xmlEncoderConst (val ::: t)
      _ -> fail $ show tp

xmlEncoderConst :: TypedValue (Const a) -> XMLEncoder a
xmlEncoderConst tv@(val ::: tp) =
   case tp of
      SomeExercise -> case val of
                         Some a -> exerciseInfo a
      Strategy     -> builder (strategyToXML val)
      Rule         -> "ruleid" .=. show val
      Constraint   -> "constraint" .=. show val
      State        -> encodeState val
      Context      -> encodeContext val
      Location     -> encodeLocation val
      Environment  -> encodeEnvironment val
      Term         -> builderXML (toOMOBJ val)
      Text         -> encodeText val
      Bool         -> string (showBool val)
      XML          -> builder val
      _            -> text tv

encodeState :: State a -> XMLEncoder a
encodeState st = element "state"
   [ if withoutPrefix st
     then mempty
     else element "prefix" [string (show (statePrefix st))]
   , encodeContext (stateContext st)
   ]

encodeContext :: Context a -> XMLEncoder a
encodeContext ctx = do
   ex    <- getExercise
   useOM <- useOpenMath <$> getRequest
   maybe (error "encodeContext") (buildExpression useOM ex) (fromContext ctx)
      <>
      let values = bindings (withLoc ctx)
          loc    = fromLocation (location ctx)
          withLoc
             | null loc  = id
             | otherwise = insertRef (makeRef "location") loc
      in munless (null values) $ element "context"
            [  element "item"
                  [ "name"  .=. showId tb
                  , case getTermValue tb of
                       term | useOM ->
                          builder (omobj2xml (toOMOBJ term))
                       _ -> "value" .=. showValue tb
                  ]
            | tb <- values
            ]

buildExpression :: BuildXML b => Bool -> Exercise a -> a -> b
buildExpression useOM ex
   | useOM     = either msg builderXML . toOpenMath ex
   | otherwise = tag "expr" . string . prettyPrinter ex
 where
   msg s = error ("Error encoding term in OpenMath: " ++ s)

encodeLocation :: Location -> XMLEncoder a
encodeLocation loc = "location" .=. show loc

encodeEnvironment :: HasEnvironment env => env -> XMLEncoder a
encodeEnvironment env = mconcat [ encodeTypedBinding b | b <- bindings env ]

encodeTypedBinding :: Binding -> XMLEncoder a
encodeTypedBinding tb = do
   useOM <- useOpenMath <$> getRequest
   tag "argument" $
      ("description" .=. showId tb) <>
      case getTermValue tb of
         term | useOM -> builder $
            omobj2xml $ toOMOBJ term
         _ -> string (showValue tb)

encodeDerivation :: Derivation (Rule (Context a), Environment) (Context a) -> XMLEncoder a
encodeDerivation d =
   let xs = [ (s, a) | (_, s, a) <- triples d ]
   in xmlEncoder (xs ::: tList (tPair (tPair tRule tEnvironment) tContext))

encodeDerivationText :: Derivation String (Context a) -> XMLEncoder a
encodeDerivationText d = encodeAsList
   [ ("ruletext" .=. s) <> encodeContext a
   | (_, s, a) <- triples d
   ]

ruleShortInfo :: Rule (Context a) -> XMLEncoder a
ruleShortInfo r = mconcat
   [ "name"        .=. showId r
   , "buggy"       .=. showBool (isBuggy r)
   , "arguments"   .=. show (length (getRefs r))
   , "rewriterule" .=. showBool (isRewriteRule r)
   ]

encodeDifficulty :: Difficulty -> XMLEncoder a
encodeDifficulty d =
   "difficulty" .=. show d

encodeText :: Text -> XMLEncoder a
encodeText txt = do
   ex    <- getExercise
   useOM <- useOpenMath <$> getRequest
   mconcat (intersperse (string " ") [ encodeItem ex useOM item | item <- textItems txt ])
 where
   encodeItem ex useOM item =
      case item of
         TextTerm a -> fromMaybe (text item) $ do
            v <- hasTermView ex
            b <- match v a
            return (buildExpression useOM ex b)
         _ -> text item

encodeMessage :: FeedbackText.Message -> XMLEncoder a
encodeMessage msg =
   element "message"
      [ case FeedbackText.accept msg of
           Just b  -> "accept" .=. showBool b
           Nothing -> mempty
      , encodeText (FeedbackText.text msg)
      ]

encodeDiagnosis :: Diagnosis a -> XMLEncoder a
encodeDiagnosis diagnosis =
   case diagnosis of
      SyntaxError s -> element "syntaxerror" [string s]
      Buggy env r -> element "buggy"
         [encodeEnvironment env, "ruleid" .=. showId r]
      NotEquivalent s ->
          if null s then emptyTag "notequiv"
                    else element "notequiv" [ "reason" .=.  s ]
      Similar b st -> element "similar"
         ["ready" .=. showBool b, encodeState st]
      WrongRule b st mr -> element "wrongrule" $
         [ "ready" .=. showBool b, encodeState st ] ++
         maybe [] (\r -> ["ruleid" .=. showId r]) mr
      Expected b st r -> element "expected"
         ["ready" .=. showBool b, encodeState st, "ruleid" .=. showId r]
      Detour b st env r -> element "detour"
         [ "ready" .=. showBool b, encodeState st
         , encodeEnvironment env, "ruleid" .=. showId r
         ]
      Correct b st -> element "correct"
         ["ready" .=. showBool b, encodeState st]
      Unknown b st -> element "unknown"
         ["ready" .=. showBool b, encodeState st]

encodeDecompositionReply :: PD.Reply a -> XMLEncoder a
encodeDecompositionReply reply =
   case reply of
      PD.Ok loc st ->
         element "correct" [encLoc loc, encodeState st]
      PD.Incorrect eq loc st env ->
         element "incorrect"
            [ "equivalent" .=. showBool eq
            , encLoc loc
            , encodeState st
            , encodeEnvironment env
            ]
 where
    encLoc = tag "location" . text

exerciseInfo :: Exercise b -> XMLEncoder a
exerciseInfo ex = mconcat
   [ "exerciseid"  .=. showId ex
   , "description" .=. description ex
   , "status"      .=. show (status ex)
   ]

------------------------------------------------

-- helpers


encodeAsList :: [XMLEncoder a] -> XMLEncoder a
encodeAsList = element "list" . map (tag "elem")

showBool :: Bool -> String
showBool = map toLower . show