/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */

package org.apache.xalan.xsltc.compiler;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.PUSH;
import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
import org.apache.xalan.xsltc.compiler.util.Type;
import org.apache.xalan.xsltc.compiler.util.TypeCheckError;
import org.apache.xalan.xsltc.compiler.util.Util;

import org.apache.xml.serializer.ElemDesc;
import org.apache.xml.serializer.ToHTMLStream;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 * @author Morten Jorgensen
 */
final class LiteralElement extends Instruction {

    private String _name;
    private LiteralElement _literalElemParent = null;
    private Vector _attributeElements = null;
    private Hashtable _accessedPrefixes = null;
    
    // True if all attributes of this LRE are unique, i.e. they all have
    // different names. This flag is set to false if some attribute
    // names are not known at compile time.
    private boolean _allAttributesUnique = false;
        
    private final static String XMLNS_STRING = "xmlns";

    /**
     * Returns the QName for this literal element
     */
    public QName getName() {
	return _qname;
    }
 
    /**
     * Displays the contents of this literal element
     */
    public void display(int indent) {
	indent(indent);
	Util.println("LiteralElement name = " + _name);
	displayContents(indent + IndentIncrement);
    }

    /**
     * Returns the namespace URI for which a prefix is pointing to
     */
    private String accessedNamespace(String prefix) {
        if (_literalElemParent != null) {
            String result = _literalElemParent.accessedNamespace(prefix);
            if (result != null) {
                return result;
            }
        }       
        return _accessedPrefixes != null ? 
            (String) _accessedPrefixes.get(prefix) : null;
    }

    /**
     * Method used to keep track of what namespaces that are references by
     * this literal element and its attributes. The output must contain a
     * definition for each namespace, so we stuff them in a hashtable.
     */
    public void registerNamespace(String prefix, String uri,
				  SymbolTable stable, boolean declared) {

	// Check if the parent has a declaration for this namespace
	if (_literalElemParent != null) {
	    final String parentUri = _literalElemParent.accessedNamespace(prefix);
	    if (parentUri != null && parentUri.equals(uri)) {
                return;
            }
	}

	// Check if we have any declared namesaces
	if (_accessedPrefixes == null) {
	    _accessedPrefixes = new Hashtable();
	}
	else {
	    if (!declared) {
		// Check if this node has a declaration for this namespace
		final String old = (String)_accessedPrefixes.get(prefix);
		if (old != null) {
		    if (old.equals(uri))
			return;
		    else 
			prefix = stable.generateNamespacePrefix();
		}
	    }
	}

	if (!prefix.equals("xml")) {
	    _accessedPrefixes.put(prefix,uri);
	}
    }

    /**
     * Translates the prefix of a QName according to the rules set in
     * the attributes of xsl:stylesheet. Also registers a QName to assure
     * that the output element contains the necessary namespace declarations.
     */
    private String translateQName(QName qname, SymbolTable stable) {
	// Break up the QName and get prefix:localname strings
	String localname = qname.getLocalPart();
	String prefix = qname.getPrefix();

	// Treat default namespace as "" and not null
	if (prefix == null)
	    prefix = Constants.EMPTYSTRING;
	else if (prefix.equals(XMLNS_STRING))
	    return(XMLNS_STRING);
	
	// Check if we must translate the prefix
	final String alternative = stable.lookupPrefixAlias(prefix);
	if (alternative != null) {
	    stable.excludeNamespaces(prefix);
	    prefix = alternative;
	}

	// Get the namespace this prefix refers to
	String uri = lookupNamespace(prefix);
	if (uri == null) return(localname);

	// Register the namespace as accessed
	registerNamespace(prefix, uri, stable, false);

	// Construct the new name for the element (may be unchanged)
	if (prefix != Constants.EMPTYSTRING)
	    return(prefix+":"+localname);
	else
	    return(localname);
    }

    /**
     * Add an attribute to this element
     */
    public void addAttribute(SyntaxTreeNode attribute) {
	if (_attributeElements == null) {
	    _attributeElements = new Vector(2);
	}
	_attributeElements.add(attribute);
    }

    /**
     * Set the first attribute of this element
     */
    public void setFirstAttribute(SyntaxTreeNode attribute) {
	if (_attributeElements == null) {
	    _attributeElements = new Vector(2);
	}
	_attributeElements.insertElementAt(attribute,0);
    }

