/*
 * Copyright 1999-2005 The Apache Software Foundation.
 * 
 * Licensed 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.fop.fo;

// Java
import java.util.ListIterator;

import org.xml.sax.Attributes;
import org.xml.sax.Locator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fo.extensions.ExtensionElementMapping;
import org.apache.fop.fo.extensions.svg.SVGElementMapping;
import org.apache.fop.fo.pagination.Root;
import org.apache.fop.util.CharUtilities;

/**
 * base class for nodes in the XML tree
 */
public abstract class FONode implements Cloneable {

    protected static String FO_URI = FOElementMapping.URI;

    /** Parent FO node */
    protected FONode parent;

    /**  Marks location of this object from the input FO
     *   Call locator.getSystemId(), getLineNumber(),
     *   getColumnNumber() for file, line, column
     *   information
     */
    public Locator locator;

    /** Logger for fo-tree related messages **/
    private static Log log = LogFactory.getLog(FONode.class);

    /**
     * Main constructor.
     * @param parent parent of this node
     */
    protected FONode(FONode parent) {
        this.parent = parent;
    }

    /**
     * Perform a shallow cloning operation,
     * set its parent, and optionally clean the list of child nodes
     * @param parent the intended parent of the clone
     * @param removeChildren if true, clean the list of child nodes
     * @return the cloned FO node
     */
    public FONode clone(FONode parent, boolean removeChildren)
        throws FOPException {
        FONode foNode = (FONode) clone();
        foNode.parent = parent;
        parent.addChildNode(foNode);
        return foNode;
    }

