/*******************************************************************************
 * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/
package org.eclipse.persistence.internal.oxm;

import java.util.List;

import javax.xml.namespace.QName;

import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.eclipse.persistence.core.queries.CoreAttributeItem;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.queries.CoreContainerPolicy;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.mappings.CompositeCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.InverseReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.UnmarshalKeepAsElementPolicy;
import org.eclipse.persistence.internal.oxm.record.MarshalContext;
import org.eclipse.persistence.internal.oxm.record.MarshalRecord;
import org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext;
import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord;
import org.eclipse.persistence.internal.oxm.record.XMLReader;
import org.eclipse.persistence.internal.oxm.record.XMLRecord;
import org.eclipse.persistence.internal.oxm.record.deferred.CompositeCollectionMappingContentHandler;
import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy;
import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * INTERNAL:
 * <p><b>Purpose</b>: This is how the XML Composite Collection Mapping is handled when used with the TreeObjectBuilder.</p>
 * when used with the TreeObjectBuilder.
 * </p>
 */
public class XMLCompositeCollectionMappingNodeValue extends XMLRelationshipMappingNodeValue implements ContainerValue {
    private CompositeCollectionMapping xmlCompositeCollectionMapping;
    private int index = -1;
    private boolean isInverseReference;

    public XMLCompositeCollectionMappingNodeValue(CompositeCollectionMapping xmlCompositeCollectionMapping) {
        super();
        this.xmlCompositeCollectionMapping = xmlCompositeCollectionMapping;
    }
        
    public XMLCompositeCollectionMappingNodeValue(CompositeCollectionMapping xmlCompositeCollectionMapping, boolean isInverse) {
        this(xmlCompositeCollectionMapping);
        this.isInverseReference = isInverse;
    }