    /**
     * Type-check the contents of this element. The element itself does not
     * need any type checking as it leaves nothign on the JVM's stack.
     */
    public Type typeCheck(SymbolTable stable) throws TypeCheckError {
	// Type-check all attributes
	if (_attributeElements != null) {
	    final int count = _attributeElements.size();
	    for (int i = 0; i < count; i++) {
		SyntaxTreeNode node = 
		    (SyntaxTreeNode)_attributeElements.elementAt(i);
		node.typeCheck(stable);
	    }
	}
	typeCheckContents(stable);
	return Type.Void;
    }

    /**
     * This method starts at a given node, traverses all namespace mappings,
     * and assembles a list of all prefixes that (for the given node) maps
     * to _ANY_ namespace URI. Used by literal result elements to determine
     */
    public Enumeration getNamespaceScope(SyntaxTreeNode node) {
	Hashtable all = new Hashtable();
	
	while (node != null) {
	    Hashtable mapping = node.getPrefixMapping();
	    if (mapping != null) {
		Enumeration prefixes = mapping.keys();
		while (prefixes.hasMoreElements()) {
		    String prefix = (String)prefixes.nextElement();
		    if (!all.containsKey(prefix)) {
			all.put(prefix, mapping.get(prefix));
		    }
		}
	    }
	    node = node.getParent();
	}
	return(all.keys());
    }

    /**
     * Determines the final QName for the element and its attributes.
     * Registers all namespaces that are used by the element/attributes
     */
    public void parseContents(Parser parser) {
	final SymbolTable stable = parser.getSymbolTable();
	stable.setCurrentNode(this);

	// Check if in a literal element context
	SyntaxTreeNode parent = getParent();
        if (parent != null && parent instanceof LiteralElement) {
            _literalElemParent = (LiteralElement) parent;
	}

	_name = translateQName(_qname, stable);

	// Process all attributes and register all namespaces they use
	final int count = _attributes.getLength();
	for (int i = 0; i < count; i++) {
	    final QName qname = parser.getQName(_attributes.getQName(i));
	    final String uri = qname.getNamespace();
	    final String val = _attributes.getValue(i);

	    // Handle xsl:use-attribute-sets. Attribute sets are placed first
	    // in the vector or attributes to make sure that later local
	    // attributes can override an attributes in the set.
	    if (qname.equals(parser.getUseAttributeSets())) {
            	if (!Util.isValidQNames(val)) {
                    ErrorMsg err = new ErrorMsg(ErrorMsg.INVALID_QNAME_ERR, val, this);
                    parser.reportError(Constants.ERROR, err);	
               }
		setFirstAttribute(new UseAttributeSets(val, parser));
	    }
	    // Handle xsl:extension-element-prefixes
	    else if (qname.equals(parser.getExtensionElementPrefixes())) {
		stable.excludeNamespaces(val);
	    }
	    // Handle xsl:exclude-result-prefixes
	    else if (qname.equals(parser.getExcludeResultPrefixes())) {
		stable.excludeNamespaces(val);
	    }
	    else {
		// Ignore special attributes (e.g. xmlns:prefix and xmlns)
		final String prefix = qname.getPrefix();
		if (prefix != null && prefix.equals(XMLNS_PREFIX) ||
		    prefix == null && qname.getLocalPart().equals("xmlns") ||
		    uri != null && uri.equals(XSLT_URI))
		{
		    continue;	
		}

		// Handle all other literal attributes
		final String name = translateQName(qname, stable);
		LiteralAttribute attr = new LiteralAttribute(name, val, parser, this);
		addAttribute(attr);
		attr.setParent(this);
		attr.parseContents(parser);
	    }
	}

	// Register all namespaces that are in scope, except for those that
	// are listed in the xsl:stylesheet element's *-prefixes attributes
	final Enumeration include = getNamespaceScope(this);
	while (include.hasMoreElements()) {
	    final String prefix = (String)include.nextElement();
	    if (!prefix.equals("xml")) {
		final String uri = lookupNamespace(prefix);
		if (uri != null && !stable.isExcludedNamespace(uri)) {
		    registerNamespace(prefix, uri, stable, true);
		}
	    }
	}

	parseChildren(parser);

	// Process all attributes and register all namespaces they use
	for (int i = 0; i < count; i++) {
	    final QName qname = parser.getQName(_attributes.getQName(i));
	    final String val = _attributes.getValue(i);

	    // Handle xsl:extension-element-prefixes
	    if (qname.equals(parser.getExtensionElementPrefixes())) {
		stable.unExcludeNamespaces(val);
	    }
	    // Handle xsl:exclude-result-prefixes
	    else if (qname.equals(parser.getExcludeResultPrefixes())) {
		stable.unExcludeNamespaces(val);
	    }
	}
    }

