package com.icl.saxon.tinytree;
import com.icl.saxon.om.*;
import com.icl.saxon.om.Axis;
import com.icl.saxon.Controller;
import com.icl.saxon.Context;
import com.icl.saxon.PreparedStyleSheet;
import com.icl.saxon.PIGrabber;
import com.icl.saxon.KeyManager;
import com.icl.saxon.expr.NodeSetExtent;
import com.icl.saxon.expr.XPathException;
import com.icl.saxon.output.Outputter;
import com.icl.saxon.sort.LocalOrderComparer;
import com.icl.saxon.pattern.Pattern;
import com.icl.saxon.pattern.AnyNodeTest;
import com.icl.saxon.tree.LineNumberMap;
import com.icl.saxon.tree.SystemIdMap;
import java.util.Hashtable;
import java.net.URL;

import org.w3c.dom.*;
import javax.xml.transform.TransformerException;


/**
  * 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> 
  * @version 26 April 1999
  */

public final class TinyDocumentImpl extends TinyParentNodeImpl
    implements DocumentInfo, Document {

    private Hashtable idTable = null;
    private NamePool namePool;
    private Hashtable elementList = null;
    private boolean usesNamespaces = false;
    private Hashtable entityTable = null;

    // the contents of the document

    protected char[] charBuffer = new char[4000];
    protected int charBufferLength = 0;
    protected StringBuffer commentBuffer = new StringBuffer(500);

    protected int numberOfNodes = 0;    // excluding attributes and namespaces
    protected int lastLevelOneNode = -1;

    protected byte[] nodeType = new byte[4000];
    protected short[] depth = new short[4000];
    /*NEXT*/ protected int[] next = new int[4000];
    protected int[] offset = new int[4000];
    protected int[] length = new int[4000];
    protected int[] nameCode = new int[4000];
    // the prior array indexes preceding-siblings; it is constructed only when required
    protected int[] prior = null;

    protected int numberOfAttributes = 0;
    protected int[] attParent = new int[100];
    protected int[] attCode = new int[100];
    protected String[] attValue = new String[100];

    protected int numberOfNamespaces = 0;
    protected int[] namespaceParent = new int[20];
    protected int[] namespaceCode = new int[20];
    
    private LineNumberMap lineNumberMap;
    private SystemIdMap systemIdMap = new SystemIdMap();

    // list of indexes for keys. Each entry is a triple: KeyManager, Fingerprint of Name of Key, 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 TinyDocumentImpl() {
        nodeNr = 0;
        document = this;
    }

	/**
	* Set the name pool used for all names in this document
	*/
	
	public void setNamePool(NamePool pool) {
		namePool = pool;
		addNamespace(0, pool.getNamespaceCode("xml", Namespace.XML));
	}
	
	/**
	* Get the name pool used for the names in this document
	*/
	
	public NamePool getNamePool() {
		return namePool;
	}

    protected void ensureNodeCapacity() {
        if (nodeType.length < numberOfNodes+1) {
            int k = numberOfNodes*2;

            byte[] nodeType2 = new byte[k];
            /*NEXT*/ int[] next2 = new int[k];
            short[] depth2 = new short[k];
            int[] offset2 = new int[k];
            int[] length2 = new int[k];
            int[] nameCode2 = new int[k];

            System.arraycopy(nodeType, 0, nodeType2, 0, numberOfNodes);
            /*NEXT*/ System.arraycopy(next, 0, next2, 0, numberOfNodes);
            System.arraycopy(depth, 0, depth2, 0, numberOfNodes);
            System.arraycopy(offset, 0, offset2, 0, numberOfNodes);
            System.arraycopy(length, 0, length2, 0, numberOfNodes);
            System.arraycopy(nameCode, 0, nameCode2, 0, numberOfNodes);

            nodeType = nodeType2;
            /*NEXT*/ next = next2;
            depth = depth2;
            offset = offset2;
            length = length2;
            nameCode = nameCode2;
        }
    }

    protected void ensureAttributeCapacity() {
        if (attParent.length < numberOfAttributes+1) {
            int k = numberOfAttributes*2;

            int[] attParent2 = new int[k];
            int[] attCode2 = new int[k];
            //byte[] attType2 = new byte[k];
            String[] attValue2 = new String[k];


            System.arraycopy(attParent, 0, attParent2, 0, numberOfAttributes);
            System.arraycopy(attCode, 0, attCode2, 0, numberOfAttributes);
            //System.arraycopy(attType, 0, attType2, 0, numberOfAttributes);
            System.arraycopy(attValue, 0, attValue2, 0, numberOfAttributes);

            attParent = attParent2;
            attCode = attCode2;
            //attType = attType2;
            attValue = attValue2;
        }
    }

    protected void ensureNamespaceCapacity() {
        if (namespaceParent.length < numberOfNamespaces+1) {
            int k = numberOfNamespaces*2;

            int[] namespaceParent2 = new int[k];
            int[] namespaceCode2 = new int[k];

            System.arraycopy(namespaceParent, 0, namespaceParent2, 0, numberOfNamespaces);
            System.arraycopy(namespaceCode, 0, namespaceCode2, 0, numberOfNamespaces);

            namespaceParent = namespaceParent2;
            namespaceCode = namespaceCode2;
        }
    }

    protected void addNode(short type0, int depth0, int offset0, int length0, int nameCode0) {
        ensureNodeCapacity();
        nodeType[numberOfNodes] = (byte)type0;
        depth[numberOfNodes] = (short)depth0;
        offset[numberOfNodes] = offset0;
        length[numberOfNodes] = length0;
        nameCode[numberOfNodes] = nameCode0;
        /*NEXT*/ next[numberOfNodes] = -1;      // safety precaution, esp for preview mode

        if (depth0 == 1) lastLevelOneNode = numberOfNodes;
        
        numberOfNodes++;
    }
    
    protected void appendChars(char[] chars, int start, int length) {
        while (charBuffer.length < charBufferLength + length) {
            char[] ch2 = new char[charBuffer.length * 2];
            System.arraycopy(charBuffer, 0, ch2, 0, charBufferLength);
            charBuffer = ch2;
        }
        System.arraycopy(chars, start, charBuffer, charBufferLength, length);
        charBufferLength += length;
    } 
    
    /**
    * Truncate the tree: used in preview mode to delete an element after it has
    * been processed
    */
    
    protected void truncate(int nodes) {
        
        if (nodes==numberOfNodes) return;

        // shrink the text buffer
        for (int i=nodes; i<numberOfNodes; i++) {
            if (nodeType[i]==NodeInfo.TEXT) {
                charBufferLength = offset[i];
                break;
            }
        }
        
        // shrink the attributes array
        for (int i=nodes; i<numberOfNodes; i++) {
            if (nodeType[i]==NodeInfo.ELEMENT && offset[i]>=0) {
                numberOfAttributes = offset[i];
                break;
            }
        }        

        // shrink the namespace array
        for (int i=nodes; i<numberOfNodes; i++) {
            if (nodeType[i]==NodeInfo.ELEMENT && length[i]>=0) {
                numberOfNamespaces = length[i];
                break;
            }
        }
        
        // TODO: shrink the comment buffer        

        // shrink the main node array                                 
        numberOfNodes = nodes;
        
        // kill the prior index, to be rebuilt when needed
        prior = null;
        
        // add a dummy node at the end, because some axes such as "following"
        // can otherwise walk off the end
        
        nodeType[nodes] = (byte)NodeInfo.ROOT;
        depth[nodes] = 0;
        // System.err.println("After truncate:"); diagnosticDump();
    }

    /**
    * On demand, make an index for quick access to preceding-sibling nodes
    */
    
    protected void ensurePriorIndex() {
        if (prior==null) {
            makePriorIndex();
        }
    }
    
    private synchronized void makePriorIndex() {
        prior = new int[numberOfNodes];
        for (int i=0; i<numberOfNodes; i++) {
            prior[i] = -1;
        }
        for (int i=0; i<numberOfNodes; i++) {
            int nextNode = next[i];
            if (nextNode!=-1) {
                prior[nextNode] = i;
            }
        }
    }
 

    protected void addAttribute(int parent0, int code0, String type0, String value0) {
        ensureAttributeCapacity();
        attParent[numberOfAttributes] = parent0;
        attCode[numberOfAttributes] = code0;
        attValue[numberOfAttributes] = value0;
        numberOfAttributes++;
        
        if (type0.equals("ID")) {
        	if (idTable==null) {
        		idTable = new Hashtable();
        	}
			NodeInfo e = getNode(parent0);
            registerID(e, value0);
        }
    }

    protected void addNamespace(int parent0, int nscode0 ) {
        usesNamespaces = true;
        ensureNamespaceCapacity();
        namespaceParent[numberOfNamespaces] = parent0;
        namespaceCode[numberOfNamespaces] = nscode0;
        numberOfNamespaces++;
    }

    public TinyNodeImpl getNode(int nr) {
        switch ((short)nodeType[nr]) {
            case NodeInfo.ROOT:
                return this;
            case NodeInfo.ELEMENT:
                return new TinyElementImpl(this, nr);
            case NodeInfo.TEXT:
                return new TinyTextImpl(this, nr);
            case NodeInfo.COMMENT:
                return new TinyCommentImpl(this, nr);
            case NodeInfo.PI:
                return new TinyProcInstImpl(this, nr);
        }
        return null;
    }

    /**
    * Get the node sequence number (in document order). Sequence numbers are monotonic but not
    * consecutive. 
    */

    public long getSequenceNumber() {
        return 0;
    }

    /**
    * Make a (transient) attribute node from the array of attributes
    */

    protected TinyAttributeImpl getAttributeNode(int nr) {
        return new TinyAttributeImpl(this, nr);
    }

    /**
    * determine whether this document uses namespaces
    */

    protected boolean isUsingNamespaces() {
        return usesNamespaces;
    }

    /**
    * Make a (transient) namespace node from the array of namespace declarations
    */

    protected TinyNamespaceImpl getNamespaceNode(int nr) {
        return new TinyNamespaceImpl(this, nr);
    }

    /**
    * 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(nodeNr, uri);
    }
        
    /**
    * Get the system id of this root node
    */

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

    /**
    * 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) {
        //    throw new IllegalArgumentException("System ID must not be null");
        //}
        if (uri==null) {
            uri = "";
        }
        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(0, 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;
    }

    /**
     * Find the parent node of this node.
     * @return The Node object describing the containing element or root node.
     */

    public NodeInfo getParent()  {
        return null;
    }
    
    /**
    * 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 unique number identifying this document
    */

    //public int getDocumentNumber() {
    //    return documentNumber;
    //}

    /**
    * Get a list of all elements with a given name. This is implemented
    * as a memo function: the first time it is called for a particular
    * element type, it remembers the result for next time.
    */

    protected AxisEnumeration getAllElements(int fingerprint) {
    	Integer key = new Integer(fingerprint);
    	if (elementList==null) {
    	    elementList = new Hashtable();
    	}
        NodeSetExtent list = (NodeSetExtent)elementList.get(key);
        if (list==null) {
            list = new NodeSetExtent(LocalOrderComparer.getInstance());
            list.setSorted(true);
            for (int i=1; i<numberOfNodes; i++) {
                if (nodeType[i]==NodeInfo.ELEMENT &&
                        (nameCode[i] & 0xfffff ) == fingerprint) {
                    list.append(getNode(i));
                }
            }
            elementList.put(key, list);
        }
        return (AxisEnumeration)list.enumerate();
    }
                    
    /**
    * Register a unique element ID. Fails if there is already an element with that ID.
    * @param e The NodeInfo (always an 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
        NodeInfo old = (NodeInfo)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 (always an Element) for the given ID if one has been registered,
    * otherwise null.
    */

    public NodeInfo selectID(String id) {
        if (idTable==null) return null;			// no ID values found
        return (NodeInfo)idTable.get(id);
    }

    /**
    * Get the index for a given key
    * @param keymanager The key manager managing this key
    * @int 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 set 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];
            }
        }
        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 set 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) {
        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 {

        // TODO: this could be optimized by walking all the descendants in order,
        // instead of doing a recursive tree walk. It would be necessary to maintain
        // a stack, so that end tags could be written when the depth decreases.

        // output the children
        
        AxisEnumeration children = 
            getEnumeration(Axis.CHILD, AnyNodeTest.getInstance());
                    
        while (children.hasMoreElements()) {
            children.nextElement().copy(out);
        }
    }

	/**
	* Produce diagnostic print of main tree arrays
	*/
	
	public void diagnosticDump() {
		System.err.println("Node\ttype\tdepth\toffset\tlength");
		for (int i=0; i<numberOfNodes; i++) {
			System.err.println(i + "\t" + nodeType[i] + "\t" + depth[i] + "\t" +
									 offset[i] + "\t" + length[i] + "\t" + Navigator.getPath(getNode(i)));
		}
	}
    
}

//
// 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. 
//
