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

package org.apache.xerces.xpointer;

import org.apache.xerces.impl.Constants;
import org.apache.xerces.impl.dv.XSSimpleType;
import org.apache.xerces.util.SymbolTable;
import org.apache.xerces.xni.Augmentations;
import org.apache.xerces.xni.QName;
import org.apache.xerces.xni.XMLAttributes;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xs.AttributePSVI;
import org.apache.xerces.xs.XSTypeDefinition;

/**
 * <p>
 * Implements the XPointerPart interface and handles processing of
 * ShortHand Pointers.  It identifies at most one element in the 
 * resource's information set; specifically, the first one (if any) 
 * in document order that has a matching NCName as an identifier.
 * </p>
 *
 * @version $Id: ShortHandPointer.java 603808 2007-12-13 03:44:48Z mrglavas $
 */
final class ShortHandPointer implements XPointerPart {
    
    // The name of the ShortHand pointer
    private String fShortHandPointer;
    
    // The name of the ShortHand pointer
    private boolean fIsFragmentResolved = false;
    
    // SymbolTable
    private SymbolTable fSymbolTable;
    
    //
    // Constructors
    //
    public ShortHandPointer() {
    }
    
    public ShortHandPointer(SymbolTable symbolTable) {
        fSymbolTable = symbolTable;
    }
    
    /**
     * The XPointerProcessor takes care of this.  Simply set the ShortHand Pointer here.
     * 
     * @see org.apache.xerces.xpointer.XPointerPart#parseXPointer(java.lang.String)
     */
    public void parseXPointer(String part) throws XNIException {
        fShortHandPointer = part;
        // reset fIsFragmentResolved
        fIsFragmentResolved = false;
    }
    
    /**
     * Resolves the XPointer ShortHand pointer based on the rules defined in 
     * Section 3.2 of the XPointer Framework Recommendation.
     * Note that in the current implementation only supports DTD determined ID's. 
     *
     * @see org.apache.xerces.xpointer.XPointerPart#resolveXPointer(org.apache.xerces.xni.QName, org.apache.xerces.xni.XMLAttributes, org.apache.xerces.xni.Augmentations, int event)
     */
    int fMatchingChildCount = 0;
    public boolean resolveXPointer(QName element, XMLAttributes attributes,
            Augmentations augs, int event) throws XNIException {

        // reset fIsFragmentResolved
        if (fMatchingChildCount == 0) {
            fIsFragmentResolved = false;
        }

        // On startElement or emptyElement, if no matching elements or parent 
        // elements were found, check for a matching idenfitier.
        if (event == XPointerPart.EVENT_ELEMENT_START) {
            if (fMatchingChildCount == 0) {
                fIsFragmentResolved = hasMatchingIdentifier(element, attributes, augs,
                    event);
            }
            if (fIsFragmentResolved) {
               fMatchingChildCount++;
            }
        } else if (event == XPointerPart.EVENT_ELEMENT_EMPTY) {
            if (fMatchingChildCount == 0) {
                fIsFragmentResolved = hasMatchingIdentifier(element, attributes, augs,
                    event);
            }
        }
        else {
            // On endElement, decrease the matching child count if the child or
            // its parent was resolved.
            if (fIsFragmentResolved) {
                fMatchingChildCount--;
            }
        }
        
        return fIsFragmentResolved ;
    }
    
