module Text.XML.HaXml.XmlContent.Parser
  ( 
    Document(..)
  , Element(..)
  , ElemTag(..)
  , Content(..)
  , Attribute()
  , AttValue(..)
  , Prolog(..)
  , Reference(..)
  
  
  , XmlContent(..)
  , XmlAttributes(..)
  , XmlAttrType(..)
  
  , module Text.ParserCombinators.Poly
  , XMLParser
  , content, posnElement, element, interior, inElement, text, attributes
  , posnElementWith, elementWith, inElementWith
  , choice, definite 
  
  , mkElem, mkElemC, mkAttr
  , toText, toCData
  
  , maybeToAttr, defaultToAttr
  , definiteA, defaultA, possibleA, fromAttrToStr, toAttrFrStr
  , Defaultable(..)
  , str2attr, attr2str, attval
  , catMaybes   
  
  
  , module Text.XML.HaXml.TypeMapping
  
  , List1(..)
  , ANYContent(..)
  ) where
import Data.Maybe (catMaybes)
import Data.Char  (chr, isSpace)
import Text.XML.HaXml.Types
import Text.XML.HaXml.Namespaces
import Text.XML.HaXml.TypeMapping
import Text.XML.HaXml.Posn     (Posn)
import Text.XML.HaXml.Verbatim (Verbatim(verbatim))
import Text.ParserCombinators.Poly
#if defined(DEBUG)
import Debug.Trace(trace)
debug :: a -> String -> a
v `debug` s = trace s v
#else
debug :: t -> t1 -> t
v `debug` _ = v
#endif
attval :: (Read a) => Element i -> a
attval (Elem _ [(_,v@(AttValue _))] []) = read (show v)
mkAttr :: String -> String -> Attribute
mkAttr n v = (N n, AttValue [Left v])
mkElem :: XmlContent a => a -> [Content ()] -> Content ()
mkElem x cs  = CElem (Elem (N (showHType (toHType x) "")) [] cs) ()
mkElemC :: String -> [Content ()] -> Content ()
mkElemC x cs = CElem (Elem (N x) [] cs) ()
toText :: String -> [Content ()]
toText s = [CString False s ()]
toCData :: String -> [Content ()]
toCData s = [CString True s ()]
type XMLParser a = Parser (Content Posn) a
content :: String -> XMLParser (Content Posn)
content word = next `adjustErr` (++" when expecting "++word)
posnElementWith :: (String->String->Bool) -> [String]
                   -> XMLParser (Posn, Element Posn)
posnElementWith match tags = do
    { c <- content (formatted tags)
    ; case c of
          CElem e@(Elem t _ _) pos
              | any (match (localName t)) tags -> return (pos, e)
              | otherwise   -> fail ("Found a <"++printableName t
                                     ++">, but expected "
                                     ++formatted tags++"\nat "++show pos)
          CString b s pos
              | not b && all isSpace s -> posnElementWith match tags
                                                        
              | otherwise -> fail ("Found text content, but expected "
                                  ++formatted tags++"\ntext is: "++s
                                  ++"\nat "++show pos)
          CRef r pos -> fail ("Found reference, but expected "
                             ++formatted tags++"\nreference is: "++verbatim r
                             ++"\nat "++show pos)
          CMisc _ _ -> posnElementWith match tags  
    }
  where
    formatted [t]  = "a <"++t++">"
    formatted tgs = "one of"++ concatMap (\t->" <"++t++">") tgs
posnElement :: [String] -> XMLParser (Posn, Element Posn)
posnElement = posnElementWith (==)
element :: [String] -> XMLParser (Element Posn)
element tags = fmap snd (posnElement tags)
                                `debug` ("Element: "++unwords tags++"\n")
elementWith :: (String->String->Bool) -> [String] -> XMLParser (Element Posn)
elementWith match tags = fmap snd (posnElementWith match tags)
                                `debug` ("Element: "++unwords tags++"\n")