    public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver) {
        if (xmlCompositeCollectionMapping.isReadOnly()) {
            return false;
        }

        Object collection = xmlCompositeCollectionMapping.getAttributeAccessor().getAttributeValueFromObject(object);
        if (null == collection) {
            AbstractNullPolicy wrapperNP = xmlCompositeCollectionMapping.getWrapperNullPolicy();
            if (wrapperNP != null && wrapperNP.getMarshalNullRepresentation() == XMLNullRepresentationType.XSI_NIL) {
                marshalRecord.nilSimple(namespaceResolver);
                return true;
            } else {
                return false;
            }
        }
        
        CoreContainerPolicy cp = getContainerPolicy();
        int size = marshalRecord.getCycleDetectionStack().size();
        // when writing the collection need to see if any of the objects we are
        // writing are in the parent collection inverse ref
        if ((isInverseReference || xmlCompositeCollectionMapping.getInverseReferenceMapping() != null) && size >= 2) {
        	Object owner = marshalRecord.getCycleDetectionStack().get(size - 2);
        	try {
        		if (cp.contains(owner, collection, session)) {
        			return false;
        		}
        	} catch (ClassCastException e) {
        		// For Bug #416875
        	}
        }
        Object iterator = cp.iteratorFor(collection);        
        if (null != iterator && cp.hasNext(iterator)) {
        	XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver);
        	marshalRecord.closeStartGroupingElements(groupingFragment);
        } else {
        	return marshalRecord.emptyCollection(xPathFragment, namespaceResolver, xmlCompositeCollectionMapping.getWrapperNullPolicy() != null);
        }
                        
        marshalRecord.startCollection();
        iterator = cp.iteratorFor(collection);
        while (cp.hasNext(iterator)) {
            Object objectValue = cp.next(iterator, session);
            marshalSingleValue(xPathFragment, marshalRecord, object, objectValue, session, namespaceResolver, ObjectMarshalContext.getInstance());
        }
        marshalRecord.endCollection();
        return true;
    }

    public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) {
        try {        	
        	Descriptor xmlDescriptor = (Descriptor) xmlCompositeCollectionMapping.getReferenceDescriptor();        	
            if (xmlDescriptor == null) {
            	xmlDescriptor = findReferenceDescriptor(xPathFragment, unmarshalRecord, atts, xmlCompositeCollectionMapping, xmlCompositeCollectionMapping.getKeepAsElementPolicy());
                
            	if (xmlDescriptor == null) {
            		if (unmarshalRecord.getXMLReader().isNullRepresentedByXsiNil(xmlCompositeCollectionMapping.getNullPolicy())) {
            			if (unmarshalRecord.isNil()) {
                            return true;
                		}
                    } else if (xmlCompositeCollectionMapping.getNullPolicy().valueIsNull(atts)) { 
                    	getContainerPolicy().addInto(null, unmarshalRecord.getContainerInstance(this), unmarshalRecord.getSession());
                    	return true;
                    }
            		if (xmlCompositeCollectionMapping.getField() != null) {
                        //try leaf element type
            			QName leafType = ((Field) xmlCompositeCollectionMapping.getField()).getLastXPathFragment().getLeafElementType();
                        if (leafType != null) {
                            XPathFragment frag = new XPathFragment();
                            frag.setNamespaceAware(unmarshalRecord.isNamespaceAware());

                            String xpath = leafType.getLocalPart();
                            String uri = leafType.getNamespaceURI();
                            if (uri != null && uri.length() > 0) {
                                frag.setNamespaceURI(uri);
                                String prefix = ((Descriptor) xmlCompositeCollectionMapping.getDescriptor()).getNonNullNamespaceResolver().resolveNamespaceURI(uri);
                                if (prefix != null && prefix.length() > 0) {
                                    xpath = prefix + Constants.COLON + xpath;
                                }
                            }
                            frag.setXPath(xpath);  
                            Context xmlContext = unmarshalRecord.getUnmarshaller().getContext();
                            xmlDescriptor = xmlContext.getDescriptorByGlobalType(frag);
                        }
                    }
                } 
                    
                UnmarshalKeepAsElementPolicy policy = xmlCompositeCollectionMapping.getKeepAsElementPolicy();
                if (policy != null && ((xmlDescriptor == null && policy.isKeepUnknownAsElement()) || policy.isKeepAllAsElement())) {                    
                    if (unmarshalRecord.getTypeQName() != null) {                    	
                        Class theClass = unmarshalRecord.getConversionManager().javaType(unmarshalRecord.getTypeQName());
                        if (theClass == null) {
                            setupHandlerForKeepAsElementPolicy(unmarshalRecord, xPathFragment, atts);
                            return true;
                        }
                    } else {
                        setupHandlerForKeepAsElementPolicy(unmarshalRecord, xPathFragment, atts);
                        return true;
                    }
                }          
            }

            AbstractNullPolicy nullPolicy = xmlCompositeCollectionMapping.getNullPolicy();
            if (nullPolicy.isNullRepresentedByEmptyNode()) {
                String qnameString = xPathFragment.getLocalName();
                if (xPathFragment.getPrefix() != null) {
                	qnameString = xPathFragment.getPrefix() + Constants.COLON + qnameString;
                }
                if (null != xmlDescriptor) {
                	// Process null capable value
                    CompositeCollectionMappingContentHandler aHandler = new CompositeCollectionMappingContentHandler(//
                    		unmarshalRecord, this, xmlCompositeCollectionMapping, atts, xPathFragment, xmlDescriptor);
                    // Send control to the handler
                    aHandler.startElement(xPathFragment.getNamespaceURI(), xPathFragment.getLocalName(), qnameString, atts);
                    XMLReader xmlReader = unmarshalRecord.getXMLReader();
                    xmlReader.setContentHandler(aHandler);
                    xmlReader.setLexicalHandler(aHandler);
                }
            } else if (!(unmarshalRecord.getXMLReader().isNullRecord(nullPolicy, atts, unmarshalRecord))) {            	
            	Field xmlFld = (Field) this.xmlCompositeCollectionMapping.getField();
                if (xmlFld.hasLastXPathFragment()) {
                    unmarshalRecord.setLeafElementType(xmlFld.getLastXPathFragment().getLeafElementType());
                }
                processChild(xPathFragment, unmarshalRecord, atts, xmlDescriptor, xmlCompositeCollectionMapping);
            }
        } catch (SAXException e) {
            throw XMLMarshalException.unmarshalException(e);
        }
        return true;
    }

    public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) {
        Object collection = unmarshalRecord.getContainerInstance(this);
        endElement(xPathFragment, unmarshalRecord, collection);
    }
    
    public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Object collection) {
        if(unmarshalRecord.isNil() && unmarshalRecord.getXMLReader().isNullRepresentedByXsiNil(xmlCompositeCollectionMapping.getNullPolicy()) && 
        		(unmarshalRecord.getChildRecord() == null)){
        	if(unmarshalRecord.getXMLReader().isInCollection()){
        		unmarshalRecord.addAttributeValue(this, null);
        	}else{
        		unmarshalRecord.setAttributeValueNull(this);
        	}
            unmarshalRecord.resetStringBuffer();        	
            return;
        }
        if (null == unmarshalRecord.getChildRecord()) {
               SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder();
               UnmarshalKeepAsElementPolicy keepAsElementPolicy = xmlCompositeCollectionMapping.getKeepAsElementPolicy();        
                              
               if (null != keepAsElementPolicy && (keepAsElementPolicy.isKeepUnknownAsElement() || keepAsElementPolicy.isKeepAllAsElement()) && builder.getNodes().size() > 1) {
                   if(unmarshalRecord.getTypeQName() != null){
                       Class theClass = unmarshalRecord.getConversionManager().javaType(unmarshalRecord.getTypeQName());
                       if(theClass != null){
                           //handle simple text
                           endElementProcessText(unmarshalRecord, xmlCompositeCollectionMapping, xPathFragment, collection);
                           return;
                       }
                   }
            	   if(builder.getNodes().size() > 1) {
                       setOrAddAttributeValueForKeepAsElement(builder, xmlCompositeCollectionMapping, xmlCompositeCollectionMapping, unmarshalRecord, true, collection);
                       return;
                   }
               }else{
                    //handle simple text
                    endElementProcessText(unmarshalRecord, xmlCompositeCollectionMapping, xPathFragment, collection);
                    return;
               }

               return;
        }
        Object objectValue = unmarshalRecord.getChildRecord().getCurrentObject();

        InverseReferenceMapping inverseReferenceMapping = xmlCompositeCollectionMapping.getInverseReferenceMapping();
        if(null != inverseReferenceMapping) {
            if(inverseReferenceMapping.getContainerPolicy() == null) {
            	Object currentValue = inverseReferenceMapping.getAttributeAccessor().getAttributeValueFromObject(objectValue);
                if( !isInverseReference || (currentValue == null && isInverseReference)) {
                   inverseReferenceMapping.getAttributeAccessor().setAttributeValueInObject(objectValue, unmarshalRecord.getCurrentObject());
                }
            } else {
                Object backpointerContainer = inverseReferenceMapping.getAttributeAccessor().getAttributeValueFromObject(objectValue);
                if(backpointerContainer == null) {
                    backpointerContainer = inverseReferenceMapping.getContainerPolicy().containerInstance();
                    inverseReferenceMapping.getAttributeAccessor().setAttributeValueInObject(objectValue, backpointerContainer);
                }
                inverseReferenceMapping.getContainerPolicy().addInto(unmarshalRecord.getCurrentObject(), backpointerContainer, unmarshalRecord.getSession());
            }
        }
        
        // convert the value - if necessary
        objectValue = xmlCompositeCollectionMapping.convertDataValueToObjectValue(objectValue, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller());
        unmarshalRecord.addAttributeValue(this, objectValue, collection);

        unmarshalRecord.setChildRecord(null);

    }

    public Object getContainerInstance() {
        return getContainerPolicy().containerInstance();
    }

    public void setContainerInstance(Object object, Object containerInstance) {
        xmlCompositeCollectionMapping.setAttributeValueInObject(object, containerInstance);
    }

    public CoreContainerPolicy getContainerPolicy() {
        return xmlCompositeCollectionMapping.getContainerPolicy();
    }
    
    public boolean isContainerValue() {
        return true;
    }
    
	public boolean marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
      
        Marshaller marshaller = marshalRecord.getMarshaller();
        // convert the value - if necessary
        boolean isNil = false;
        if (value instanceof Root) {
        	isNil = ((Root) value).nil;
        	value = ((Root) value).getObject();
        }
        
        value = xmlCompositeCollectionMapping.convertObjectValueToDataValue(value, session, marshaller);
        if (null == value) {
        	   return xmlCompositeCollectionMapping.getNullPolicy().compositeObjectMarshal(xPathFragment, marshalRecord, object, session, namespaceResolver);
        }
        Descriptor descriptor = (Descriptor)xmlCompositeCollectionMapping.getReferenceDescriptor();
        if(descriptor == null){
        	descriptor = (Descriptor) session.getDescriptor(value.getClass());
        }else if(descriptor.hasInheritance()){
        	Class objectValueClass = value.getClass();
        	if(!(objectValueClass == descriptor.getJavaClass())){
        		descriptor = (Descriptor) session.getDescriptor(objectValueClass);
        	}
        }
        
        UnmarshalKeepAsElementPolicy keepAsElementPolicy = xmlCompositeCollectionMapping.getKeepAsElementPolicy();
        if (null != keepAsElementPolicy && (keepAsElementPolicy.isKeepUnknownAsElement() || keepAsElementPolicy.isKeepAllAsElement()) && value instanceof org.w3c.dom.Node) {
            marshalRecord.node((org.w3c.dom.Node) value, marshalRecord.getNamespaceResolver());
            return true;
        }
        
        if(descriptor != null){                    
            marshalRecord.beforeContainmentMarshal(value);

            ObjectBuilder objectBuilder = (ObjectBuilder)descriptor.getObjectBuilder();

            CoreAttributeGroup group = marshalRecord.getCurrentAttributeGroup();
            CoreAttributeGroup nestedGroup = XMLRecord.DEFAULT_ATTRIBUTE_GROUP;
            CoreAttributeItem item = group.getItem(getMapping().getAttributeName());
            if(item != null) {
                if(item.getGroups() != null) {
                    nestedGroup = item.getGroup(descriptor.getJavaClass());
                } 
                if(nestedGroup == null) {
                    nestedGroup = item.getGroup() == null?XMLRecord.DEFAULT_ATTRIBUTE_GROUP:item.getGroup();
                }
            }
            marshalRecord.pushAttributeGroup(nestedGroup);
            
            xPathNode.startElement(marshalRecord, xPathFragment, object, session, namespaceResolver, objectBuilder, value);
            if (isNil) {
            	marshalRecord.nilSimple(namespaceResolver);
            }

            List extraNamespaces = objectBuilder.addExtraNamespacesToNamespaceResolver(descriptor, marshalRecord, session,true, false);
            writeExtraNamespaces(extraNamespaces, marshalRecord, session);

            marshalRecord.addXsiTypeAndClassIndicatorIfRequired(descriptor, (Descriptor) xmlCompositeCollectionMapping.getReferenceDescriptor(), (Field)xmlCompositeCollectionMapping.getField(), false);

            objectBuilder.buildRow(marshalRecord, value, session, marshaller, xPathFragment);
            marshalRecord.afterContainmentMarshal(object, value);
            marshalRecord.popAttributeGroup();
            marshalRecord.endElement(xPathFragment, namespaceResolver);
            marshalRecord.removeExtraNamespacesFromNamespaceResolver(extraNamespaces, session);    
        } else {            
            if(Constants.UNKNOWN_OR_TRANSIENT_CLASS.equals(xmlCompositeCollectionMapping.getReferenceClassName())){                
                throw XMLMarshalException.descriptorNotFoundInProject(value.getClass().getName());                            
            }
            xPathNode.startElement(marshalRecord, xPathFragment, object, session, namespaceResolver, null, value);
            
            QName schemaType = ((Field) xmlCompositeCollectionMapping.getField()).getSchemaTypeForValue(value, session);
            updateNamespaces(schemaType, marshalRecord,((Field)xmlCompositeCollectionMapping.getField()));
            marshalRecord.characters(schemaType, value, null, false);            
            marshalRecord.endElement(xPathFragment, namespaceResolver);
        }
        return true;
    }

    public CompositeCollectionMapping getMapping() {
        return xmlCompositeCollectionMapping;
    }

    protected void setOrAddAttributeValue(UnmarshalRecord unmarshalRecord, Object value, XPathFragment xPathFragment, Object collection){
        unmarshalRecord.addAttributeValue(this, value, collection);            	
    }

    public boolean getReuseContainer() {
        return xmlCompositeCollectionMapping.getReuseContainer();
    }
    
    /**
     *  INTERNAL:
     *  Used to track the index of the corresponding containerInstance in the containerInstances Object[] on UnmarshalRecord 
     */  
    public void setIndex(int index){
    	this.index = index;
    }
    
    /**
     * INTERNAL:
     * Set to track the index of the corresponding containerInstance in the containerInstances Object[] on UnmarshalRecord
     * Set during TreeObjectBuilder initialization 
     */
    public int getIndex(){
    	return index;
    }

    /**
     * INTERNAL
     * Return true if an empty container should be set on the object if there
     * is no presence of the collection in the XML document.
     * @since EclipseLink 2.3.3
     */
    public boolean isDefaultEmptyContainer() {
        return xmlCompositeCollectionMapping.isDefaultEmptyContainer();
    }

    @Override
    public boolean isWrapperAllowedAsCollectionName() {
        return true;
    }

}