/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.utils.stax;

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

import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Lightweight adaptor which translates SAX content events into
 * StAX form, and provides delegation services.
 *
 * @author Thomas Down
 */

public class SAX2StAXAdaptor extends DefaultHandler {
    private List stack;
    private HandlerBinding current;
        
    {
	stack = new ArrayList();
    }

    /**
     * Construct a new SAX Content handler which wraps a StAX
     * handler.
     */

    public SAX2StAXAdaptor(StAXContentHandler rootHandler) {
	current = new HandlerBinding(rootHandler);
	stack.add(current);
    }

    public void startDocument() throws SAXException {
	current.handler.startTree();
    }

    public void endDocument() throws SAXException {
	current.handler.endTree();
    }

    public void characters(char[] ch,
			   int start,
			   int end)
	throws SAXException
    {
	current.handler.characters(ch, start, end);
    }

    public void ignorableWhitespace(char[] ch,
				    int start,
				    int end)
	throws SAXException
    {
	current.handler.ignorableWhitespace(ch, start, end);
    }

    public void startPrefixMapping(String prefix, String uri)
        throws SAXException
    {
	current.handler.startPrefixMapping(prefix, uri);
    }

    public void endPrefixMapping(String prefix)
        throws SAXException
    {
	current.handler.endPrefixMapping(prefix);
    }

    public void processingInstruction(String target, String data)
        throws SAXException
    {
	current.handler.processingInstruction(target, data);
    }

    public void setDocumentLocator(Locator locator) {
	current.handler.setDocumentLocator(locator);
    }

    public void skippedEntity(String name)
        throws SAXException
    {
	current.handler.skippedEntity(name);
    }

    public void startElement(final String nsURI,
			     final String localName,
			     final String qName,
			     final Attributes attrs)
	throws SAXException
    {
	S2SDelegationManager dm = new S2SDelegationManager();
	current.handler.startElement(nsURI,
				     localName,
				     qName,
				     attrs,
				     dm);

	if (dm.getDelegate() != null) {
	    current = new HandlerBinding(dm.getDelegate());
	    stack.add(current);
	    current.handler.startTree();
	    startElement(nsURI, localName, qName, attrs); // Recurse until someone
	                                                  // takes responsibility. 
	} else {
	    current.count++;
	}
    }

    private static class S2SDelegationManager implements DelegationManager {
	private StAXContentHandler delegate;

	public void delegate(StAXContentHandler handler) 
	    throws SAXException
	{
	    if (this.delegate != null) {
		throw new SAXException("Tried to multiply delegate a single StAX element");
	    }

	    this.delegate = handler;
	}

	private StAXContentHandler getDelegate() {
	    return delegate;
	}
    }

    public void endElement(String nsURI, String localName, String qName)
        throws SAXException
    {
	current.handler.endElement(nsURI, localName, qName, null);
	current.count--;
	while (current.count == 0 && stack.size() > 1) {
	    StAXContentHandler oldHandler = current.handler;
	    current.handler.endTree();
	    stack.remove(stack.size() - 1);
	    current = (HandlerBinding) stack.get(stack.size() - 1);
	    current.handler.endElement(nsURI, localName, qName, oldHandler);
	}
    }

    private class HandlerBinding {
	public StAXContentHandler handler;
	public int count;

	private HandlerBinding(StAXContentHandler handler) {
	    this.handler = handler;
	    this.count = 0;
	}
    }
}
