-----------------------------------------------------------------------------
-- Copyright 2009, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Text.OpenMath.Object 
   ( OMOBJ(..), xml2omobj, omobj2xml
   ) where

import Text.XML
import Data.Char (isSpace)
import Data.Maybe
import Text.OpenMath.Symbol

-- internal representation for OpenMath objects
data OMOBJ = OMI Integer 
           | OMF Float 
           | OMV String 
           | OMS Symbol 
           | OMA [OMOBJ] 
           | OMBIND OMOBJ [String] OMOBJ
   deriving (Show, Eq)

instance InXML OMOBJ where
   toXML   = omobj2xml
   fromXML = either fail return . xml2omobj

----------------------------------------------------------
-- conversion functions: XML <-> OMOBJ
   
xml2omobj :: XML -> Either String OMOBJ
xml2omobj xml =
   case xml of  
      Element "OMOBJ" _ [Right e] -> rec e
      _ -> fail $ "expected an OMOBJ tag" ++ show xml
 where
   rec xml =
      case content xml of
      
         _ | name xml == "OMA" -> do
            ys <- mapM rec (children xml) 
            return (OMA ys)
            
         [] | name xml == "OMS" -> do
            let mcd = findAttribute "cd" xml
            name <- findAttribute "name" xml
            return (OMS (Symbol mcd name))

         [Left s] | name xml == "OMI" ->
            case reads s of
               [(i, xs)] | all isSpace xs -> return (OMI i)
               _ -> fail "invalid integer in OMI"
         
         [] | name xml == "OMF" -> do
            s <- findAttribute "dec" xml 
            case reads s of
               [(fp, xs)] | all isSpace xs -> return (OMF fp)
               _ -> fail "invalid floating-point in OMF"
                    
         [] | name xml == "OMV" -> do
            s <- findAttribute "name" xml
            return (OMV s)
        
         [Right x1, Right x2, Right x3] | name xml == "OMBIND" -> do
            y1 <- rec x1
            y2 <- recOMBVAR x2
            y3 <- rec x3
            return (OMBIND y1 y2 y3)

         _ -> fail ("invalid tag " ++ show (name xml))
   
   recOMBVAR xml
      | name xml == "OMBVAR" =
           let f (Right (OMV s)) = return s
               f this = fail $ "expected tag OMV in OMBVAR, but found " ++ show this
           in mapM (f . rec) (children xml)
      | otherwise = 
           fail ("expected tag OMVAR, but found " ++ show tag)
   
omobj2xml :: OMOBJ -> XML
omobj2xml object = makeXML "OMOBJ" $ do
   "xmlns"   .=. "http://www.openmath.org/OpenMath"
   "version" .=. "2.0"
   "cdbase"  .=. "http://www.openmath.org/cd"
   rec object
 where
   rec omobj =
      case omobj of
         OMI i  -> element "OMI" (text (show i))
         OMF f  -> element "OMF" ("dec" .=. show f)
         OMV v  -> element "OMV" ("name" .=. v)
         OMA xs -> element "OMA" (mapM_ rec xs)
         OMS s  -> element "OMS" $ do
            "cd"   .=. fromMaybe "unknown" (dictionary s)
            "name" .=. symbolName s
         OMBIND x ys z -> element "OMBIND" $ do 
            rec x 
            element "OMBVAR" (mapM_ (rec . OMV) ys) 
            rec z