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

-- 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)

--

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


module Ideas.Text.MathML
   ( MathML(..), xml2mathml, mathml2xml
   ) where

import Data.Maybe
import Ideas.Text.XML
import Ideas.Utils.Uniplate hiding (children)

data MathML = MRow [MathML]
            | MId String
            | MNumber String
            | MOperator String
            | MString String
            | MText String
            | MSqrt MathML
            | MRoot MathML MathML
            | MSup MathML MathML -- base, superscript

            | MSub MathML MathML -- base, subscript

            | MSubSup MathML MathML MathML -- base, subscript, superscript

            | MFrac MathML MathML -- numerator, denominator

            | MFenced String String MathML -- left, right, content

            | MSpace
            | MStyle
            | MPadded
            | MPhantom
            | MError
            | MEnclose
            | MUnder
            | MOver
            | MUnderOver
            | MTable
            | MTableRow
            | MLabeledTableRow
            | MTableData
   deriving (Show, Eq)
{-
from: https://www.w3.org/TR/MathML2/chapter3.html#presm.mn @ 3.2.1
Conversely, since mn is a presentation element, there are a few situations where it may desirable to include arbitrary text in the content of an mn that should merely render as a numeric literal
-}

instance ToXML MathML where
   toXML = mathml2xml

instance InXML MathML where
   fromXML = either fail return . xml2mathml

instance Uniplate MathML where
   uniplate math =
      case math of
         MRow xs       -> plate MRow ||* xs
         _             -> plate math

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

-- conversion functions: XML <-> MathML


xml2mathml :: XML -> Either String MathML
xml2mathml = rec
 where
   rec xml = case name xml of
       "mrow"       -> MRow <$> mapM rec (children xml)
       "mi"         -> return (MId (getData xml))
       "mn"         -> return (MNumber (getData xml))
       "mo"         -> return (MOperator (getData xml))
       "ms"         -> return (MString (getData xml))
       "mtext"      -> return (MText (getData xml))
       "mroot"      -> case children xml of
                          [c, d] -> MRoot <$> rec c <*> rec d
                          _ -> fail "invalid mroot"
       "msup"       -> case children xml of
                          [c, d] -> MSup <$> rec c <*> rec d
                          _ -> fail "invalid msup"
       "msub"       -> case children xml of
                          [c, d] -> MSub <$> rec c <*> rec d
                          _ -> fail "invalid msub"
       "msubsup"    -> case children xml of
                          [c, d, e] -> MSubSup <$> rec c <*> rec d <*> rec e
                          _ -> fail "invalid msubsup"
       "mfrac"      -> case children xml of
                          [c, d] -> MFrac <$> rec c <*> rec d
                          _ -> fail "invalid mfrac"
       "mfenced"    -> case children xml of
                          [c] -> MFenced (fromMaybe "(" (findAttribute "open" xml)) (fromMaybe ")" (findAttribute "close" xml)) <$> rec c
                          _ -> fail "invalid mfenced"
       "mspace"     -> return MSpace
       "mtable"     -> return MTable
       "mtr"        -> return MTableRow
       "mlabeledtr" -> return MLabeledTableRow
       "munder"     -> return MUnder
       "mover"      -> return MOver
       "munderover" -> return MUnderOver
       -- below are cases that have 1* arguments, when none-one an mrow is implied.

       "math"       -> impliedMRow xml
       "msqrt"      -> MSqrt <$> impliedMRow xml
       "mphantom"   -> return MPhantom
       "mpadded"    -> return MPadded
       "mstyle"     -> return MStyle
       "merror"     -> return MError
       "mtd"        -> return MTableData
       "menclose"   -> return MEnclose
       _ -> fail ("unsupported MathML: " ++ show xml)

   impliedMRow :: XML -> Either String MathML
   impliedMRow xml =
      case children xml of
         [x] -> rec x
         xs  -> MRow <$> mapM rec xs

mathml2xml :: MathML -> XML
mathml2xml = makeXML "math" . rec
 where
   rec :: MathML -> XMLBuilder
   rec math =
      case math of
         MRow ms           -> element "mrow" (map rec ms)
         MId s             -> element "mi" [string s]
         MNumber s         -> element "mn" [string s]
         MOperator s       -> element "mo" [string s]
         MString s         -> element "ms" [string s]
         MText s           -> element "mtext" [string s]
         MSqrt m           -> element "msqrt" [rec m]
         MRoot m1 m2       -> element "mroot" [rec m1, rec m2]
         MSup m1 m2        -> element "msup" [rec m1, rec m2]
         MSub m1 m2        -> element "msub" [rec m1, rec m2]
         MSubSup m1 m2 m3  -> element "msubsup" [rec m1, rec m2, rec m3]
         MFrac m1 m2       -> element "mfrac" [rec m1, rec m2]
         MFenced s1 s2 m   -> element "mfenced" ["open" .=. s1, "close" .=. s2, rec m]
         MSpace            -> element "mspace" []
         MStyle            -> element "mstyle" []
         MPadded           -> element "mpadded" []
         MPhantom          -> element "mphantom" []
         MError            -> element "merror" []
         MEnclose          -> element "menclose" []
         MUnder            -> element "munder" []
         MOver             -> element "mover" []
         MUnderOver        -> element "munderover" []
         MTable            -> element "mtable" []
         MTableRow         -> element "mtr" []
         MLabeledTableRow  -> element "mlabeledtr" []
         MTableData        -> element "mtd" []