/*
 * 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.oxm.record;

import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;

import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.Namespace;
import org.eclipse.persistence.internal.oxm.XPathQName;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.record.AbstractMarshalRecord;
import org.eclipse.persistence.internal.oxm.record.AbstractMarshalRecordImpl;
import org.eclipse.persistence.internal.oxm.record.AbstractUnmarshalRecord;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLField;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.oxm.XMLUnmarshaller;
import org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * PUBLIC:
 * Provides a Record/Map API on an XML DOM element.
 */
public abstract class XMLRecord extends AbstractRecord implements AbstractMarshalRecord<AbstractSession, DatabaseField, XMLMarshaller, NamespaceResolver>, AbstractUnmarshalRecord<AbstractSession, DatabaseField, XMLUnmarshaller> {
    protected XMLUnmarshaller unmarshaller;
    private DocumentPreservationPolicy docPresPolicy;
    protected Object currentObject;
    protected AbstractSession session;

    protected boolean hasCustomNamespaceMapper;
    protected boolean equalNamespaceResolvers = false;

    private AbstractMarshalRecord<AbstractSession, DatabaseField, XMLMarshaller, NamespaceResolver> abstractMarshalRecord;

    /**
     * INTERNAL:
     * Nil: This is used to indicate that this field represents xsi:nil="true"
     */
    public static final XMLRecord.Nil NIL = org.eclipse.persistence.internal.oxm.record.XMLRecord.NIL;

    public XMLRecord() {
        super(null, null, 0);
        abstractMarshalRecord = new AbstractMarshalRecordImpl(this);
        // Required for subclasses.
    }

    /**
     * PUBLIC:
     * Add the field-value pair to the row.
     */
    @Override
    public Object get(String key) {
        return get(new XMLField(key));
    }

    /**
     * PUBLIC:
     * Add the field-value pair to the row.
     */
    @Override
    public Object put(String key, Object value) {
        return put(new XMLField(key), value);
    }


    /**
     * Marshal an attribute for the give namespaceURI, localName, preifx and value
     * @param namespaceURI
     * @param localName
     * @param prefix
     * @param value
     */
    public void attributeWithoutQName(String namespaceURI, String localName, String prefix, String value){
        String qualifiedName = localName;
        if(prefix != null && prefix.length() >0){
            qualifiedName = prefix + getNamespaceSeparator() + qualifiedName;
        }
        attribute(namespaceURI, localName, qualifiedName, value);
    }

    /**
     * Marshal an attribute for the give namespaceURI, localName, qualifiedName and value
     * @param namespaceURI
     * @param localName
     * @param qName
     * @param value
     */
    public void attribute(String namespaceURI, String localName, String qName, String value){
        XMLField xmlField = new XMLField(XMLConstants.ATTRIBUTE +qName);
        xmlField.setNamespaceResolver(getNamespaceResolver());
        xmlField.getLastXPathFragment().setNamespaceURI(namespaceURI);
        add(xmlField, value);
    }

