/******************************************************************************
 * Copyright (c) 2010 Oracle
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Konstantin Komissarchik - initial implementation and ongoing maintenance
 ******************************************************************************/

package org.eclipse.wst.common.project.facet.core.util.internal;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
 */

public final class DomUtil 
{
    public static Document doc( final Reader r )
    {
        final DocumentBuilder docbuilder = docbuilder();
        
        try
        {
            return docbuilder.parse( new InputSource( r ) );
        }
        catch( IOException e )
        {
            throw new XmlParseException( e );
        }
        catch( SAXParseException e )
        {
            throw new XmlParseException( e );
        }
        catch( SAXException e )
        {
            throw new XmlParseException( e );
        }
    }

    public static Document doc( final InputStream stream )
    {
        final DocumentBuilder docbuilder = docbuilder();
        
        try
        {
            return docbuilder.parse( stream );
        }
        catch( IOException e )
        {
            throw new XmlParseException( e );
        }
        catch( SAXParseException e )
        {
            throw new XmlParseException( e );
        }
        catch( SAXException e )
        {
            throw new XmlParseException( e );
        }
    }
    
    public static Document doc( final String str )
    {
        return doc( new StringReader( str ) );
    }
    
    public static Document doc( final File f )
    {
        InputStream stream = null;

        try
        {
            stream = new BufferedInputStream( new FileInputStream( f ) );
            return doc( stream );
        }
        catch( IOException e )
        {
            throw new XmlParseException( e );
        }
        finally
        {
            if( stream != null )
            {
                try
                {
                    stream.close();
                }
                catch( IOException e ) {}
            }
        }
    }
    
    public static Document doc()
    {
        return docbuilder().newDocument();
    }
    
    public static Element root( final Reader r )
    {
        return doc( r ).getDocumentElement();
    }
    
    public static Element root( final String str )
    {
        return doc( str ).getDocumentElement();
    }
    
    public static Element root( final File f )
    {
        return doc( f ).getDocumentElement();
    }

    public static Element element( final Element el,
                                   final String name )
    {
        final NodeList nodes = el.getChildNodes();
        
        for( int i = 0, n = nodes.getLength(); i < n; i++ )
        {
            final Node node = nodes.item( i );
            
            if( node.getNodeType() == Node.ELEMENT_NODE &&
                basename( node.getNodeName() ).equals( name ) )
            {
                return (Element) node;
            }
        }
        
        return null;
    }
    
    public static Iterable<Element> elements( final Element el )
    {
        return new ElementsIterator( el.getChildNodes() );
    }
    
    public static Iterable<Element> elements( final Element el,
                                              final String name )
    {
        return new ElementsIterator( el.getChildNodes(), name );
    }
    
    public static Iterable<Element> elements( final NodeList nodes )
    {
        return new ElementsIterator( nodes );
    }

    public static Iterable<Element> elements( final NodeList nodes,
                                              final String name )
    {
        return new ElementsIterator( nodes, name );
    }
    
    public static final class ElementsIterator
    
        implements Iterator<Element>, Iterable<Element>
        
    {
        private final NodeList nodes;
        private final int length;
        private final String name;
        private int position;
        private Element element;
        
        public ElementsIterator( final NodeList nodes )
        {
            this( nodes, null );
        }
        
        public ElementsIterator( final NodeList nodes,
                                 final String name )
        {
            this.nodes = nodes;
            this.length = nodes.getLength();
            this.position = -1;
            this.name = name;
            
            advance();
        }
        
        private void advance()
        {
            this.element = null;
            this.position++;
            
            for( ; this.position < this.length && this.element == null; 
                 this.position++ )
            {
                final Node node = this.nodes.item( this.position );
                
                if( node.getNodeType() == Node.ELEMENT_NODE &&
                    ( this.name == null || 
                      basename( node.getNodeName() ).equals( this.name ) ) )
                {
                    this.element = (Element) node;
                }
            }
        }

        public boolean hasNext() 
        {
            return ( this.element != null );
        }

        public Element next() 
        {
            final Element el = this.element;

            if( el == null ) 
            {
                throw new NoSuchElementException();
            }
            
            advance();
            
            return el;
        }

        public void remove() 
        {
            throw new UnsupportedOperationException();
        }

        public Iterator<Element> iterator() 
        {
            return this;
        }
    }
    
    public static String text( final Element el )
    {
        final NodeList nodes = el.getChildNodes();

        String str = null;
        StringBuilder buf = null;
        
        for( int i = 0, n = nodes.getLength(); i < n; i++ )
        {
            final Node node = nodes.item( i );
            final short nodeType = node.getNodeType();
            
            if( nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE )
            {
                final String val = node.getNodeValue();
                
                if( buf != null )
                {
                    buf.append( val );
                }
                else if( str != null )
                {
                    buf = new StringBuilder();
                    buf.append( str );
                    buf.append( val );
                    
                    str = null;
                }
                else
                {
                    str = val;
                }
            }
        }
        
        if( buf != null )
        {
            return buf.toString();
        }
        else
        {
            return str;
        }
    }
    
    public static String text( final Element el,
                               final String childElementName )
    {
        final Element childElement = element( el, childElementName );
        
        if( childElement == null )
        {
            return null;
        }
        else
        {
            return text( childElement );
        }
    }
    
    public static String basename( final String name )
    {
        final int colon = name.indexOf( ':' );
        
        if( colon != -1 )
        {
            return name.substring( colon + 1 );
        }
        else
        {
            return name;
        }
    }
    
    public static void write( final Document doc,
                              final File file )
    {
        try
        {
            final DOMSource source = new DOMSource( doc );
            final StreamResult result = new StreamResult( file );
            
            final TransformerFactory factory = TransformerFactory.newInstance();
            final Transformer transformer = factory.newTransformer();
            
            transformer.transform( source, result );
        }
        catch( TransformerConfigurationException e )
        {
            throw new RuntimeException( e );
        }
        catch( TransformerException e )
        {
            throw new RuntimeException( e );
        }
    }
    
    private static DocumentBuilder docbuilder()
    {
        try
        {
            final DocumentBuilderFactory factory 
                = DocumentBuilderFactory.newInstance();
            
            factory.setValidating( false );
            factory.setNamespaceAware( true );
            factory.setIgnoringComments( false );
            
            final DocumentBuilder builder = factory.newDocumentBuilder();
            
            builder.setEntityResolver
            (
                new EntityResolver()
                {
                    public InputSource resolveEntity( final String publicID, 
                                                      final String systemID )
                    {
                        return new InputSource( new StringReader( "" ) ); //$NON-NLS-1$
                    }
                }
            );
            
            return builder;
        }
        catch( ParserConfigurationException e )
        {
            throw new RuntimeException( e );
        }
    }
    
}
