/***
 *** Title: NameSpace Utility
 *** Author: Itsuki Noda
 *** Date: 2006.08.18
 ***/

package daruma.xml;

import daruma.xml.util.DOMDocumentFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.parsers.ParserConfigurationException;
import java.util.HashMap ;

import daruma.util.Itk;

/**
 * namespace と default prefix を統一的に扱うためのクラス。
 *
 * あらかじめ宣言されるネームスペースは、できるだけこれを利用する。
 */

public class NameSpace
{
    //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    /* static constants
     */

    /* !!! [06/08/18 18:03 I.Noda] !!! 
     * FIXME: この定数は、どこで定義されるべきか？
     */
    final public static String PrefixSeparator = ":" ;
    final public static String UNameUriPrefix = "{" ;
    final public static String UNameUriSuffix = "}" ;
    

    //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    /* instance variables
     */

    public String uri;			// URI for the namespace
    public String defaultPrefix;	// default prefix for the namespace


    //============================================================
    /**
     * Table for NameSpace
     */
    static public class Table {
	public HashMap<String, NameSpace> uriTable ;
	public HashMap<String, NameSpace> prefixTable ;

	//----------------------------------------
	/**
	 * constructor
	 */
	public Table() {
	    this.uriTable = new HashMap<String, NameSpace>() ;
	    this.prefixTable = new HashMap<String, NameSpace>() ;
	}

	//--------------------------------------------------
	/**
	 * put a namespace to the table
	 */
	public NameSpace put(NameSpace ns) {
	    this.prefixTable.put(ns.defaultPrefix, ns) ;
	    this.uriTable.put(ns.uri, ns) ;
	    return ns ;
	}

	//--------------------------------------------------
	/* get a namespace by prefix
	 */
	public NameSpace getByPrefix(String prefix) {
	    return this.prefixTable.get(prefix) ;
	}

	//--------------------------------------------------
	/* get a namespace by uri
	 */
	public NameSpace getByUri(String uri) {
	    return this.uriTable.get(uri) ;
	}

    }

    static public final Table DefaultTable = new Table() ;

    //--------------------------------------------------
    /**
     * デフォルトの NameSpace 定義を保持する Element。
     * XPath などで利用。
     */
    static public Element DefaultContext = null ;

    static private Element prepareDefaultContext() 
    {
	if(DefaultContext == null) {
	    try {
		Document doc = DOMDocumentFactory.create() ;
		DefaultContext = doc.createElement("DefaultContxt") ;
	    } 
	    catch (ParserConfigurationException ex) {
		ex.printStackTrace() ;
		System.exit(1) ;
	    }
	}
	return DefaultContext ;
    }

    //------------------------------------------------------------
    /**
     * Constructors
     */
    public NameSpace(String uri) {
	set(uri) ;
    }

    public NameSpace(String prefix, String uri) {
	set(prefix,uri) ;
    }

    public NameSpace(String prefix, String uri, Table table) {
	set(prefix,uri,table) ;
    }

    public NameSpace(String prefix, String uri, boolean registerP) {
	set(prefix,uri,registerP) ;
    }

    //------------------------------------------------------------
    /**
     * set
     */
    public NameSpace set(String uri) {
	return set(null,uri,false) ;
    }

    public NameSpace set(String prefix, String uri) {
	return set(prefix, uri, false) ;
    }

    public NameSpace set(String prefix, String uri, boolean registerP) {
	return set(prefix, uri, 
		   (registerP ? NameSpace.DefaultTable : null)) ;
    }
    public NameSpace set(String prefix, String uri, Table nsTable) {
	this.defaultPrefix = prefix ;
	this.uri = uri ;
	if(nsTable != null) {
	    nsTable.put(this) ;
	}
	return this ;
    }

    //--------------------------------------------------
    /* generate QName (qualified name) String
     */
    public static String genQName(String prefix, String localname) {
	if(prefix == null || prefix.length() == 0) {
	    return localname ;
	} else {
	    return prefix + PrefixSeparator + localname ;
	}
    }

    //------------------------------
    public static String genQName(NameSpace ns, String localname) {
	if(ns == null) {
	    return genQName((String)null, localname) ;
	} else {
	    return genQName(ns.defaultPrefix, localname) ;
	}
    }

    //------------------------------
    public String genQNameFor(String localname) {
	return NameSpace.genQName(this, localname) ;
    }

    //--------------------------------------------------
    /* generate UName (universal name) String
     */
    public static String genUName(String nsUri, String localname) {
	if(nsUri == null || nsUri.length() == 0) {
	    return localname ;
	} else {
	    return UNameUriPrefix + nsUri + UNameUriSuffix + localname ;
	}
    }

    //------------------------------
    public static String genUName(NameSpace ns, String localname) {
	if(ns == null) {
	    return genUName((String)null, localname) ;
	} else {
	    return genUName(ns.uri, localname) ;
	}
    }

