/*
 * Copyright 2007 Werner Guttmann
 *
 * 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.
 */
package org.exolab.castor.builder.binding;

import org.exolab.castor.xml.schema.AttributeDecl;
import org.exolab.castor.xml.schema.ComplexType;
import org.exolab.castor.xml.schema.ElementDecl;
import org.exolab.castor.xml.schema.Group;
import org.exolab.castor.xml.schema.ModelGroup;
import org.exolab.castor.xml.schema.Schema;
import org.exolab.castor.xml.schema.SimpleType;
import org.exolab.castor.xml.schema.Structure;

/**
 * Helper class to assemble an XPATH expression to qualify the path of 
 * an XML schema structure from the XML schema root. 
 * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
 * @since 1.1
 */
public class XPathHelper {

    /**
     * Initial size of the {@link StringBuffer} used to asseble the XPATH
     * expression.
     */
    private static final int INITIAL_XPATH_SIZE = 50;

    /**
     * Deduces an XPATH expression qualifying the path from the schema root
     * to the given structure in question.
     * @param structure AN XML structure.
     * @param location The XPATH expression to be created.
     */
    public static void getSchemaLocation(
            final Structure structure,
            final StringBuffer location) {
        getSchemaLocation(structure, location, false);
    }

    /**
     * Deduces an XPATH expression qualifying the path from the schema root to
     * the given structure in question.
     * 
     * @param structure
     *            AN XML structure.
     * @param location
     *            The XPATH expression to be created.
     * @param dealWithAnonTypes
     *            Indicates whether to include XPATH fragments for anonymous
     *            types.
     */
    public static void getSchemaLocation(
            final Structure structure,
            final StringBuffer location,
            final boolean dealWithAnonTypes) {
        if (structure == null) {
            throw new IllegalArgumentException("Structure cannot be null");
        }

        if (location == null) {
            throw new IllegalArgumentException("location cannot be null");
        }

        Structure parent = null;
        switch (structure.getStructureType()) {
        case Structure.ELEMENT:
            parent = ((ElementDecl) structure).getParent();
            if (parent.getStructureType() != Structure.SCHEMA) {
                getSchemaLocation(parent, location, dealWithAnonTypes);
            }
            location.append(ExtendedBinding.PATH_SEPARATOR);
            location.append(((ElementDecl) structure).getName());
            if (parent instanceof Schema) {
                Schema schema = (Schema) parent;
                if (schema.getTargetNamespace() != null) {
                    String targetNamespace = schema.getTargetNamespace();
                    location.append("{" + targetNamespace + "}");
                }
            }
            break;

        case Structure.COMPLEX_TYPE:
            ComplexType complexType = (ComplexType) structure;
            parent = (complexType).getParent();
            if (parent.getStructureType() != Structure.SCHEMA) {
                getSchemaLocation(parent, location, dealWithAnonTypes);
            }
            if (complexType.getName() != null) {
                location.append(ExtendedBinding.PATH_SEPARATOR);
                location.append(ExtendedBinding.COMPLEXTYPE_ID);
                location.append(((ComplexType) structure).getName());
            } 
//            else if (dealWithAnonTypes){
//                location.append(ExtendedBinding.PATH_SEPARATOR);
//                location.append(ExtendedBinding.COMPLEXTYPE_ID);
//                location.append("anonymous");
//            }
            break;

        case Structure.SIMPLE_TYPE:
            SimpleType simpleType = (SimpleType) structure;
            parent = simpleType.getParent();
            if (parent != null && parent.getStructureType() != Structure.SCHEMA) {
                getSchemaLocation(parent, location, dealWithAnonTypes);
            }

            if (parent != null && simpleType.getName() != null) {
                location.append(ExtendedBinding.PATH_SEPARATOR);
                location.append(ExtendedBinding.ENUMTYPE_ID);
                location.append(((SimpleType) structure).getName());
            }
//            else if (dealWithAnonTypes){
//                location.append(ExtendedBinding.PATH_SEPARATOR);
//                location.append(ExtendedBinding.ENUMTYPE_ID);
//                location.append("anonymous");
//            }
            break;

        case Structure.MODELGROUP:
            ModelGroup group = (ModelGroup) structure;
            parent = group.getParent();
            if (parent.getStructureType() != Structure.SCHEMA) {
                getSchemaLocation(parent, location, dealWithAnonTypes);
            }
            if (group.getName() != null) {
                location.append(ExtendedBinding.PATH_SEPARATOR);
                location.append(ExtendedBinding.GROUP_ID);
                location.append(group.getName());
            }
            break;

        case Structure.ATTRIBUTE:
            parent = ((AttributeDecl) structure).getParent();
            if (parent.getStructureType() != Structure.SCHEMA) {
                getSchemaLocation(parent, location, dealWithAnonTypes);
            }
            location.append(ExtendedBinding.PATH_SEPARATOR);
            location.append(ExtendedBinding.ATTRIBUTE_PREFIX);
            location.append(((AttributeDecl) structure).getName());
            break;

        case Structure.GROUP:
            // --we are inside a complexType
            getSchemaLocation(((Group) structure).getParent(), location, dealWithAnonTypes);
            break;

        // case Structure.ATTRIBUTE_GROUP:
        // //handle the real location

        default:
            break;
        }
    }

    /**
     * Returns a string (XPATH) representation of an XML Schema component. This
     * representation is directly adapted from XPath and will used as a key to
     * store the component bindings.
     * <p>
     * The location of a structure is composed of two parts:
     * <ol>
     *   <li>the location of the parent structure</li>
     *   <li>the local location of the structure itself</li>
     * </ol>
     * <p>
     * The local location is defined by:
     * <ul>
     *   <li>If the structure is an <b>Element</b>: the location is the XPath
     *       representation "/element_name"</li>
     *   <li>If the structure is an <b>Attribute</b>: the location is the XPath
     *       representation "/@attribute_name"</li>
     *   <li>If the structure is a <b>ComplexType</b>: the location is
     *       "complexType:complexType_name"</li>
     *   <li>If the structure is a <b>SimpleType</b>: the location is
     *       "simpleType:simpleType_name"</li>
     *   <li>If the structure is a <b>Enumeration</b>: the location is
     *       "enumType:enumType_name"</li>
     *   <li>If the structure is a <b>ModelGroup</b>: the location is
     *       "group:group_name"</li>
     * </ul>
     * Note that only top-level groups and complexTypes are named and thus will
     *
     * @param structure the structure for which to return a representation.
     * @param dealWithAnonTypes
     *            Indicates whether to include XPATH fragments for anonymous
     *            types.
     * @return a string representation of an XML Schema component.
     */
    public static String getSchemaLocation(final Structure structure, 
            final boolean dealWithAnonTypes) {
        if (structure == null) {
            return null;
        }
        StringBuffer buffer = new StringBuffer(INITIAL_XPATH_SIZE);
        getSchemaLocation(structure, buffer, dealWithAnonTypes);
        return buffer.toString();
    }

    /**
     * Returns a string (XPATH) representation of an XML Schema component. This
     * representation is directly adapted from XPath and will used as a key to
     * store the component bindings.
     * @param structure the structure for which to return a representation
     * @return a string representation of the XPATH identifying an XML Schema component
     * @see #getSchemaLocation(Structure, boolean)
     */
    public static String getSchemaLocation(final Structure structure) {
        if (structure == null) {
            return null;
        }
        StringBuffer buffer = new StringBuffer(INITIAL_XPATH_SIZE);
        getSchemaLocation(structure, buffer, false);
        return buffer.toString();
    }

}
