package com.icl.saxon.tree;
import com.icl.saxon.om.NodeInfo;
import com.icl.saxon.om.DocumentInfo;
import com.icl.saxon.om.NamePool;
import com.icl.saxon.Controller;
import com.icl.saxon.Context;
import com.icl.saxon.KeyManager;
import com.icl.saxon.om.AxisEnumeration;
import com.icl.saxon.expr.NodeSetExtent;
import com.icl.saxon.output.Outputter;
import com.icl.saxon.pattern.Pattern;
import com.icl.saxon.sort.LocalOrderComparer;

import java.util.*;
import java.net.*;
import org.xml.sax.Attributes;
import javax.xml.transform.TransformerException;

import org.w3c.dom.*;

/**
  * A node in the XML parse tree representing the Document itself (or equivalently, the root
  * node of the Document).<P>
  * @author <A HREF="mailto:mhkay@iclway.co.uk>Michael H. Kay</A> 
  */

public final class DocumentImpl extends ParentNodeImpl
    implements DocumentInfo, Document {

    //private static int nextDocumentNumber = 0;

    private ElementImpl documentElement;

    private Hashtable idTable = null;
    //private int documentNumber;
    private Hashtable entityTable = null;
    private Hashtable elementList = null;
    private StringBuffer characterBuffer;
    private NamePool namePool;
    private NodeFactory nodeFactory;
    private LineNumberMap lineNumberMap;
    private SystemIdMap systemIdMap = new SystemIdMap();

    // list of indexes for keys. Each entry is a triple: KeyManager, fingerprint of Key name, Hashtable.
    // This reflects the fact that the same document may contain indexes for more than one stylesheet.

    private Object[] index = new Object[30];
    private int indexEntriesUsed = 0;

    public DocumentImpl() {
        parent = null;
    }

    /**
    * Set the character buffer
    */

    protected void setCharacterBuffer(StringBuffer buffer) {
        characterBuffer = buffer;
    }

    /**
    * Get the character buffer
    */

    public final StringBuffer getCharacterBuffer() {
        return characterBuffer;
    }

	/**
	* Set the name pool used for all names in this document
	*/
	
	public void setNamePool(NamePool pool) {
		namePool = pool;
	}
	
	/**
	* Get the name pool used for the names in this document
	*/
	
	public NamePool getNamePool() {
		return namePool;
	}

	/**
	* Set the node factory that was used to build this document
	*/
	
	public void setNodeFactory(NodeFactory factory) {
		nodeFactory = factory;
	}
	
	/**
	* Get the node factory that was used to build this document
	*/
	
	public NodeFactory getNodeFactory() {
		return nodeFactory;
	}

    /**
    * Set the top-level element of the document (variously called the root element or the
    * document element). Note that a DocumentImpl may represent the root of a result tree
    * fragment, in which case there is no document element.
    * @param e the top-level element
    */

    protected void setDocumentElement(ElementImpl e) {
        documentElement = e;
    }

    /**
    * Set the system id of this node
    */

    public void setSystemId(String uri) {
        //if (uri==null) {
        //    throw new IllegalArgumentException("System ID must not be null");       
        //}
        if (uri==null) {
            uri = "";
        }
        systemIdMap.setSystemId(sequence, uri);
    }
        
    /**
    * Get the system id of this root node
    */

    public String getSystemId() {
        return systemIdMap.getSystemId(sequence);
    }

    /**
    * Get the base URI of this root node. For a root node the base URI is the same as the
    * System ID.
    */

    public String getBaseURI() {
        return getSystemId();
    }

    /**
    * Set the system id of an element in the document
    */

    protected void setSystemId(int seq, String uri) {
        if (uri==null) {
            uri = "";
        //    uri = "*unknown.uri*";
        //    throw new NullPointerException("URI may not be null");   
        }    
        systemIdMap.setSystemId(seq, uri);
    }
        

    /**
    * Get the system id of an element in the document
    */

    protected String getSystemId(int seq) {
        return systemIdMap.getSystemId(seq);
    }


    /**
    * Set line numbering on
    */

    public void setLineNumbering() {
        lineNumberMap = new LineNumberMap();
        lineNumberMap.setLineNumber(sequence, 0);
    }

    /**
    * Set the line number for an element. Ignored if line numbering is off.
    */

    protected void setLineNumber(int sequence, int line) {
        if (lineNumberMap != null) {
            lineNumberMap.setLineNumber(sequence, line);
        }
    }

    /**
    * Get the line number for an element. Return -1 if line numbering is off.
    */

    protected int getLineNumber(int sequence) {
        if (lineNumberMap != null) {
            return lineNumberMap.getLineNumber(sequence);
        }
        return -1;
    }

    /**
    * Get the line number of this root node.
    * @return 0 always
    */

    public int getLineNumber() {
        return 0;
    }

    /**
    * Return the type of node.
    * @return NodeInfo.ROOT (always)
    */

    public final short getNodeType() {
        return ROOT;
    }

    /**
    * Get next sibling - always null
    * @return null
    */

    public final Node getNextSibling() {
        return null;
    }

    /**
    * Get previous sibling - always null
    * @return null
    */

    public final Node getPreviousSibling()  {
        return null;
    }

    /**
     * Get the root (outermost) element.
     * @return the Element node for the outermost element of the document.
     */
     
    public Element getDocumentElement() {
        return (ElementImpl)documentElement;
    }
    
    /**
    * Get the root (document) node
    * @return the DocumentInfo representing this document
    */

    public DocumentInfo getDocumentRoot() {
        return this;
    }

    /**
    * Get a character string that uniquely identifies this node within the document
    * @return the empty string
    */

    public String generateId() {
        return "";
    }

    /**
    * Get a list of all elements with a given name fingerprint
    */

    protected AxisEnumeration getAllElements(int fingerprint) {
        Integer elkey = new Integer(fingerprint);
        if (elementList==null) {
            elementList = new Hashtable();
        }
        NodeSetExtent list = (NodeSetExtent)elementList.get(elkey);
        if (list==null) {
            list = new NodeSetExtent(LocalOrderComparer.getInstance());
            list.setSorted(true);
            NodeImpl next = getNextInDocument(this);
            while (next!=null) {
                if (next.getNodeType()==ELEMENT &&
                        next.getFingerprint() == fingerprint) {
                    list.append(next);
                }
                next = next.getNextInDocument(this);
            }
            elementList.put(elkey, list);
        }
        return (AxisEnumeration)list.enumerate();
    }

    /**
    * Index all the ID attributes. This is done the first time the id() function
    * is used on this document
    */

    private void indexIDs() {
        if (idTable!=null) return;      // ID's are already indexed
        idTable = new Hashtable();

        NodeImpl curr = this;
        NodeImpl root = curr;
        while(curr!=null) {
            if (curr.getNodeType()==ELEMENT) {
                ElementImpl e = (ElementImpl)curr;
                Attributes atts = e.getAttributeList();
                for (int i=0; i<atts.getLength(); i++) {
                    if ("ID".equals(atts.getType(i))) {
                        registerID(e, atts.getValue(i));
                    }
                }
            }
            curr = curr.getNextInDocument(root);
        }
    }

    /**
    * Register a unique element ID. Fails if there is already an element with that ID.
    * @param e The Element having a particular unique ID value
    * @param id The unique ID value
    */

    private void registerID(NodeInfo e, String id) {
        // the XPath spec (5.2.1) says ignore the second ID if it's not unique
        Object old = idTable.get(id);
        if (old==null) {
            idTable.put(id, e);
        }
        
    }

    /**
    * Get the element with a given ID.
    * @param id The unique ID of the required element, previously registered using registerID()
    * @return The NodeInfo for the given ID if one has been registered, otherwise null.
    */

    public NodeInfo selectID(String id) {
        if (idTable==null) indexIDs();
        return (NodeInfo)idTable.get(id);
    }

    /**
    * Get the index for a given key
    * @param keymanager The key manager managing this key
    * @param fingerprint The fingerprint of the name of the key (unique with the key manager)
    * @return The index, if one has been built, in the form of a Hashtable that
    * maps the key value to a Vector of nodes having that key value. If no index
    * has been built, returns null.
    */

    public synchronized Hashtable getKeyIndex(KeyManager keymanager, int fingerprint) {
        for (int k=0; k<indexEntriesUsed; k+=3) {
            if (((KeyManager)index[k])==keymanager &&
            		 ((Integer)index[k+1]).intValue()==fingerprint) {
                Object ix = index[k+2];
                return (Hashtable)index[k+2];

                            // circular references are now a compile-time error
            }
        }
        return null;
    }

    /**
    * Set the index for a given key. The method is synchronized because the same document
    * can be used by several stylesheets at the same time.
    * @param keymanager The key manager managing this key
    * @param fingerprint The fingerprint of the name of the key (unique with the key manager)
    * @param keyindex the index, in the form of a Hashtable that
    * maps the key value to a Vector of nodes having that key value. Or the String
    * "under construction", indicating that the index is being built.
    */

    public synchronized void setKeyIndex(KeyManager keymanager, int fingerprint, Hashtable keyindex) /*throws SAXException*/ {
        for (int k=0; k<indexEntriesUsed; k+=3) {
            if (((KeyManager)index[k])==keymanager &&
            		 ((Integer)index[k+1]).intValue() == fingerprint) {
                index[k+2] = keyindex;
                return;
            }
        }

        if (indexEntriesUsed+3 >= index.length) {
            Object[] index2 = new Object[indexEntriesUsed*2];
            System.arraycopy(index, 0, index2, 0, indexEntriesUsed);
            index = index2;
        }
        index[indexEntriesUsed++] = keymanager;
        index[indexEntriesUsed++] = new Integer(fingerprint);
        index[indexEntriesUsed++] = keyindex;
    }

    /**
    * Set an unparsed entity URI associated with this document. For system use only, while
    * building the document.
    */

    protected void setUnparsedEntity(String name, String uri) {
        if (entityTable==null) {
            entityTable = new Hashtable();
        }
        entityTable.put(name, uri);
    }

    /**
    * Get the unparsed entity with a given name
    * @param name the name of the entity
    * @return the URI of the entity if there is one, or empty string if not
    */

    public String getUnparsedEntity(String name) {
        if (entityTable==null) {
            return "";
        }
        String uri = (String)entityTable.get(name);
        return (uri==null ? "" : uri);
    }

    /**
    * Copy this node to a given outputter
    */

    public void copy(Outputter out) throws TransformerException {
        NodeImpl next = (NodeImpl)getFirstChild();
        while (next!=null) {
            next.copy(out);
            next = (NodeImpl)next.getNextSibling();
        }
    }
    
}

//
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
// you may not use this file except in compliance with the License. You may obtain a copy of the
// License at http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License. 
//
// The Original Code is: all this file except PB-SYNC section. 
//
// The Initial Developer of the Original Code is
// Michael Kay of International Computers Limited (mhkay@iclway.co.uk).
//
// Portions marked PB-SYNC are Copyright (C) Peter Bryant (pbryant@bigfoot.com). All Rights Reserved. 
//
// Contributor(s): Michael Kay, Peter Bryant. 
//