    //------------------------------
    public String genUNameFor(String localname) {
	return NameSpace.genUName(this, localname) ;
    }

    //--------------------------------------------------
    /* generate attribute name string for xmlns def.
     */
    public static String genXmlNsAttrName(String prefix) {
	if(prefix == null || prefix.length() == 0) {
	    return NameSpace.XMLNS.defaultPrefix ;
	} else {
	    return genQName(NameSpace.XMLNS.defaultPrefix, prefix) ;
	}
    }

    //------------------------------
    public String genXmlNsAttrName() {
	return this.genXmlNsAttrName(false) ; 
    }

    //------------------------------
    public String genXmlNsAttrName(boolean defaultp) {
	if(defaultp) {
	    return NameSpace.genXmlNsAttrName(null) ;
	} else {
	    return NameSpace.genXmlNsAttrName(this.defaultPrefix) ;
	}
    }

    //--------------------------------------------------
    /* add namespace decls to element if need
     */
    public boolean addNsDeclToIfNeeded(Element element) 
    {
	return NameSpace.addNsDeclToIfNeeded(element, 
					     this.defaultPrefix, this.uri) ;
    }

    public static boolean addNsDeclToIfNeeded(Element element,
					      String prefix,
					      String uri) 
    {
	String predefined = NameSpace.findPredefinedPrefixIn(element, uri) ;
	if(predefined == null || !predefined.equals(prefix)) {
	    element.setAttributeNS(XMLNS.uri,
				   NameSpace.genXmlNsAttrName(prefix),
				   uri) ;
	    return true ;
	} else {
	    return false ;
	}
    }

    //--------------------------------------------------
    /* find prefix defined in parent nodes
     */
    public String findPredefinedPrefixIn(Node element) 
    {
	return NameSpace.findPredefinedPrefixIn(element, this) ;
    }

    public static String findPredefinedPrefixIn(Node element, NameSpace ns) 
    {
	if(ns != null) {
	    return ns.findPredefinedPrefixIn(element) ;
	} else {
	    return null ;
	}
    }

    public static String  findPredefinedPrefixIn(Node element, String uri) 
    {
	if(element == null || uri == null || element instanceof Document) {
	    return null ;
	} else if(element.isDefaultNamespace(uri)) {
	    return "" ;
	} else {
	    return element.lookupPrefix(uri) ;
	}
    }

    //--------------------------------------------------
    /* find namespaceURI defined in parent nodes
     */
    public String findPredefinedNamespaceUriIn(Node element, String prefix) 
    {
	return element.lookupNamespaceURI(prefix) ;
    }

    //--------------------------------------------------
    /* check the namespace of the node is the same one
     */
    public boolean includes(Node node) {
	String uri = node.getNamespaceURI() ;
	if(uri == null) {
	    return false ;
	} else {
	    return uri.equals(this.uri) ;
	}
    }

    //--------------------------------------------------
    /* check a given namespace is the same one
     */
    public boolean equals(String nsUri) {
	return this.uri.equals(nsUri) ;
    }

    public boolean equals(NameSpace ns) {
	return equals(ns.uri) ;
    }

    //--------------------------------------------------
    /* hash code
     */
    public int hashCode() {
	return this.uri.hashCode() ;
    }

    //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    /* static constants
     */
    /* !!! [06/08/18 18:03 I.Noda] !!! 
     * できれば他で定義したいが、XMLNS をメソッド内で使うので、
     * こちらに宣言しておく。
     * アイデアとしては、特別扱いのXMLNS だけここに宣言。
     * でも、あまり宣言をばらしたくないなぁ。
     */

    static public final NameSpace SOAP_ENV = 
	new NameSpace("SOAP-ENV",
		      "http://schemas.xmlsoap.org/soap/envelope/", 
		      true) ; 
    static public final NameSpace XMLNS = 
	new NameSpace("xmlns",
		      "http://www.w3.org/2000/xmlns/", 
		      true) ; 
    static public final NameSpace XML_SCHEMA = 
	new NameSpace("xsd",
		      "http://www.w3.org/2001/XMLSchema", 
		      true) ;
    static public final NameSpace GML = 
	new NameSpace("gml",
		      "http://www.opengis.net/gml", 
		      true) ;
    static public final NameSpace WFS = 
	new NameSpace("wfs",
		      "http://www.opengis.net/wfs", 
		      true) ;
    static public final NameSpace MISP = 
	new NameSpace("misp",
		      "http://www.infosharp.org/misp", 
		      true) ;

    /* !!! [06/08/18 18:03 I.Noda] !!! 
     * URI.java から移設。必要かどうか、議論の余地あり。
     */
    static public final NameSpace MISP_ERROR = 
	new NameSpace("misp-error",
		      "http://www.infosharp.org/misp/error", 
		      true) ;

    static {
	prepareDefaultContext() ;
	for(NameSpace ns : DefaultTable.prefixTable.values()) {
	    ns.addNsDeclToIfNeeded(DefaultContext) ;
	}
    }

}