    /**
     * Marshal a namespace declaration for the given prefix and url
     * @param prefix
     * @param namespaceURI
     */
    public void namespaceDeclaration(String prefix, String namespaceURI){

        String existingPrefix = getNamespaceResolver().resolveNamespaceURI(namespaceURI);
        if(existingPrefix == null || (existingPrefix != null && !existingPrefix.equals(XMLConstants.EMPTY_STRING) && !existingPrefix.equals(prefix))){
            XMLField xmlField = new XMLField("@" + javax.xml.XMLConstants.XMLNS_ATTRIBUTE + XMLConstants.COLON + prefix);
            xmlField.setNamespaceResolver(getNamespaceResolver());
            xmlField.getXPathFragment().setNamespaceURI(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
            add(xmlField, namespaceURI);
        }
    }

    /**
     * PUBLIC:
     * Get the local name of the context root element.
     */
    public abstract String getLocalName();

    /**
     * PUBLIC:
     *  Get the namespace URI for the context root element.
     */
    public abstract String getNamespaceURI();

    /**
     * PUBLIC:
     * Clear the sub-nodes of the DOM.
     */
    public abstract void clear();

    /**
     * PUBLIC:
     * Return the document.
     */
    public abstract Document getDocument();

    /**
     * PUBLIC:
     * Check if the value is contained in the row.
     */
    public boolean contains(Object value) {
        return values().contains(value);
    }

    /**
    * PUBLIC:
    * Return the DOM.
    */
    public abstract Node getDOM();

    /**
     * Return the XML string representation of the DOM.
     */
    public abstract String transformToXML();

    /**
     * INTERNAL:
     * Convert a DatabaseField to an XMLField
     */
    protected XMLField convertToXMLField(DatabaseField databaseField) {
        try {
            return (XMLField)databaseField;
        } catch (ClassCastException ex) {
            return new XMLField(databaseField.getName());
        }
    }

    protected List<XMLField> convertToXMLField(List<DatabaseField> databaseFields) {
        ArrayList<XMLField> xmlFields = new ArrayList(databaseFields.size());
        for(DatabaseField next:databaseFields) {
            try {
                xmlFields.add((XMLField)next);
            } catch(ClassCastException ex) {
                xmlFields.add(new XMLField(next.getName()));
            }
        }
        return xmlFields;
    }

    /**
     * INTERNAL:
     * Retrieve the value for the field. If missing null is returned.
     */
    public Object get(DatabaseField key) {
        return getIndicatingNoEntry(key);
    }
    /**
     * INTERNAL:
     * Retrieve the value for the field name.
     */
    public Object getIndicatingNoEntry(String fieldName) {
        return getIndicatingNoEntry(new XMLField(fieldName));
    }

    @Override
    public String resolveNamespacePrefix(String prefix) {
        return abstractMarshalRecord.resolveNamespacePrefix(prefix);
    }

    /**
     * INTERNAL:
     */
    @Override
    public XMLMarshaller getMarshaller() {
        return abstractMarshalRecord.getMarshaller();
    }

    /**
     * INTERNAL:
     */
    @Override
    public void setMarshaller(XMLMarshaller marshaller) {
        abstractMarshalRecord.setMarshaller(marshaller);

    }

    /**
     * INTERNAL:
     */
    public XMLUnmarshaller getUnmarshaller() {
        return unmarshaller;
    }

    /**
     * INTERNAL:
     */
    public void setUnmarshaller(XMLUnmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }

    public void setDocPresPolicy(DocumentPreservationPolicy policy) {
        this.docPresPolicy = policy;
    }

    public DocumentPreservationPolicy getDocPresPolicy() {
        return docPresPolicy;
    }
    /**
     * INTERNAL:
     */
    @Override
    public Object getOwningObject() {
        return abstractMarshalRecord.getOwningObject();
    }

    /**
     * INTERNAL:
     */
    @Override
    public void setOwningObject(Object owningObject) {
        abstractMarshalRecord.setOwningObject(owningObject);
    }

    /**
     * INTERNAL:
     */
    public Object getCurrentObject() {
        return currentObject;
    }

    /**
     * INTERNAL:
     */
    public void setCurrentObject(Object obj) {
        this.currentObject = obj;
    }
    /**
     * INTERNAL:
     */
    @Override
    public XPathQName getLeafElementType() {
        return abstractMarshalRecord.getLeafElementType();
    }
    /**
     * INTERNAL:
     */
    @Override
    public void setLeafElementType(XPathQName leafElementType) {
        abstractMarshalRecord.setLeafElementType(leafElementType);
    }

    /**
     * INTERNAL:
     */
    @Override
    public void setLeafElementType(QName leafElementType) {
        abstractMarshalRecord.setLeafElementType(leafElementType);
    }

    public void setNamespaceResolver(NamespaceResolver nr) {
        abstractMarshalRecord.setNamespaceResolver(nr);
    }

    public NamespaceResolver getNamespaceResolver() {
        return abstractMarshalRecord.getNamespaceResolver();
    }

    public AbstractSession getSession() {
        return session;
    }

    public void setSession(AbstractSession session) {
        this.session = session;
    }

    public void setEqualNamespaceResolvers(boolean equalNRs) {
        this.equalNamespaceResolvers = equalNRs;
    }

    public boolean hasEqualNamespaceResolvers() {
        return equalNamespaceResolvers;
    }

    @Override
    public boolean isXOPPackage() {
        return abstractMarshalRecord.isXOPPackage();
    }

    @Override
    public void setXOPPackage(boolean isXOPPackage) {
        abstractMarshalRecord.setXOPPackage(isXOPPackage);
    }

    /**
     * INTERNAL:
     * Determine if namespaces will be considered during marshal/unmarshal operations.
     * @since 2.4
     */
    @Override
    public boolean isNamespaceAware() {
        return abstractMarshalRecord.isNamespaceAware();
    }

    /**
     * INTERNAL:
     * The character used to separate the prefix and uri portions when namespaces are present
     * @since 2.4
     */
    public char getNamespaceSeparator(){
        return XMLConstants.COLON;
    }

    public boolean hasCustomNamespaceMapper() {
        return hasCustomNamespaceMapper;
    }

    public void setCustomNamespaceMapper(boolean customNamespaceMapper) {
        this.hasCustomNamespaceMapper = customNamespaceMapper;
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    @Override
    public List<Namespace> addExtraNamespacesToNamespaceResolver(Descriptor descriptor, CoreAbstractSession session, boolean allowOverride, boolean ignoreEqualResolvers) {
        return abstractMarshalRecord.addExtraNamespacesToNamespaceResolver(descriptor, session, allowOverride, ignoreEqualResolvers);
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    @Override
    public boolean addXsiTypeAndClassIndicatorIfRequired(Descriptor descriptor, Descriptor referenceDescriptor, Field xmlField, boolean isRootElement) {
        return abstractMarshalRecord.addXsiTypeAndClassIndicatorIfRequired(descriptor, referenceDescriptor, xmlField, isRootElement);
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    @Override
   public boolean addXsiTypeAndClassIndicatorIfRequired(Descriptor descriptor, Descriptor referenceDescriptor, Field xmlField,
           Object originalObject, Object obj, boolean wasXMLRoot, boolean isRootElement) {
       return abstractMarshalRecord.addXsiTypeAndClassIndicatorIfRequired(descriptor, referenceDescriptor, xmlField, originalObject, obj, wasXMLRoot, isRootElement);
   }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    @Override
    public void removeExtraNamespacesFromNamespaceResolver(List<Namespace> extraNamespaces, CoreAbstractSession session) {
        abstractMarshalRecord.removeExtraNamespacesFromNamespaceResolver(extraNamespaces, session);
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    @Override
    public void writeXsiTypeAttribute(Descriptor descriptor, String typeUri,  String  typeLocal, String typePrefix, boolean addToNamespaceResolver) {
        abstractMarshalRecord.writeXsiTypeAttribute(descriptor, typeUri, typeLocal, typePrefix, addToNamespaceResolver);
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    @Override
    public void writeXsiTypeAttribute(Descriptor xmlDescriptor, XMLSchemaReference xmlRef, boolean addToNamespaceResolver) {
        abstractMarshalRecord.writeXsiTypeAttribute(xmlDescriptor, xmlRef, addToNamespaceResolver);
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.6.0
     */
    @Override
    public ConversionManager getConversionManager() {
        if(null == session) {
            return null;
        } else {
            return (ConversionManager) session.getDatasourcePlatform().getConversionManager();
        }
    }

}