interior :: Element Posn -> XMLParser a -> XMLParser a
interior (Elem e _ cs) p =
    case runParser p cs of
        (Left msg, _) -> fail msg
        (Right x, []) -> return x
        (Right x, ds@(d:_))
            | all onlyMisc ds -> return x
            | otherwise       -> fail ("Too many elements inside <"
                                      ++printableName e++"> at\n"
                                      ++show (info d)++"\n"
                                      ++"Found excess: "
                                      ++verbatim (take 7 ds)
                                      ++"\n[...]")
  where onlyMisc (CMisc _ _) = True
        onlyMisc (CString False s _) | all isSpace s = True
        onlyMisc _ = False
inElement :: String -> XMLParser a -> XMLParser a
inElement tag p = do { e <- element [tag]; commit (interior e p) }
inElementWith :: (String->String->Bool) -> String -> XMLParser a -> XMLParser a
inElementWith match tag p = do { e <- elementWith match [tag]
                               ; commit (interior e p) }
attributes :: XmlAttributes a => Element Posn -> XMLParser a
attributes (Elem _ as _) = return (fromAttrs as)
text :: XMLParser String
text = text' []
  where text' acc =
          do { c <- content "plain text"
             ; case c of
                 CString _ s _        -> text' (s:acc)
                 CRef (RefChar s) _   -> text' (("&#"++show s++";") :acc)
                 CRef (RefEntity s) _ -> text' (('&':s++";"):acc)
                 CMisc _ _            -> text' acc
                 CElem _ _         -> do { reparse [c] 
                                         ; if null acc then fail "empty string"
                                           else return (concat (reverse acc))
                                         }
             }
          `onFail` ( if null acc then fail "empty string"
                     else return (concat (reverse acc)) )
