/*
 * Copyright (c) 1998, 2018 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 v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.oxm;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.persistence.internal.core.helper.CoreField;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.mappings.BinaryDataMapping;
import org.eclipse.persistence.internal.oxm.mappings.ChoiceObjectMapping;
import org.eclipse.persistence.internal.oxm.mappings.CompositeObjectMapping;
import org.eclipse.persistence.internal.oxm.mappings.DirectMapping;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.mappings.ObjectReferenceMapping;
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.UnmarshalContext;
import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord;

import org.xml.sax.Attributes;

/**
 * INTERNAL:
 * <p><b>Purpose</b>: This is how the XML Choice Collection Mapping is
 * handled when used with the TreeObjectBuilder.</p>
 * @author mmacivor
 */

public class XMLChoiceObjectMappingNodeValue extends MappingNodeValue {

    private NodeValue choiceElementNodeValue;
    private Map<Class, NodeValue> choiceElementNodeValues;
    private ChoiceObjectMapping xmlChoiceMapping;
    //The first node value of the choice will be registered as a null capable value. If any
    //of the choice elements get hit, this needs to be removed as a null value.
    private XMLChoiceObjectMappingNodeValue nullCapableNodeValue;
    private Field xmlField;

    public XMLChoiceObjectMappingNodeValue(ChoiceObjectMapping mapping, Field xmlField) {
        this.xmlChoiceMapping = mapping;
        this.xmlField = xmlField;
        initializeNodeValue();
    }

    public boolean isOwningNode(XPathFragment xPathFragment) {
        return choiceElementNodeValue.isOwningNode(xPathFragment);
    }

    public void initializeNodeValue() {
        Mapping xmlMapping = (Mapping) xmlChoiceMapping.getChoiceElementMappings().get(xmlField);
        choiceElementNodeValue = getNodeValueForMapping(xmlMapping);
        //check for mappings to other classes with the same field
        for(Entry<Class, Mapping> entry: ((Map<Class, Mapping>)xmlChoiceMapping.getChoiceElementMappingsByClass()).entrySet()) {
            Field field = (Field) xmlChoiceMapping.getClassToFieldMappings().get(entry.getKey());
            if(field != null && field.equals(this.xmlField)) {
                Mapping mappingForClass = entry.getValue();
                if(mappingForClass != xmlMapping) {
                    if(this.choiceElementNodeValues == null) {
                        choiceElementNodeValues = new HashMap<Class, NodeValue>();
                    }
                    choiceElementNodeValues.put(entry.getKey(), getNodeValueForMapping(mappingForClass));
                }
            }
        }
    }

    private NodeValue getNodeValueForMapping(Mapping xmlMapping) {
        if(xmlMapping instanceof BinaryDataMapping){
            return new XMLBinaryDataMappingNodeValue((BinaryDataMapping)xmlMapping);
        } else if(xmlMapping instanceof DirectMapping) {
            return new XMLDirectMappingNodeValue((DirectMapping)xmlMapping);
        } else if(xmlMapping instanceof ObjectReferenceMapping) {
            return new XMLObjectReferenceMappingNodeValue((ObjectReferenceMapping)xmlMapping, xmlField);
        } else {
            return new XMLCompositeObjectMappingNodeValue((CompositeObjectMapping)xmlMapping);
        }
    }
    public void setNullCapableNodeValue(XMLChoiceObjectMappingNodeValue nodeValue) {
        this.nullCapableNodeValue = nodeValue;
    }