    protected boolean contextDependent() {
	return dependentContents();
    }

    /**
     * Compiles code that emits the literal element to the output handler,
     * first the start tag, then namespace declaration, then attributes,
     * then the element contents, and then the element end tag. Since the
     * value of an attribute may depend on a variable, variables must be
     * compiled first.
     */
    public void translate(ClassGenerator classGen, MethodGenerator methodGen) {

	final ConstantPoolGen cpg = classGen.getConstantPool();
	final InstructionList il = methodGen.getInstructionList();

        // Check whether all attributes are unique.
        _allAttributesUnique = checkAttributesUnique();

	// Compile code to emit element start tag
	il.append(methodGen.loadHandler());
	
	il.append(new PUSH(cpg, _name));
	il.append(DUP2); 		// duplicate these 2 args for endElement
	il.append(methodGen.startElement());

	// The value of an attribute may depend on a (sibling) variable
        int j=0;
        while (j < elementCount())  {
            final SyntaxTreeNode item = (SyntaxTreeNode) elementAt(j);
            if (item instanceof Variable) {
                item.translate(classGen, methodGen);
            }
            j++;
        }

	// Compile code to emit namespace attributes
	if (_accessedPrefixes != null) {
	    boolean declaresDefaultNS = false;
	    Enumeration e = _accessedPrefixes.keys();

	    while (e.hasMoreElements()) {
		final String prefix = (String)e.nextElement();
		final String uri = (String)_accessedPrefixes.get(prefix);

		if (uri != Constants.EMPTYSTRING || 
			prefix != Constants.EMPTYSTRING) 
		{
		    if (prefix == Constants.EMPTYSTRING) {
			declaresDefaultNS = true;
		    }
		    il.append(methodGen.loadHandler());
		    il.append(new PUSH(cpg,prefix));
		    il.append(new PUSH(cpg,uri));
		    il.append(methodGen.namespace());
		}
	    }

	    /* 
	     * If our XslElement parent redeclares the default NS, and this
	     * element doesn't, it must be redeclared one more time.
	     */
	    if (!declaresDefaultNS && (_parent instanceof XslElement)
		    && ((XslElement) _parent).declaresDefaultNS()) 
	    {
		il.append(methodGen.loadHandler());
		il.append(new PUSH(cpg, Constants.EMPTYSTRING));
		il.append(new PUSH(cpg, Constants.EMPTYSTRING));
		il.append(methodGen.namespace());
	    }
	}

	// Output all attributes
	if (_attributeElements != null) {
	    final int count = _attributeElements.size();
	    for (int i = 0; i < count; i++) {
		SyntaxTreeNode node = 
		    (SyntaxTreeNode)_attributeElements.elementAt(i);
		if (!(node instanceof XslAttribute)) {
		    node.translate(classGen, methodGen);
	        }
	    }
	}
	
	// Compile code to emit attributes and child elements
	translateContents(classGen, methodGen);

	// Compile code to emit element end tag
	il.append(methodGen.endElement());
    }

    /**
     * Return true if the output method is html.
     */
    private boolean isHTMLOutput() {
        return getStylesheet().getOutputMethod() == Stylesheet.HTML_OUTPUT;
    }
    
    /**
     * Return the ElemDesc object for an HTML element.
     * Return null if the output method is not HTML or this is not a 
     * valid HTML element.
     */
    public ElemDesc getElemDesc() {
    	if (isHTMLOutput()) {
    	    return ToHTMLStream.getElemDesc(_name);
    	}
    	else
    	    return null;
    }
    
    /**
     * Return true if all attributes of this LRE have unique names.
     */
    public boolean allAttributesUnique() {
    	return _allAttributesUnique;
    }
    
