/*
 * 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.Map;
import java.util.Map.Entry;

import org.eclipse.persistence.core.sessions.CoreSession;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.FragmentMapping;
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.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * INTERNAL:
 * <p><b>Purpose</b>: This is how the XML Fragment Collection Mapping is handled
 * when used with the TreeObjectBuilder.</p>
 * @author  mmacivor
 */
public class XMLFragmentMappingNodeValue extends MappingNodeValue implements NullCapableValue {
    private FragmentMapping xmlFragmentMapping;
    private boolean selfMapping;

    public XMLFragmentMappingNodeValue(FragmentMapping xmlFragmentMapping) {
        super();
        this.xmlFragmentMapping = xmlFragmentMapping;
        this.selfMapping = XPathFragment.SELF_XPATH.equals(xmlFragmentMapping.getXPath());
    }

    @Override
    public boolean isOwningNode(XPathFragment xPathFragment) {
        return xPathFragment.getNextFragment() == null;
    }

    @Override
    public void setNullValue(Object object, CoreSession session) {
        Object value = xmlFragmentMapping.getObjectValue(null, session);
        xmlFragmentMapping.setAttributeValueInObject(object, value);
    }

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

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

    @Override
    public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
        if (xmlFragmentMapping.isReadOnly()) {
            return false;
        }
        Object attributeValue = marshalContext.getAttributeValue(object, xmlFragmentMapping);
        return this.marshalSingleValue(xPathFragment, marshalRecord, object, attributeValue, session, namespaceResolver, marshalContext);
    }

    @Override
    public boolean marshalSelfAttributes(XPathFragment pathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver, Marshaller marshaller) {
        Node node = (Node) xmlFragmentMapping.getAttributeValueFromObject(object);
        NamedNodeMap attributes = node.getAttributes();
        if(null != attributes) {
            for(int x=0, attributesLength=attributes.getLength(); x<attributesLength; x++) {
                Node attribute = attributes.item(x);
                if(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attribute.getNamespaceURI())) {
                    String nsResolverPrefix = namespaceResolver.resolveNamespaceURI(attribute.getNodeValue());;
                    if(attribute.getLocalName().equals(nsResolverPrefix)) {
                        continue;
                    }
                }
                String namespaceURI = attribute.getNamespaceURI();
                String localName = attribute.getLocalName();
                String qualifiedName = localName;
                if(null != namespaceResolver) {
                    String prefix = namespaceResolver.resolveNamespaceURI(namespaceURI);
                    if(null != prefix) {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append(prefix);
                        stringBuilder.append(':');
                        stringBuilder.append(qualifiedName);
                        qualifiedName = stringBuilder.toString();
                    }
                }
                marshalRecord.attribute(namespaceURI, localName, qualifiedName, attribute.getNodeValue());
            }
        }
        return true;
    }

    @Override
    public boolean marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object attributeValue, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
        marshalRecord.openStartGroupingElements(namespaceResolver);
        if (!(attributeValue instanceof Node)) {
            return false;
        }
        Node nodeValue = (Node) attributeValue;
        if(selfMapping) {
            NodeList childNodes = nodeValue.getChildNodes();
            for(int x=0,childNodesLength=childNodes.getLength(); x<childNodesLength; x++) {
                Node node = childNodes.item(x);
                marshalRecord.node(node, namespaceResolver);
            }
        } else {
            marshalRecord.node((Node)attributeValue, namespaceResolver);
        }
        return true;
    }

    @Override
    public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) {
        unmarshalRecord.removeNullCapableValue(this);
        SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder();
        builder.setOwningRecord(unmarshalRecord);
        try {
            String namespaceURI = Constants.EMPTY_STRING;
            if (xPathFragment.getNamespaceURI() != null) {
                namespaceURI = xPathFragment.getNamespaceURI();
            }
            String qName = xPathFragment.getLocalName();
            if (xPathFragment.getPrefix() != null) {
                qName = xPathFragment.getPrefix() + Constants.COLON + qName;
            }
            if(!(unmarshalRecord.getPrefixesForFragment().isEmpty())) {
                for(Entry<String, String> next:((Map<String, String>) unmarshalRecord.getPrefixesForFragment()).entrySet()) {
                    builder.startPrefixMapping(next.getKey(), next.getValue());
                }
            }
            builder.startElement(namespaceURI, xPathFragment.getLocalName(), qName, atts);
            XMLReader xmlReader = unmarshalRecord.getXMLReader();
            xmlReader.setContentHandler(builder);
            xmlReader.setLexicalHandler(null);
        } catch (SAXException ex) {
            // Do nothing.
        }
        return true;
    }

    @Override
    public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) {
        unmarshalRecord.removeNullCapableValue(this);
        XPathFragment lastFrag = ((Field)xmlFragmentMapping.getField()).getLastXPathFragment();
        SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder();
        if (lastFrag.nameIsText()) {
            Object attributeValue = builder.buildTextNode(unmarshalRecord.getCharacters().toString());
            unmarshalRecord.resetStringBuffer();
            xmlFragmentMapping.setAttributeValueInObject(unmarshalRecord.getCurrentObject(), attributeValue);
        } else if (!lastFrag.isAttribute()) {
            Object value = builder.getNodes().remove(builder.getNodes().size() -1);
            unmarshalRecord.setAttributeValue(value, xmlFragmentMapping);
        }
    }

    @Override
    public void endSelfNodeValue(UnmarshalRecord unmarshalRecord, UnmarshalRecord selfRecord, Attributes atts) {
        this.endElement(XPathFragment.SELF_FRAGMENT, unmarshalRecord);
    }

    @Override
    public void attribute(UnmarshalRecord unmarshalRecord, String namespaceURI, String localName, String value) {
        unmarshalRecord.removeNullCapableValue(this);
        if(namespaceURI == null) {
            namespaceURI = Constants.EMPTY_STRING;
        }
        SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder();
        Object attributeValue = builder.buildAttributeNode(namespaceURI, localName, value);
        xmlFragmentMapping.setAttributeValueInObject(unmarshalRecord.getCurrentObject(), attributeValue);
    }

    @Override
    public FragmentMapping getMapping() {
        return xmlFragmentMapping;
    }

}