    public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver) {
        return this.marshal(xPathFragment, marshalRecord, object, session, namespaceResolver, ObjectMarshalContext.getInstance());
    }

    public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
        if(xmlChoiceMapping.isReadOnly()) {
            return false;
        }
        Object value = xmlChoiceMapping.getFieldValue(object, session, marshalRecord);
        return this.marshalSingleValue(xPathFragment, marshalRecord, object, value, session, namespaceResolver, marshalContext);
    }

    public boolean marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
        Class valueClass = null;
        if (value instanceof Root) {
            Root root = (Root)value;
            for(CoreField next: (List<CoreField>) this.xmlChoiceMapping.getFields()) {
                XPathFragment fragment = ((Field)next).getXPathFragment();
                while(fragment != null && !fragment.nameIsText) {
                    if(fragment.getNextFragment() == null || fragment.getHasText()) {
                        if(fragment.getLocalName().equals(root.getLocalName())) {
                            String fragUri = fragment.getNamespaceURI();
                            String namespaceUri = root.getNamespaceURI();
                            if((namespaceUri == null && fragUri == null) || (namespaceUri != null && fragUri != null && namespaceUri.equals(fragUri))) {
                                if(next == this.xmlField) {
                                    return this.choiceElementNodeValue.marshalSingleValue(xPathFragment, marshalRecord, object, value, session, namespaceResolver, marshalContext);
                                } else {
                                    //If this root is associated with another field, then return and let that NodeValue handle it
                                    return false;
                                }
                            }
                        }
                    }
                    fragment = fragment.getNextFragment();
                }
            }
            valueClass = root.getObject().getClass();
        }
        if (value != null) {
            if(valueClass == null) {
                valueClass = value.getClass();
            }
            Field fieldForClass = null;
            Class theClass = valueClass;
            while(theClass != null) {
                fieldForClass = (Field) xmlChoiceMapping.getClassToFieldMappings().get(valueClass);
                if(fieldForClass != null) {
                    break;
                }
                theClass = theClass.getSuperclass();
            }
            if (fieldForClass != null && fieldForClass.equals(this.xmlField)) {
                if(this.choiceElementNodeValues != null) {
                    NodeValue nodeValue = this.choiceElementNodeValues.get(theClass);
                    if(nodeValue != null) {
                        return nodeValue.marshalSingleValue(xPathFragment, marshalRecord, object, value, session, namespaceResolver, marshalContext);
                    }
                }
                return this.choiceElementNodeValue.marshalSingleValue(xPathFragment, marshalRecord, object, value, session, namespaceResolver, marshalContext);
            }
            List<Field> sourceFields = null;
            theClass = valueClass;
            while(theClass != null) {
                sourceFields = (List<Field>) xmlChoiceMapping.getClassToSourceFieldsMappings().get(theClass);
                if(sourceFields != null) {
                    break;
                }
                theClass = theClass.getSuperclass();
            }
            if (sourceFields != null && sourceFields.contains(this.xmlField)) {
                return this.choiceElementNodeValue.marshalSingleValue(xPathFragment, marshalRecord, object, value, session, namespaceResolver, marshalContext);
            }
        }
        return false;
    }

    public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) {
        if(null != xmlChoiceMapping.getConverter()) {
            UnmarshalContext unmarshalContext = unmarshalRecord.getUnmarshalContext();
            unmarshalRecord.setUnmarshalContext(new ChoiceUnmarshalContext(unmarshalContext, xmlChoiceMapping));
            this.choiceElementNodeValue.endElement(xPathFragment, unmarshalRecord);
            unmarshalRecord.setUnmarshalContext(unmarshalContext);
        } else {
            this.choiceElementNodeValue.endElement(xPathFragment, unmarshalRecord);
        }
    }

    public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) {
        return this.choiceElementNodeValue.startElement(xPathFragment, unmarshalRecord, atts);
    }

    public void setXPathNode(XPathNode xPathNode) {
        super.setXPathNode(xPathNode);
        this.choiceElementNodeValue.setXPathNode(xPathNode);
        if(this.choiceElementNodeValues != null) {
            for(NodeValue next:choiceElementNodeValues.values()) {
                next.setXPathNode(xPathNode);
            }
        }
    }

    /**
     * The underlying choice element node value will handle attributes.
     *
     */
    public void attribute(UnmarshalRecord unmarshalRecord, String URI, String localName, String value) {
        this.choiceElementNodeValue.attribute(unmarshalRecord, URI, localName, value);
    }

    @Override
    public Mapping getMapping() {
        return this.xmlChoiceMapping;
    }

    @Override
    public boolean isWhitespaceAware() {
        return choiceElementNodeValue.isWhitespaceAware();
    }
}
