module Text.XML.HaXml.ByteStringPP
  (
  
    document
  
  ,   content
  
  ,   element
  
  , doctypedecl
  
  ,   prolog
  
  ,   cp
  ) where
import Prelude hiding (maybe,either,elem,concat)
import Data.Maybe hiding (maybe)
import Data.List (intersperse)
import Data.ByteString.Lazy.Char8 (ByteString(), concat, pack, singleton
                                  , intercalate, append, elem, empty)
import Text.XML.HaXml.Types
import Text.XML.HaXml.Namespaces
either :: (t -> t1) -> (t2 -> t1) -> Either t t2 -> t1
either f _ (Left x)  = f x
either _ g (Right x) = g x
maybe :: (t -> ByteString) -> Maybe t -> ByteString
maybe _ Nothing  = empty
maybe f (Just x) = f x
infixl 6 <>
infixl 6 <+>
infixl 5 $$
(<>)   :: ByteString   -> ByteString -> ByteString 
hcat   :: [ByteString] -> ByteString               
(<+>)  :: ByteString   -> ByteString -> ByteString 
hsep   :: [ByteString] -> ByteString               
($$)   :: ByteString   -> ByteString -> ByteString 
                                                   
vcat   :: [ByteString] -> ByteString       
sep    :: [ByteString] -> ByteString       
fsep   :: [ByteString] -> ByteString       
nest   :: Int -> ByteString -> ByteString  
(<>)  b1 b2  = b1 `append` b2
(<+>) b1 b2  = b1 <> pack " " <> b2
($$)  b1 b2  = b1 <> pack "\n" <> b2
hcat = Data.ByteString.Lazy.Char8.concat
hsep = Data.ByteString.Lazy.Char8.intercalate (singleton ' ')
vcat = Data.ByteString.Lazy.Char8.intercalate (singleton '\n')
sep  = hsep
text :: [Char] -> ByteString
text = pack
fsep = sep
nest _ b = pack " " <> b
parens :: ByteString -> ByteString
parens p = pack "(" <> p <> pack ")"
document :: Document i -> ByteString
prolog   :: Prolog     -> ByteString
xmldecl  :: XMLDecl    -> ByteString
misc     :: Misc       -> ByteString
sddecl   :: Bool       -> ByteString
doctypedecl   :: DocTypeDecl   -> ByteString
markupdecl    :: MarkupDecl    -> ByteString
cp            :: CP            -> ByteString
element   :: Element i -> ByteString
attribute :: Attribute -> ByteString
content   :: Content i -> ByteString
document (Document p _ e m)= prolog p $$ element e $$ vcat (Prelude.map misc m)
prolog (Prolog x m1 dtd m2)= maybe xmldecl x $$
                             vcat (Prelude.map misc m1) $$
                             maybe doctypedecl dtd $$
                             vcat (Prelude.map misc m2)
xmldecl (XMLDecl v e sd)   = text "<?xml version='" <> text v <> text "'" <+>
                             maybe encodingdecl e <+>
                             maybe sddecl sd <+>
                             text "?>"
misc (Comment s)           = text "<!--" <+> text s <+> text "-->"
misc (PI (n,s))            = text "<?" <> text n <+> text s <+> text "?>"
sddecl sd   | sd           = text "standalone='yes'"
            | otherwise    = text "standalone='no'"
doctypedecl (DTD n eid ds) = if Prelude.null ds then
                                  hd <> text ">"
                             else hd <+> text " [" $$
                                  vcat (Prelude.map markupdecl ds) $$ text "]>"
                           where hd = text "<!DOCTYPE" <+> qname n <+>
                                      maybe externalid eid
markupdecl (Element e)     = elementdecl e
markupdecl (AttList a)     = attlistdecl a
markupdecl (Entity e)      = entitydecl e
markupdecl (Notation n)    = notationdecl n
markupdecl (MarkupMisc m)  = misc m
element (Elem n as []) = text "<" <> qname n <+>
                         fsep (Prelude.map attribute as) <> text "/>"
element e@(Elem n as cs)
    | isText (head cs) = text "<" <> qname n <+> fsep (Prelude.map attribute as) <>
                         text ">" <> hcat (Prelude.map content cs) <>
                         text "</" <> qname n <> text ">"
    | otherwise        = let (d,c) = carryelem e empty
                         in d <> c