choice :: XmlContent a => (a -> b) -> XMLParser b -> XMLParser b
choice cons (P other) =
    P (\cs-> case runParser parseContents cs of
                 (Left _, _)  -> other cs
                 (Right x, cs') -> Success cs' (cons x) )
definite :: XmlContent a => XMLParser a -> String -> String -> XMLParser a
definite p inner tag = P (\cs-> case runParser p cs of
                                   (Left _, cs')   -> Failure cs' msg'
                                   (Right x, cs')  -> Success cs' x )
  where msg' = "content error: expected "++inner++" inside <"++tag
               ++"> element\n"
class HTypeable a => XmlContent a where
    
    parseContents :: XMLParser a
    
    toContents    :: a -> [Content ()]
    
    
    xToChar       :: a -> Char
    xFromChar     :: Char -> a
    xToChar        = error "HaXml.XmlContent.xToChar used in error"
    xFromChar      = error "HaXml.XmlContent.xFromChar used in error"
class XmlAttributes a where
    fromAttrs :: [Attribute] -> a
    toAttrs   :: a -> [Attribute]
class XmlAttrType a where
    fromAttrToTyp :: String -> Attribute -> Maybe a
    toAttrFrTyp   :: String -> a -> Maybe Attribute
instance (XmlContent a, XmlContent b) => XmlContent (a,b) where
    toContents (a,b) = toContents a ++ toContents b
    parseContents    = do
        { a <- parseContents
        ; b <- parseContents
        ; return (a,b)
        }
instance (XmlContent a, XmlContent b, XmlContent c) => XmlContent (a,b,c) where
    toContents (a,b,c) = toContents a ++ toContents b ++ toContents c
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; return (a,b,c)
        }
instance (XmlContent a, XmlContent b, XmlContent c, XmlContent d) =>
         XmlContent (a,b,c,d) where
    toContents (a,b,c,d) = toContents a ++ toContents b ++ toContents c
                           ++ toContents d
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; return (a,b,c,d)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e ) =>
         XmlContent (a,b,c,d,e) where
    toContents (a,b,c,d,e) = toContents a ++ toContents b ++ toContents c
                             ++ toContents d ++ toContents e
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; return (a,b,c,d,e)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f ) =>
         XmlContent (a,b,c,d,e,f) where
    toContents (a,b,c,d,e,f) = toContents a ++ toContents b ++ toContents c
                               ++ toContents d ++ toContents e ++ toContents f
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; return (a,b,c,d,e,f)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g ) =>
         XmlContent (a,b,c,d,e,f,g) where
    toContents (a,b,c,d,e,f,g)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; return (a,b,c,d,e,f,g)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h ) =>
         XmlContent (a,b,c,d,e,f,g,h) where
    toContents (a,b,c,d,e,f,g,h)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; return (a,b,c,d,e,f,g,h)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i ) =>
         XmlContent (a,b,c,d,e,f,g,h,i) where
    toContents (a,b,c,d,e,f,g,h,i)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; return (a,b,c,d,e,f,g,h,i)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i, XmlContent j ) =>
         XmlContent (a,b,c,d,e,f,g,h,i,j) where
    toContents (a,b,c,d,e,f,g,h,i,j)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i ++ toContents j
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; j <- parseContents
        ; return (a,b,c,d,e,f,g,h,i,j)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i, XmlContent j, XmlContent k ) =>
         XmlContent (a,b,c,d,e,f,g,h,i,j,k) where
    toContents (a,b,c,d,e,f,g,h,i,j,k)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i ++ toContents j ++ toContents k
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; j <- parseContents
        ; k <- parseContents
        ; return (a,b,c,d,e,f,g,h,i,j,k)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i, XmlContent j, XmlContent k, XmlContent l ) =>
         XmlContent (a,b,c,d,e,f,g,h,i,j,k,l) where
    toContents (a,b,c,d,e,f,g,h,i,j,k,l)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i ++ toContents j ++ toContents k ++ toContents l
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; j <- parseContents
        ; k <- parseContents
        ; l <- parseContents
        ; return (a,b,c,d,e,f,g,h,i,j,k,l)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i, XmlContent j, XmlContent k, XmlContent l
         , XmlContent m ) =>
         XmlContent (a,b,c,d,e,f,g,h,i,j,k,l,m) where
    toContents (a,b,c,d,e,f,g,h,i,j,k,l,m)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i ++ toContents j ++ toContents k ++ toContents l
          ++ toContents m
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; j <- parseContents
        ; k <- parseContents
        ; l <- parseContents
        ; m <- parseContents
        ; return (a,b,c,d,e,f,g,h,i,j,k,l,m)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i, XmlContent j, XmlContent k, XmlContent l
         , XmlContent m, XmlContent n ) =>
         XmlContent (a,b,c,d,e,f,g,h,i,j,k,l,m,n) where
    toContents (a,b,c,d,e,f,g,h,i,j,k,l,m,n)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i ++ toContents j ++ toContents k ++ toContents l
          ++ toContents m ++ toContents n
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; j <- parseContents
        ; k <- parseContents
        ; l <- parseContents
        ; m <- parseContents
        ; n <- parseContents
        ; return (a,b,c,d,e,f,g,h,i,j,k,l,m,n)
        }
instance ( XmlContent a, XmlContent b, XmlContent c, XmlContent d
         , XmlContent e, XmlContent f, XmlContent g, XmlContent h
         , XmlContent i, XmlContent j, XmlContent k, XmlContent l
         , XmlContent m, XmlContent n, XmlContent o ) =>
         XmlContent (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o) where
    toContents (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)
        = toContents a ++ toContents b ++ toContents c ++ toContents d
          ++ toContents e ++ toContents f ++ toContents g ++ toContents h
          ++ toContents i ++ toContents j ++ toContents k ++ toContents l
          ++ toContents m ++ toContents n ++ toContents o
    parseContents = do
        { a <- parseContents
        ; b <- parseContents
        ; c <- parseContents
        ; d <- parseContents
        ; e <- parseContents
        ; f <- parseContents
        ; g <- parseContents
        ; h <- parseContents
        ; i <- parseContents
        ; j <- parseContents
        ; k <- parseContents
        ; l <- parseContents
        ; m <- parseContents
        ; n <- parseContents
        ; o <- parseContents
        ; return (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)
        }