    /**
     * Perform a shallow cloning operation
     * 
     * @see java.lang.Object#clone()
     * @return the cloned object
     */
    protected Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) { }
        return null;
    }

    /**
     * Set the location information for this element
     * @param locator the org.xml.sax.Locator object
     */
    public void setLocator(Locator locator) {
        if (locator != null) {
            this.locator = locator;
        }
    }

    /**
     * Recursively goes up the FOTree hierarchy until the fo:root is found,
     * which returns the parent FOEventHandler.
     * @return the FOEventHandler object that is the parent of the FO Tree
     */
    public FOEventHandler getFOEventHandler() {
        return parent.getFOEventHandler();
    }

    /**
     * Returns the user agent for the node.
     * @return FOUserAgent
     */
    public FOUserAgent getUserAgent() {
        return getFOEventHandler().getUserAgent();
    }

    /**
     * Returns the logger for the node.
     * @return the logger
     */
    public Log getLogger() {
        return log;
    }

    /**
     * Initialize the node with its name, location information, and attributes
     * The attributes must be used immediately as the sax attributes
     * will be altered for the next element.
     * @param elementName element name (e.g., "fo:block")
     * @param locator Locator object (ignored by default)
     * @param attlist Collection of attributes passed to us from the parser.
     * @throws FOPException for errors or inconsistencies in the attributes
    */
    public void processNode(String elementName, Locator locator, 
            Attributes attlist, PropertyList parent) throws FOPException {
        log.debug("name = " + elementName);
    }

    /**
     * Create a property list for this node. Return null if the node does not
     * need a property list.
     * @param parent the closest parent propertylist. 
     * @param foEventHandler The FOEventHandler where the PropertyListMaker 
     *              instance can be found.
     * @return A new property list.
     */
    protected PropertyList createPropertyList(PropertyList parent, FOEventHandler foEventHandler) throws FOPException {
        return null;
    }

    /**
     * Checks to make sure, during SAX processing of input document, that the
     * incoming node is valid for the this (parent) node (e.g., checking to
     * see that fo:table is not an immediate child of fo:root)
     * called within FObj constructor
     * @param namespaceURI namespace of incoming node
     * @param localName (e.g. "table" for "fo:table")
     * @throws ValidationException if incoming node not valid for parent
     */
    protected void validateChildNode(Locator loc, String namespaceURI, String localName) 
        throws ValidationException {}

    /**
     * Adds characters (does nothing here)
     * @param data array of characters containing text to be added
     * @param start starting array element to add
     * @param end ending array element to add
     * @param pList currently applicable PropertyList 
     * @param locator location in fo source file.
     * @throws FOPException if there's a problem during processing
     */
    protected void addCharacters(char[] data, int start, int end,
                                 PropertyList pList,
                                 Locator locator) throws FOPException {
        // ignore
    }

    /**
    *
    */
    protected void startOfNode() throws FOPException {
        // do nothing by default
   }

    /**
     *  Primarily used for making final content model validation checks
     *  and/or informing the FOEventHandler that the end of this FO
     *  has been reached.
     */
    protected void endOfNode() throws FOPException {
        // do nothing by default
    }

    /**
     * @param child child node to be added to the childNodes of this node
     */
    protected void addChildNode(FONode child) throws FOPException {
    }

    /**
     * Removes a child node. Used by the child nodes to remove themselves, for
     * example table-body if it has no children.
     * @param child child node to be removed
     */
    public void removeChild(FONode child) {
        //nop
    }

    /**
     * @return the parent node of this node
     */
    public FONode getParent() {
        return this.parent;
    }

    /**
     * Return an iterator over all the child nodes of this FObj.
     * @return A ListIterator.
     */
    public ListIterator getChildNodes() {
        return null;
    }

    /**
     * Return an iterator over the object's child nodes starting
     * at the pased node.
     * @param childNode First node in the iterator
     * @return A ListIterator or null if child node isn't a child of
     * this FObj.
     */
    public ListIterator getChildNodes(FONode childNode) {
        return null;
    }

    /**
     * @return an iterator for the characters in this node
     */
    public CharIterator charIterator() {
        return new OneCharIterator(CharUtilities.CODE_EOT);
    }

    /**
     * Helper function to standardize the names of all namespace URI - local
     * name pairs in text messages.
     * For readability, using fo:, fox:, svg:, for those namespaces even
     * though that prefix may not have been chosen in the document.
     * @param namespaceURI URI of node found 
     *         (e.g., "http://www.w3.org/1999/XSL/Format")
     * @param localName local name of node, (e.g., "root" for "fo:root")
     * @return the prefix:localname, if fo/fox/svg, or a longer representation
     * with the unabbreviated URI otherwise.
     */
    public static String getNodeString(String namespaceURI, String localName) {
        if (namespaceURI.equals(FOElementMapping.URI)) {
            return "fo:" + localName;
        } else if (namespaceURI.equals(ExtensionElementMapping.URI)) {
            return "fox:" + localName;
        } else if (namespaceURI.equals(SVGElementMapping.URI)) {
            return "svg:" + localName;
        } else
            return "(Namespace URI: \"" + namespaceURI + "\", " +
                "Local Name: \"" + localName + "\")";
    }

    /**
     * Helper function to standardize property error exceptions
     * (e.g., not specifying either an internal- or an external-destination
     * property for an FO:link)
     * @param problem text to display that indicates the problem
     */
    protected void attributeError(String problem) 
        throws ValidationException {
        throw new ValidationException(errorText(locator) + getName() + ", " + 
            problem, locator);
    }

    /**
     * Helper function to standardize attribute warnings
     * (e.g., currently unsupported properties)
     * @param problem text to display that indicates the problem
     */
    protected void attributeWarning(String problem) {
        getLogger().warn(errorText(locator) + getName() + ", " + problem);
    }

    /**
     * Helper function to standardize "too many" error exceptions
     * (e.g., two fo:declarations within fo:root)
     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
     * @param nsURI namespace URI of incoming invalid node
     * @param lName local name (i.e., no prefix) of incoming node 
     */
    protected void tooManyNodesError(Locator loc, String nsURI, String lName) 
        throws ValidationException {
        throw new ValidationException(errorText(loc) + "For " + getName() + 
            ", only one " + getNodeString(nsURI, lName) + " may be declared.", 
            loc);
    }

    /**
     * Helper function to standardize "too many" error exceptions
     * (e.g., two fo:declarations within fo:root)
     * This overrloaded method helps make the caller code better self-documenting
     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
     * @param offendingNode incoming node that would cause a duplication.
     */
    protected void tooManyNodesError(Locator loc, String offendingNode) 
        throws ValidationException {
        throw new ValidationException(errorText(loc) + "For " + getName() + 
            ", only one " + offendingNode + " may be declared.", loc);
    }

    /**
     * Helper function to standardize "out of order" exceptions
     * (e.g., fo:layout-master-set appearing after fo:page-sequence)
     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
     * @param tooLateNode string name of node that should be earlier in document
     * @param tooEarlyNode string name of node that should be later in document
     */
    protected void nodesOutOfOrderError(Locator loc, String tooLateNode, 
        String tooEarlyNode) throws ValidationException {
        throw new ValidationException(errorText(loc) + "For " + getName() + ", " + tooLateNode 
            + " must be declared before " + tooEarlyNode + ".", loc);
    }
    
    /**
     * Helper function to return "invalid child" exceptions
     * (e.g., fo:block appearing immediately under fo:root)
     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
     * @param nsURI namespace URI of incoming invalid node
     * @param lName local name (i.e., no prefix) of incoming node 
     */
    protected void invalidChildError(Locator loc, String nsURI, String lName) 
        throws ValidationException {
        invalidChildError(loc, nsURI, lName, null);
    }
    
    /**
     * Helper function to return "invalid child" exceptions with more
     * complex validation rules (i.e., needing more explanation of the problem)
     * @param loc org.xml.sax.Locator object of the error (*not* parent node)
     * @param nsURI namespace URI of incoming invalid node
     * @param lName local name (i.e., no prefix) of incoming node
     * @param ruleViolated text explanation of problem
     */
    protected void invalidChildError(Locator loc, String nsURI, String lName,
        String ruleViolated)
        throws ValidationException {
        throw new ValidationException(errorText(loc) + getNodeString(nsURI, lName) + 
            " is not a valid child element of " + getName() 
            + ((ruleViolated != null) ? ": " + ruleViolated : "."), loc);
    }

    /**
     * Helper function to return missing child element errors
     * (e.g., fo:layout-master-set not having any page-master child element)
     * @param contentModel The XSL Content Model for the fo: object.
     * or a similar description indicating child elements needed.
     */
    protected void missingChildElementError(String contentModel)
        throws ValidationException {
        throw new ValidationException(errorText(locator) + getName() + 
            " is missing child elements. \nRequired Content Model: " 
            + contentModel, locator);
    }

    /**
     * Helper function to return missing child element errors
     * (e.g., fo:layout-master-set not having any page-master child element)
     * @param contentModel The XSL Content Model for the fo: object.
     * or a similar description indicating child elements needed.
     */
    protected void missingPropertyError(String propertyName)
        throws ValidationException {
        throw new ValidationException(errorText(locator) + getName() +
            " is missing required \"" + propertyName + "\" property.", locator);
    }

    /**
     * Helper function to return "Error (line#/column#)" string for
     * above exception messages
     * @param loc org.xml.sax.Locator object
     * @return String opening error text
     */
    protected static String errorText(Locator loc) {
        if (loc == null) {
            return "Error(Unknown location): ";
        } else {
            return "Error(" + loc.getLineNumber() + "/" + loc.getColumnNumber() + "): ";
        }
    }

    /**
     * Helper function to return "Warning (line#/column#)" string for
     * warning messages
     * @param loc org.xml.sax.Locator object
     * @return String opening warning text
     */
    protected static String warningText(Locator loc) {
        if (loc == null) {
            return "Warning(Unknown location): ";
        } else {
            return "Warning(" + loc.getLineNumber() + "/" + loc.getColumnNumber() + "): ";
        }
    }

    /**
     * Returns the root node of this tree
     * @return the root node
     */
    public Root getRoot() {
        return parent.getRoot();
    }

    /**
     * Returns the name of the node
     * @return the name of this node
     */
    public String getName() {
        return null;
    }

    /**
     * Returns the Constants class integer value of this node
     * @return the integer enumeration of this FO (e.g., FO_ROOT)
     *      if a formatting object, FO_UNKNOWN_NODE otherwise
     */
    public int getNameId() {
        return Constants.FO_UNKNOWN_NODE;
    }

}