isText :: Content t -> Bool
isText (CString _ _ _) = True
isText (CRef _ _)      = True
isText _               = False
carryelem :: Element t -> ByteString -> (ByteString, ByteString)
carryelem (Elem n as []) c
                       = ( c <>
                           text "<" <> qname n <+> fsep (Prelude.map attribute as)
                         , text "/>")
carryelem (Elem n as cs) c
    | otherwise        =  let (cs0,d0) = carryscan carrycontent cs (text ">")
                          in
                          ( c <>
                            text "<" <> qname n <+> fsep (Prelude.map attribute as) $$
                            nest 2 (vcat cs0) <> 
                            d0 <> text "</" <> qname n
                          , text ">")
carrycontent :: Content t -> ByteString -> (ByteString, ByteString)
carrycontent (CElem e _) c   = carryelem e c
carrycontent (CString False s _) c = (c <> chardata s, empty)
carrycontent (CString True  s _) c = (c <> cdsect s, empty)
carrycontent (CRef r _) c    = (c <> reference r, empty)
carrycontent (CMisc m _) c   = (c <> misc m, empty)
carryscan :: (a->c->(b,c)) -> [a] -> c -> ([b],c)
carryscan _ []     c = ([],c)
carryscan f (a:as) c = let (b, c0) = f a c
                           (bs,c1) = carryscan f as c0
                       in (b:bs, c1)
attribute (n,v)             = text (printableName n) <> text "=" <> attvalue v
content (CElem e _)         = element e
content (CString False s _) = chardata s
content (CString True s _)  = cdsect s
content (CRef r _)          = reference r
content (CMisc m _)         = misc m
elementdecl :: ElementDecl -> ByteString
elementdecl (ElementDecl n cs) = text "<!ELEMENT" <+> qname n <+>
                                 contentspec cs <> text ">"
contentspec :: ContentSpec -> ByteString
contentspec EMPTY              = text "EMPTY"
contentspec ANY                = text "ANY"
contentspec (Mixed m)          = mixed m
contentspec (ContentSpec c)    = cp c
cp (TagName n m)       = qname n <> modifier m
cp (Choice cs m)       = parens (hcat (intersperse (text "|") (Prelude.map cp cs))) <>
                           modifier m
cp (Seq cs m)          = parens (hcat (intersperse (text ",") (Prelude.map cp cs))) <>
                           modifier m
modifier :: Modifier -> ByteString
modifier None          = empty
modifier Query         = text "?"
modifier Star          = text "*"
modifier Plus          = text "+"
mixed :: Mixed -> ByteString
mixed  PCDATA          = text "(#PCDATA)"
mixed (PCDATAplus ns)  = text "(#PCDATA |" <+>
                         hcat (intersperse (text "|") (Prelude.map qname ns)) <>
                         text ")*"
attlistdecl :: AttListDecl -> ByteString
attlistdecl (AttListDecl n ds) = text "<!ATTLIST" <+> qname n <+>
                                 fsep (Prelude.map attdef ds) <> text ">"
attdef :: AttDef -> ByteString
attdef (AttDef n t d)          = qname n <+> atttype t <+> defaultdecl d
atttype :: AttType -> ByteString
atttype  StringType            = text "CDATA"
atttype (TokenizedType t)      = tokenizedtype t
atttype (EnumeratedType t)     = enumeratedtype t
tokenizedtype :: TokenizedType -> ByteString
tokenizedtype ID               = text "ID"
tokenizedtype IDREF            = text "IDREF"
tokenizedtype IDREFS           = text "IDREFS"
tokenizedtype ENTITY           = text "ENTITY"
tokenizedtype ENTITIES         = text "ENTITIES"
tokenizedtype NMTOKEN          = text "NMTOKEN"
tokenizedtype NMTOKENS         = text "NMTOKENS"
enumeratedtype :: EnumeratedType -> ByteString
enumeratedtype (NotationType n)= notationtype n
enumeratedtype (Enumeration e) = enumeration e
notationtype :: [[Char]] -> ByteString
notationtype ns                = text "NOTATION" <+>
                                 parens (hcat (intersperse (text "|") (Prelude.map text ns)))
