package daruma.xml.util;

import daruma.xml.util.PrefixMap;
import daruma.xml.util.XMLParseErrorException;
import daruma.xml.UniversalName;
import daruma.xml.URI;
import daruma.xml.NameSpace;
import daruma.xml.DeclaredName;

import daruma.xml.util.XMLFormatConverter;

import daruma.util.Pair;
import daruma.util.LogWriter;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;


import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.IOException;

import javax.xml.transform.TransformerException;

public class ElementUtil
{
	private	ElementUtil()
	{
	}

	public	static	boolean	hasExtraChildElements( Element  element ,
						       int  beginChildIndex )
	{
		NodeList	childNodes = element.getChildNodes();

		for ( int  i = beginChildIndex  ;
		      i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item( i );

			if ( n instanceof Element )
			{
				return( true );
			}
		}

		return( false );
	}

	public	static	List<Element>	getExtraChildElements
					( Element  element ,
					  int  beginChildIndex )
	{
		List<Element>	ret = new ArrayList<Element>();

		NodeList	childNodes = element.getChildNodes();
		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item( i );

			if ( n instanceof Element )
			{
				ret.add( (Element)n );
			}
		}

		return( ret );
	}

	public	static	String	getChildNodesWholeText( Element  element )
	{
		/*
		NodeList	childNodes = element.getChildNodes();

		StringBuilder	buf = new StringBuilder();

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item(i);

			if ( n instanceof Text )
			{
				buf.append( ((Text)n).getWholeText() );
			}
		}

		return( buf.toString() );
		*/

		return( element.getTextContent() );
	}

	public	static	String	getStringOnlyChildNodesWholeText( Element  element )
						  throws XMLParseErrorException
	{
		if ( getChildElements( element ).size() != 0 )
		{
			throw new XMLParseErrorException
			    ( "element [" + new UniversalName( element )
			      + "] should not contain any tags." );
		}

		return( getChildNodesWholeText( element ) );
	}

	public	static	List<Element>	getChildElements( Element  element )
	{
		List<Element>	ret = new ArrayList<Element>();

		NodeList	childNodes = element.getChildNodes();

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item( i );

			if ( n instanceof Element )
			{
				ret.add( (Element)n );
			}
		}

		return( ret );
	}

	public	static	Element	getSingleChildElement( Element  element )
						  throws XMLParseErrorException
	{
		NodeList	childNodes = element.getChildNodes();

		Element		ret = null;
		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item( i );

			if ( n instanceof Element )
			{
				if ( ret != null )
				{
					throw new XMLParseErrorException
						( "too many child elements"
						  + " of element "
						  + element.getLocalName()
						  + " in namespace "
						  + element.getNamespaceURI()
						  + " found" );
				}

				ret = (Element)n;
			}

		}

		if ( ret == null )
		{
			throw new XMLParseErrorException
				( "empty child element of element \""
				  + element.getLocalName()
				  + "\" in namespace \""
				  + element.getNamespaceURI()
				  + "\"" );
		}

		return( ret );
	}

	public static Element getNameReplacedElement
				( Element originalElement,
				  UniversalName nameToReplace,
				  Document doc )
	{
	    Element replaced = doc.createElementNS
				( nameToReplace.getNamespace(),
				  nameToReplace.getLocalName() );

	    NodeList childNodes = originalElement.getChildNodes();
	    for ( int i = 0 ; i < childNodes.getLength() ; ++ i )
	    {
		replaced.appendChild( childNodes.item(i).cloneNode( true ) );
	    }

	    return replaced;
	}

	/*
	public	static	void	debugPrint( Element  element ,
					    OutputStream  out )
	{
		NodeList	childNodes = element.getChildNodes();

		PrintWriter	w = new PrintWriter( out );

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item( i );

			w.print( i + ": " );

			if ( n instanceof Element )
			{
				Element	e = (Element)n;

				w.println( "Element {" + e.getNamespaceURI()
					   + "}" + e.getLocalName() );
			}
			else if ( n instanceof Text )
			{
				w.println( "Text [" + ((Text)n).getWholeText()
					   + "]" );
			}
			else
			{
				w.println( "???" );
			}
		}

		w.flush();

		try
		{
			out.flush();
		}
		catch( IOException  e )
		{
			e.printStackTrace();
		}
	}
	*/

	public	static	void	debugPrint( Element  element )
	{
		NodeList	childNodes = element.getChildNodes();

		for ( int  i = 0  ;  i < childNodes.getLength()  ;  ++ i )
		{
			Node	n = childNodes.item( i );

			if ( n instanceof Element )
			{
				Element	e = (Element)n;

				LogWriter.qwrite
				    ( "DEBUG",
				      i, ": ",
				      "Element {", e.getNamespaceURI(), "}",
				      e.getLocalName() );
			}
			else if ( n instanceof Text )
			{
				LogWriter.qwrite
				    ( "DEBUG",
				      i, ": ",
				      "Text [", ((Text)n).getWholeText(),
				      "]" );
			}
			else
			{
				LogWriter.qwrite( "DEBUG", i, ": ", "???" );
			}
		}
	}

	public	static	void	addPrefixMappingToElement
						( Element  element ,
						  PrefixMap  prefixMap )
	{
		for( Map.Entry<String, String>  e : prefixMap.entrySet() )
		{
		    /* <<< [06/08/18 18:03 I.Noda] <<< 
		     * NameSpace のメソッドを利用するように変更
		     */
		    //	String	qName;
		    //
		    //	if ( e.getKey().length() == 0 )
		    //	{
		    //		qName = "xmlns";
		    //	}
		    //	else
		    //	{
		    //		qName = "xmlns:" + e.getKey();
		    //	}

		    //	element.setAttributeNS( URI.XMLNS ,
		    //				qName ,
		    //				e.getValue() );
		    /* === [06/08/18 18:03 I.Noda] === */
		    NameSpace.addNsDeclToIfNeeded(element,
						  e.getKey(),
						  e.getValue()) ;
		    /* >>> [06/08/18 18:03 I.Noda] >>> */
		}
	}

	public	static	void	addPrefixMappingToPrefixMap
						( PrefixMap  prefixMap ,
						  Node  node )
	{
		NamedNodeMap	attributes = node.getAttributes();

		if ( attributes == null )
		{
			return;
		}

		for ( int  i = 0  ;  i < attributes.getLength()  ;  i ++ )
		{
			Attr	attr = (Attr)attributes.item(i);
			String	pref = attr.getPrefix();

			if ( pref != null
			  && pref.equals( "xmlns" ) )
			{
				prefixMap.put( attr.getLocalName() ,
					       attr.getValue() );
			}
			else if ( pref == null
			       && attr.getLocalName().equals( "xmlns" ) )
			{
				prefixMap.put( "" , attr.getValue() );
			}
		}


		String	namespaceURI = node.getNamespaceURI();

		if ( namespaceURI != null )
		{
			prefixMap.put( node.getPrefix() , namespaceURI );
		}
	}

    //------------------------------------------------------------
    /* generate simple Element with text
     * DeclaredName.java から移動
     */

    /* !!! [06/08/18 23:19 I.Noda] !!! */
    //------------------------------
    public static Element genElementSimple(DeclaredName dname,
    					   String text,
    					   Document document,
    					   Node parent)
    {
	return genElementSimple(dname, text, document, parent, false) ;
    }
					   
    //------------------------------
    public static Element genElementSimple(DeclaredName dname,
    					   String text,
    					   Document document,
    					   Node parent,
					   boolean useParentPrefix)
    {
	return genElementSimple(dname.ns, dname.localname, 
				text, document, parent, useParentPrefix) ;
    }

    //------------------------------
    public static Element genElementSimple(NameSpace ns,
    					   String localName,
    					   String text,
    					   Document document,
    					   Node parent) 
    {
    	return genElementSimple(ns, localName, text,
    				document, parent, false) ;
    }

    public static Element genElementSimple(NameSpace ns,
    					   String localName,
    					   String text,
    					   Document document,
    					   Node parent,
					   boolean useParentPrefix) 
    {
    	return genElementSimple(ns.defaultPrefix, ns.uri, localName, text,
    				document, parent, useParentPrefix) ;
    }

    //------------------------------
    public static Element genElementSimple(String prefix,
    					   String nsUri,
    					   String localName,
    					   String text,
    					   Document document,
    					   Node parent) 
    {
    	return genElementSimple(prefix, nsUri, localName, text,
    				document, parent, false) ;
    }
    
    //------------------------------
    public static Element genElementSimple(String prefix,
					   String nsUri,
					   String localName,
					   String text,
					   Document document,
					   Node parent,
					   boolean useParentPrefix)
    {
	/* !!! [06/07/13 21:34 I.Noda] !!! */
	/* もし先祖要素で prefix が定義されていれば, 
	 * それを使う
	 */
	if(useParentPrefix) {
	    String predefined = 
		NameSpace.findPredefinedPrefixIn(parent, nsUri) ;
	    if(predefined != null) {
		prefix = predefined ;
	    }
	}

	String qname = localName ;
	if(prefix != null && prefix.length() > 0) {
	    qname = NameSpace.genQName(prefix, localName) ;
	}

	Element element = 
	    document.createElementNS(nsUri, qname) ;

	// parent があれば、それに追加。
	if(parent != null) {
	    parent.appendChild(element) ;
	}
	
	// text があれば、それを追加。
	if(text != null && text.length() > 0) {
	    element.appendChild(document.createTextNode(text)) ;
	}

	return element ;
    }

    //------------------------------------------------------------
    /* append text node to the element
     */
    public static Text addTextNode(Node parent, String text, Document doc)
    {
	Text textNode = doc.createTextNode(text) ;
	parent.appendChild(textNode) ;
	return textNode ;
    }

    //------------------------------------------------------------
    /* split header and tailer at the current node.
     */
    public static Pair<String, String> 
	splitToHeaderTailerStrAt(Node root, Node leaf, boolean withDocDeclP)
	throws TransformerException
    {
	return splitToHeaderTailerStrAt(root, leaf, null, withDocDeclP) ;
    } ; 
    
    static private String separatorStr = "___never_occur_string_I_hope___" ;

    public static Pair<String, String> 
	splitToHeaderTailerStrAt(Node root, Node leaf, Node at, 
				  boolean withDocDeclP)
	throws TransformerException
    {
	Document doc = ((root instanceof Document) ? 
			(Document)root : root.getOwnerDocument() ) ;

	Text separator = doc.createTextNode(separatorStr) ;

	leaf.insertBefore(separator,at) ;
	
	Pair<String, String> headerTailer = 
	    XMLFormatConverter.splitToHeaderTailerStrBy(root, separatorStr,
							withDocDeclP) ;

	leaf.removeChild(separator) ;

	return headerTailer ;
    }
    
    //------------------------------------------------------------
    /* element より attribute を取得
     */
    public static String getAttributeValue(Element element,
					   DeclaredName dname,
					   String defaultValue)
    {
	String attrValue = null ;

	String nsURI = ( (dname.ns == null) ? null : dname.ns.uri) ;

	if(element.hasAttributeNS(nsURI, dname.localname)) {
	    attrValue = element.getAttributeNS(nsURI, 
					       dname.localname) ;
	} else {
	    attrValue = defaultValue ;
	}
	    
	return attrValue ;
    }
	
}
