module Text.XML.HaXml.Namespaces
  ( nullNamespace
  , expandedName
  , namespaceName
  , localName
  , printableName
  , qualify
  , deQualify
  , qualifyExceptLocal
  , initNamespaceEnv
  , augmentNamespaceEnv
  , resolveAllNames
  ) where
import Prelude hiding (lookup)
import Text.XML.HaXml.Types
import Data.Map as Map (Map, insert, lookup, empty)
import Data.List (isPrefixOf)
nullNamespace :: Namespace
nullNamespace  = Namespace { nsPrefix="", nsURI="" }
expandedName   :: QName -> (Maybe Namespace, String)
expandedName n  = (namespaceName n, localName n)
namespaceName          :: QName -> Maybe Namespace
namespaceName (N _)     = Nothing
namespaceName (QN ns _) = Just ns
localName          :: QName -> String
localName (N n)     = n
localName (QN _ n)  = n
printableName :: QName -> String
printableName (N n)     = n
printableName (QN ns n) | null (nsPrefix ns) = n
                        | otherwise          = nsPrefix ns++':':n
qualify :: Maybe Namespace -> Map String Namespace -> QName -> QName
qualify def env (N n)
        | ':'`elem`n      = let (pre,':':nm) = span (/=':') n in
                            QN (maybe nullNamespace{nsPrefix=pre} id
                                      (Map.lookup pre env))
                               nm
        | Just d <- def   = QN d n
        | otherwise       = N n
qualify _ env qn@(QN ns n)
        | null (nsURI ns) = QN (maybe ns id (Map.lookup (nsPrefix ns) env)) n
        | otherwise       = qn
deQualify :: Maybe Namespace -> Map String Namespace -> QName -> QName
deQualify _ _ (QN _ n) = N n
deQualify _ _ (N n)    = N n
qualifyExceptLocal :: Maybe Namespace -> Map String Namespace -> QName -> QName
qualifyExceptLocal Nothing    env  qn   = qualify Nothing env qn
qualifyExceptLocal (Just def) env (N n)
        | ':'`elem`n      = let (pre,':':nm) = span (/=':') n in
                            if nsPrefix def == pre then N nm
                            else QN (maybe nullNamespace{nsPrefix=pre} id
                                          (Map.lookup pre env))
                                    nm
        | otherwise       = N n
qualifyExceptLocal (Just def) env qn@(QN ns n)
        | def==ns         = N n
        | null (nsURI ns) = QN (maybe ns id (Map.lookup (nsPrefix ns) env)) n
        | otherwise       = qn
initNamespaceEnv :: Map String Namespace
initNamespaceEnv =
      Map.insert "xmlns" Namespace{nsPrefix="xmlns"
                                  ,nsURI="http://www.w3.org/2000/xmlns/"}
    $ Map.insert "xml"   Namespace{nsPrefix="xml"
                                  ,nsURI="http://www.w3.org/XML/1998/namespace"}
    $ Map.empty
augmentNamespaceEnv :: Namespace -> Map String Namespace
                                 -> Map String Namespace
augmentNamespaceEnv ns env = Map.insert (nsPrefix ns) ns env
resolveAllNames :: (Maybe Namespace -> Map String Namespace -> QName -> QName)
                   -> Document i -> Document i
resolveAllNames qualify (Document prolog entities elm misc) =
    Document (walkProlog prolog) entities
             (walkElem Nothing initNamespaceEnv elm) misc
  where
    qualifyInDTD = qualify Nothing initNamespaceEnv
    walkProlog (Prolog xml misc0 mDTD misc1) =
                Prolog xml misc0 (maybe Nothing (Just . walkDTD) mDTD) misc1
    walkDTD (DTD qn ext mds)     = DTD (qualifyInDTD qn) ext (map walkMD mds)
    
    walkMD (Element ed)          = Element (walkED ed)
    walkMD (AttList ald)         = AttList (walkALD ald)
    walkMD md                    = md
    
    walkED (ElementDecl qn cs)   = ElementDecl (qualifyInDTD qn) (walkCS cs)
    
    walkCS (ContentSpec cp)      = ContentSpec (walkCP cp)
    walkCS (Mixed m)             = Mixed (walkM m)
    walkCS cs                    = cs
    
    walkCP (TagName qn m)        = TagName (qualifyInDTD qn) m
    walkCP cp                    = cp
    
    walkM (PCDATAplus qns)       = PCDATAplus (map qualifyInDTD qns)
    walkM PCDATA                 = PCDATA
    
    walkALD (AttListDecl qn ads) = AttListDecl (qualifyInDTD qn)
                                               (map walkAD ads)
    
    walkAD (AttDef qn at dd)     = AttDef (qualifyInDTD qn) at dd
    
    walkElem def env (Elem qn attrs conts) =
                      Elem (qualify def' env' qn)
                           (map (\ (a,v)-> (qualify Nothing env' a, v)) attrs)
                           (map (walkContent def' env') conts)
        where def' = foldr const def  
                           (map defNamespace (matching (=="xmlns") attrs))
              env' = foldr augmentNamespaceEnv env
                           (map mkNamespace
                                (matching ("xmlns:"`isPrefixOf`) attrs))
              defNamespace :: Attribute -> Maybe Namespace
              defNamespace (_ , atv)
                      | null (show atv) = Nothing
                      | otherwise       = Just nullNamespace{nsURI=show atv}
              mkNamespace :: Attribute -> Namespace
              mkNamespace (N n, atv)  = let (_,':':nm) = span (/=':') n in 
                                        Namespace{nsPrefix=nm,nsURI=show atv}
              matching :: (String->Bool) -> [Attribute] -> [Attribute]
              matching p = filter (p . printableName . fst)
    
    walkContent def env (CElem e i) = CElem (walkElem def env e) i
    walkContent _   _   content     = content