enumeration :: [[Char]] -> ByteString
enumeration ns                 = parens (hcat (intersperse (text "|") (Prelude.map nmtoken ns)))
defaultdecl :: DefaultDecl -> ByteString
defaultdecl  REQUIRED          = text "#REQUIRED"
defaultdecl  IMPLIED           = text "#IMPLIED"
defaultdecl (DefaultTo a f)    = maybe (const (text "#FIXED")) f <+> attvalue a
reference :: Reference -> ByteString
reference (RefEntity er)       = entityref er
reference (RefChar cr)         = charref cr
entityref :: [Char] -> ByteString
entityref n                    = text "&" <> text n <> text ";"
charref :: (Show a) => a -> ByteString
charref c                      = text "&#" <> text (show c) <> text ";"
entitydecl :: EntityDecl -> ByteString
entitydecl (EntityGEDecl d)    = gedecl d
entitydecl (EntityPEDecl d)    = pedecl d
gedecl :: GEDecl -> ByteString
gedecl (GEDecl n ed)           = text "<!ENTITY" <+> text n <+> entitydef ed <>
                                 text ">"
pedecl :: PEDecl -> ByteString
pedecl (PEDecl n pd)           = text "<!ENTITY %" <> text n <+> pedef pd <>
                                 text ">"
entitydef :: EntityDef -> ByteString
entitydef (DefEntityValue ew)  = entityvalue ew
entitydef (DefExternalID i nd) = externalid i <+> maybe ndatadecl nd
pedef :: PEDef -> ByteString
pedef (PEDefEntityValue ew)    = entityvalue ew
pedef (PEDefExternalID eid)    = externalid eid
externalid :: ExternalID -> ByteString
externalid (SYSTEM sl)         = text "SYSTEM" <+> systemliteral sl
externalid (PUBLIC i sl)       = text "PUBLIC" <+> pubidliteral i <+>
                                 systemliteral sl
ndatadecl :: NDataDecl -> ByteString
ndatadecl (NDATA n)            = text "NDATA" <+> text n
notationdecl :: NotationDecl -> ByteString
notationdecl (NOTATION n e)    = text "<!NOTATION" <+> text n <+>
                                 either externalid publicid e <>
                                 text ">"
publicid :: PublicID -> ByteString
publicid (PUBLICID p)          = text "PUBLICID" <+> pubidliteral p
encodingdecl :: EncodingDecl -> ByteString
encodingdecl (EncodingDecl s)  = text "encoding='" <> text s <> text "'"
nmtoken :: [Char] -> ByteString
nmtoken s                      = text s
attvalue :: AttValue -> ByteString
attvalue (AttValue esr)        = text "\"" <>
                                 hcat (Prelude.map (either text reference) esr) <>
                                 text "\""
entityvalue :: EntityValue -> ByteString
entityvalue (EntityValue evs)
  | containsDoubleQuote evs    = text "'"  <> hcat (Prelude.map ev evs) <> text "'"
  | otherwise                  = text "\"" <> hcat (Prelude.map ev evs) <> text "\""
ev :: EV -> ByteString
ev (EVString s)                = text s
ev (EVRef r)                   = reference r
pubidliteral :: PubidLiteral -> ByteString
pubidliteral (PubidLiteral s)
    | toWord8 '"' `elem` (pack s) = text "'" <> text s <> text "'"
    | otherwise                = text "\"" <> text s <> text "\""
systemliteral :: SystemLiteral -> ByteString
systemliteral (SystemLiteral s)
    | toWord8 '"' `elem` (pack s) = text "'" <> text s <> text "'"
    | otherwise                = text "\"" <> text s <> text "\""
chardata, cdsect :: [Char] -> ByteString
chardata s                     =  text s
cdsect c                       = text "<![CDATA[" <> chardata c <> text "]]>"
qname n                        = text (printableName n)
toWord8 :: (Enum a, Enum a1) => a1 -> a
toWord8 = toEnum . fromEnum
containsDoubleQuote :: [EV] -> Bool
containsDoubleQuote evs = any csq evs
    where csq (EVString s) = toWord8 '"' `elem` (pack s)
          csq _            = False