data Defaultable a  = Default a    | NonDefault a    deriving (Eq,Show)
searchMaybe :: (a -> Maybe b) -> [a] -> Maybe b
searchMaybe _ [] = Nothing
searchMaybe f (x:xs) =
    let fx = f x in
    case fx of
      Nothing  -> searchMaybe f xs
      (Just _) -> fx
maybeToAttr :: (String->a->Maybe Attribute) -> String -> Maybe a
               -> Maybe Attribute
maybeToAttr _ _ Nothing  = Nothing
maybeToAttr to n (Just v) = to n v
defaultToAttr :: (String->a->Maybe Attribute) -> String -> Defaultable a
                 -> Maybe Attribute
defaultToAttr _ _ (Default _)  = Nothing
defaultToAttr to n (NonDefault v) = to n v
definiteA :: (String->Attribute->Maybe a) -> String -> String
             -> [Attribute] -> a
definiteA from tag at as =
    case searchMaybe (from at) as of
      Nothing  -> error ("missing attribute "++at++" in tag <"++tag++">")
      (Just a) -> a
defaultA :: (String->Attribute->Maybe a) -> a -> String
            -> [Attribute] -> Defaultable a
defaultA from def at as =
    case searchMaybe (from at) as of
      Nothing  -> Default def
      (Just a) -> NonDefault a
possibleA :: (String->Attribute->Maybe a) -> String -> [Attribute] -> Maybe a
possibleA from at as = searchMaybe (from at) as
fromAttrToStr :: String -> Attribute -> Maybe String
fromAttrToStr n (n0,v)
        | n == localName n0   = Just (attr2str v)
        | otherwise           = Nothing
toAttrFrStr   :: String -> String -> Maybe Attribute
toAttrFrStr n v = Just (N n, str2attr v)
str2attr :: String -> AttValue
str2attr s =
    let f t =
          let (l,r) = span (\c-> not (elem c "\"&<>'")) t
          in if null r then [Left l]
             else Left l: Right (g (head r)): f (tail r)
        g '"'  = RefEntity "quot"
        g '&'  = RefEntity "amp"
        g '<'  = RefEntity "lt"
        g '>'  = RefEntity "gt"
        g '\'' = RefEntity "apos"
    in AttValue (f s)
attr2str :: AttValue -> String          
attr2str (AttValue xs) =
    let f (Left s) = s
        f (Right (RefChar i))        = [chr i]
        f (Right (RefEntity "quot")) = "\""
        f (Right (RefEntity "amp"))  = "&"
        f (Right (RefEntity "lt"))   = "<"
        f (Right (RefEntity "gt"))   = ">"
        f (Right (RefEntity "apos")) = "'"
        f (Right _)                  = "*"  
    in concatMap f xs
data ANYContent = forall a . (XmlContent a, Show a) => ANYContent a
                | UnConverted [Content Posn]
instance Show ANYContent where
    show (UnConverted c) = "UnConverted " ++ (show $ map verbatim c)
    show (ANYContent a)  = "ANYContent " ++ (show a)
instance Eq ANYContent where
    a == b = show a == show b
data List1 a = NonEmpty [a]  deriving (Eq, Show)
instance (HTypeable a) => HTypeable (List1 a) where
    toHType m  = Defined "List1" [hx]
                         [Constr "NonEmpty" [hx] [List hx] ]
               where (NonEmpty x) = m
                     hx = toHType x
instance (XmlContent a) => XmlContent (List1 a) where
    toContents (NonEmpty xs) = concatMap toContents xs
    parseContents = fmap NonEmpty $ many1 parseContents
instance HTypeable ANYContent where
    toHType _      = Prim "ANYContent" "ANY"
instance XmlContent ANYContent where
    toContents (ANYContent a)  = toContents a
    toContents (UnConverted s) = map (fmap (const ())) s
    parseContents = P (\cs -> Success [] (UnConverted cs))