    /**
     * Check whether all attributes are unique.
     */
    private boolean checkAttributesUnique() {
    	 boolean hasHiddenXslAttribute = canProduceAttributeNodes(this, true);
    	 if (hasHiddenXslAttribute)
    	     return false;
    	 
    	 if (_attributeElements != null) {
    	     int numAttrs = _attributeElements.size();
    	     Hashtable attrsTable = null;
    	     for (int i = 0; i < numAttrs; i++) {
    	         SyntaxTreeNode node = (SyntaxTreeNode)_attributeElements.elementAt(i);
    	         
    	         if (node instanceof UseAttributeSets) {
    	             return false;
    	         }
    	         else if (node instanceof XslAttribute) {   	             
    	             if (attrsTable == null) {
    	             	attrsTable = new Hashtable();
    	                 for (int k = 0; k < i; k++) {
    	                     SyntaxTreeNode n = (SyntaxTreeNode)_attributeElements.elementAt(k);
    	                     if (n instanceof LiteralAttribute) {
    	                         LiteralAttribute literalAttr = (LiteralAttribute)n;
    	                         attrsTable.put(literalAttr.getName(), literalAttr);
    	                     }
    	                 }
    	             }
    	             
    	             XslAttribute xslAttr = (XslAttribute)node;
    	             AttributeValue attrName = xslAttr.getName();
    	             if (attrName instanceof AttributeValueTemplate) {
    	                 return false;
    	             }
    	             else if (attrName instanceof SimpleAttributeValue) {
    	                 SimpleAttributeValue simpleAttr = (SimpleAttributeValue)attrName;
    	                 String name = simpleAttr.toString();
    	                 if (name != null && attrsTable.get(name) != null)
    	                     return false;
    	                 else if (name != null) {
    	                     attrsTable.put(name, xslAttr);
    	                 }    	                 
    	             }
    	         }
    	     }
    	 }
    	 return true;
    }
    
    /**
     * Return true if the instructions under the given SyntaxTreeNode can produce attribute nodes
     * to an element. Only return false when we are sure that no attribute node is produced. 
     * Return true if we are not sure. If the flag ignoreXslAttribute is true, the direct 
     * <xsl:attribute> children of the current node are not included in the check.
     */
    private boolean canProduceAttributeNodes(SyntaxTreeNode node, boolean ignoreXslAttribute) {
    	Vector contents = node.getContents();
    	int size = contents.size();
    	for (int i = 0; i < size; i++) {
    	    SyntaxTreeNode child = (SyntaxTreeNode)contents.elementAt(i);
    	    if (child instanceof Text) {
    	    	Text text = (Text)child;
    	    	if (text.isIgnore())
    	    	    continue;
    	    	else
    	    	    return false;
    	    }
    	    // Cannot add an attribute to an element after children have been added to it.
    	    // We can safely return false when the instruction can produce an output node.
   	    else if (child instanceof LiteralElement
   	        || child instanceof ValueOf
   	        || child instanceof XslElement
   	        || child instanceof Comment
   	        || child instanceof Number
   	        || child instanceof ProcessingInstruction)
    	        return false;
    	    else if (child instanceof XslAttribute) {
    	    	if (ignoreXslAttribute)
    	    	    continue;
    	    	else
    	    	    return true;
    	    }
    	    // In general, there is no way to check whether <xsl:call-template> or 
    	    // <xsl:apply-templates> can produce attribute nodes. <xsl:copy> and
    	    // <xsl:copy-of> can also copy attribute nodes to an element. Return
    	    // true in those cases to be safe.
    	    else if (child instanceof CallTemplate
    	        || child instanceof ApplyTemplates
    	        || child instanceof Copy
    	        || child instanceof CopyOf)
    	        return true;
    	    else if ((child instanceof If
    	               || child instanceof ForEach)
    	             && canProduceAttributeNodes(child, false)) {
     	    	return true;
    	    }
    	    else if (child instanceof Choose) {
    	    	Vector chooseContents = child.getContents();
    	    	int num = chooseContents.size();
    	    	for (int k = 0; k < num; k++) {
    	    	    SyntaxTreeNode chooseChild = (SyntaxTreeNode)chooseContents.elementAt(k);
    	    	    if (chooseChild instanceof When || chooseChild instanceof Otherwise) {
    	    	    	if (canProduceAttributeNodes(chooseChild, false))
    	    	    	    return true;
    	    	    }
    	    	}
    	    }
    	}
    	return false;
    }
    
}  