    /**
     * 
     * @param element
     * @param attributes
     * @param augs
     * @param event
     * @return
     * @throws XNIException
     */
    private boolean hasMatchingIdentifier(QName element,
            XMLAttributes attributes, Augmentations augs, int event)
    throws XNIException {
        String normalizedValue = null;
        
        // The identifiers of an element are determined by the 
        // ShortHand Pointer as follows:
        
        if (attributes != null) {
            for (int i = 0; i < attributes.getLength(); i++) {
                
                // 1. If an element information item has an attribute information item 
                // among its [attributes] that is a schema-determined ID, then it is 
                // identified by the value of that attribute information item's 
                // [schema normalized value] property;
                normalizedValue = getSchemaDeterminedID(attributes, i);
                if (normalizedValue != null) {
                    break;
                }
                
                // 2. If an element information item has an element information item among 
                // its [children] that is a schema-determined ID, then it is identified by 
                // the value of that element information item's [schema normalized value] property;
                // ???
                normalizedValue = getChildrenSchemaDeterminedID(attributes, i);
                if (normalizedValue != null) {
                    break;
                }
                
                // 3. If an element information item has an attribute information item among 
                // its [attributes] that is a DTD-determined ID, then it is identified by the 
                // value of that attribute information item's [normalized value] property.
                // An attribute information item is a DTD-determined ID if and only if it has 
                // a [type definition] property whose value is equal to ID.
                normalizedValue = getDTDDeterminedID(attributes, i);
                if (normalizedValue != null) {
                    break;
                }
                // 4. No externally determined ID's
            }
        }
        
        if (normalizedValue != null
                && normalizedValue.equals(fShortHandPointer)) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Rerturns the DTD determine-ID
     * 
     * @param attributes
     * @param index
     * @return String 
     * @throws XNIException
     */
    public String getDTDDeterminedID(XMLAttributes attributes, int index)
    throws XNIException {
        
        if (attributes.getType(index).equals("ID")) {
            return attributes.getValue(index);
        }
        return null;
    }
    
    /**
     * Returns the schema-determined-ID.
     * 
     * 
     * @param attributes
     * @param index
     * @return A String containing the schema-determined ID. 
     * @throws XNIException
     */
    public String getSchemaDeterminedID(XMLAttributes attributes, int index)
    throws XNIException {
        Augmentations augs = attributes.getAugmentations(index);
        AttributePSVI attrPSVI = (AttributePSVI) augs
        .getItem(Constants.ATTRIBUTE_PSVI);
        
        if (attrPSVI != null) {
            // An element or attribute information item is a schema-determined 
            // ID if and only if one of the following is true:]
            
            // 1. It has a [member type definition] or [type definition] property 
            // whose value in turn has [name] equal to ID and [target namespace] 
            // equal to http://www.w3.org/2001/XMLSchema;
            
            // 2. It has a [base type definition] whose value has that [name] and [target namespace];
            
            // 3. It has a [base type definition] whose value has a [base type definition] 
            // whose value has that [name] and [target namespace], and so on following 
            // the [base type definition] property recursively;
            
            XSTypeDefinition typeDef = attrPSVI.getMemberTypeDefinition();
            if (typeDef != null) {
                typeDef = attrPSVI.getTypeDefinition();
            }
            
            // 
            if (typeDef != null && ((XSSimpleType) typeDef).isIDType()) {
                return attrPSVI.getSchemaNormalizedValue();
            }
            
            // 4 & 5 NA
        }
        
        return null;
    }
    
    /**
     * Not quite sure how this can be correctly implemented.
     * 
     * @param attributes
     * @param index
     * @return String - We return null since we currenly do not supprt this. 
     * @throws XNIException
     */
    public String getChildrenSchemaDeterminedID(XMLAttributes attributes,
            int index) throws XNIException {
        return null;
    }
    
    /**
     * 
     * @see org.apache.xerces.xpointer.XPointerPart#isFragmentResolved()
     */
    public boolean isFragmentResolved() {
        return fIsFragmentResolved;
    }
    
    /**
     * 
     * @see org.apache.xerces.xpointer.XPointerPart#isChildFragmentResolved()
     */
    public boolean isChildFragmentResolved() {
        return fIsFragmentResolved && (fMatchingChildCount > 0);
    }
    
    /**
     * Returns the name of the ShortHand pointer
     * 
     * @see org.apache.xerces.xpointer.XPointerPart#getSchemeName()
     */
    public String getSchemeName() {
        return fShortHandPointer;
    }
    
    /**
     * @see org.apache.xerces.xpointer.XPointerPart#getSchemeData()
     */
    public String getSchemeData() {
        return null;
    }
    
    /**
     * @see org.apache.xerces.xpointer.XPointerPart#setSchemeName(java.lang.String)
     */
    public void setSchemeName(String schemeName) {
        fShortHandPointer = schemeName;
    }
    
    /**
     * @see org.apache.xerces.xpointer.XPointerPart#setSchemeData(java.lang.String)
     */
    public void setSchemeData(String schemeData) {
        // NA
    }
}