/*
 * Copyright 2015-2020 52°North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * Licensed 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.n52.svalbard.encode;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;

import org.apache.xmlbeans.XmlBoolean;
import org.apache.xmlbeans.XmlInteger;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlString;
import org.joda.time.DateTime;
import org.n52.janmayen.http.MediaType;
import org.n52.shetland.ogc.SupportedType;
import org.n52.shetland.ogc.gml.AbstractFeature;
import org.n52.shetland.ogc.gml.GenericMetaData;
import org.n52.shetland.ogc.gml.GmlConstants;
import org.n52.shetland.ogc.gml.time.Time;
import org.n52.shetland.ogc.gml.time.TimeInstant;
import org.n52.shetland.ogc.gml.time.TimePeriod;
import org.n52.shetland.ogc.om.MultiObservationValues;
import org.n52.shetland.ogc.om.ObservationStream;
import org.n52.shetland.ogc.om.OmCompositePhenomenon;
import org.n52.shetland.ogc.om.OmConstants;
import org.n52.shetland.ogc.om.OmObservableProperty;
import org.n52.shetland.ogc.om.OmObservation;
import org.n52.shetland.ogc.om.SingleObservationValue;
import org.n52.shetland.ogc.om.StreamingValue;
import org.n52.shetland.ogc.om.features.samplingFeatures.AbstractSamplingFeature;
import org.n52.shetland.ogc.om.values.BooleanValue;
import org.n52.shetland.ogc.om.values.CategoryValue;
import org.n52.shetland.ogc.om.values.CountValue;
import org.n52.shetland.ogc.om.values.GeometryValue;
import org.n52.shetland.ogc.om.values.QuantityValue;
import org.n52.shetland.ogc.om.values.TextValue;
import org.n52.shetland.ogc.ows.exception.OwsExceptionReport;
import org.n52.shetland.ogc.ows.extension.Extension;
import org.n52.shetland.ogc.ows.extension.Extensions;
import org.n52.shetland.ogc.sos.Sos1Constants;
import org.n52.shetland.ogc.sos.Sos2Constants;
import org.n52.shetland.ogc.sos.SosConstants;
import org.n52.shetland.ogc.sos.response.AbstractObservationResponse;
import org.n52.shetland.ogc.sos.response.GetObservationByIdResponse;
import org.n52.shetland.ogc.sos.response.GetObservationResponse;
import org.n52.shetland.ogc.swe.SweAbstractDataComponent;
import org.n52.shetland.ogc.swe.SweConstants;
import org.n52.shetland.ogc.swe.SweDataArray;
import org.n52.shetland.util.CollectionHelper;
import org.n52.shetland.util.OMHelper;
import org.n52.shetland.util.ReferencedEnvelope;
import org.n52.shetland.w3c.SchemaLocation;
import org.n52.svalbard.encode.exception.EncodingException;
import org.n52.svalbard.encode.exception.UnsupportedEncoderInputException;
import org.n52.svalbard.util.CodingHelper;
import org.n52.svalbard.util.GmlHelper;
import org.n52.svalbard.util.N52XmlHelper;
import org.n52.svalbard.util.SweHelper;
import org.n52.svalbard.util.XmlHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import net.opengis.om.x10.CategoryObservationDocument;
import net.opengis.om.x10.CategoryObservationType;
import net.opengis.om.x10.CountObservationDocument;
import net.opengis.om.x10.CountObservationType;
import net.opengis.om.x10.GeometryObservationDocument;
import net.opengis.om.x10.GeometryObservationType;
import net.opengis.om.x10.MeasurementDocument;
import net.opengis.om.x10.MeasurementType;
import net.opengis.om.x10.ObservationCollectionDocument;
import net.opengis.om.x10.ObservationCollectionType;
import net.opengis.om.x10.ObservationDocument;
import net.opengis.om.x10.ObservationPropertyType;
import net.opengis.om.x10.ObservationType;
import net.opengis.om.x10.TruthObservationDocument;
import net.opengis.om.x10.TruthObservationType;

/**
 * @since 1.0.0
 *
 */
public class OmEncoderv100
        extends
        AbstractXmlEncoder<XmlObject, Object>
        implements
        ObservationEncoder<XmlObject, Object> {

    private static final Logger LOGGER = LoggerFactory.getLogger(OmEncoderv100.class);

    private static final Set<SupportedType> SUPPORTED_TYPES = ImmutableSet.<SupportedType> builder()
            .add(OmConstants.OBS_TYPE_SWE_ARRAY_OBSERVATION_TYPE)
            // .add(OmConstants.OBS_TYPE_COMPLEX_OBSERVATION_TYPE)
            // .add(OmConstants.OBS_TYPE_GEOMETRY_OBSERVATION_TYPE)
            .add(OmConstants.OBS_TYPE_CATEGORY_OBSERVATION_TYPE).add(OmConstants.OBS_TYPE_COUNT_OBSERVATION_TYPE)
            .add(OmConstants.OBS_TYPE_MEASUREMENT_TYPE).add(OmConstants.OBS_TYPE_TEXT_OBSERVATION_TYPE)
            .add(OmConstants.OBS_TYPE_TRUTH_OBSERVATION_TYPE).build();

    // TODO: change to correct conformance class
    private static final Set<String> CONFORMANCE_CLASSES =
            ImmutableSet.of("http://www.opengis.net/spec/OMXML/1.0/conf/measurement",
                    "http://www.opengis.net/spec/OMXML/1.0/conf/categoryObservation",
                    "http://www.opengis.net/spec/OMXML/1.0/conf/countObservation",
                    "http://www.opengis.net/spec/OMXML/1.0/conf/truthObservation",
                    "http://www.opengis.net/spec/OMXML/1.0/conf/geometryObservation",
                    "http://www.opengis.net/spec/OMXML/1.0/conf/textObservation");

    private static final Map<String, Map<String, Set<String>>> SUPPORTED_RESPONSE_FORMATS =
            Collections.singletonMap(SosConstants.SOS, Collections.singletonMap(Sos1Constants.SERVICEVERSION,
                    (Set<String>) ImmutableSet.of(OmConstants.CONTENT_TYPE_OM.toString())));

    private static final Set<EncoderKey> ENCODER_KEYS = CollectionHelper.union(
            CodingHelper.encoderKeysForElements(OmConstants.NS_OM, OmObservation.class, GetObservationResponse.class,
                    GetObservationByIdResponse.class),
            CodingHelper.encoderKeysForElements(OmConstants.CONTENT_TYPE_OM.toString(), OmObservation.class,
                    GetObservationResponse.class, GetObservationByIdResponse.class));
    private static final String RESULT_TIME_ID_PREFIX = "resultTime_";
    private static final String OBSERVATION_ID_PREFIX = "o_";
    private SweHelper sweHelper;

    public OmEncoderv100() {
        LOGGER.debug("Encoder for the following keys initialized successfully: {}!",
                Joiner.on(", ").join(ENCODER_KEYS));
    }

    @Inject
    public void setSweHelper(SweHelper sweHelper) {
        this.sweHelper = sweHelper;
    }

    @Override
    public Set<EncoderKey> getKeys() {
        return Collections.unmodifiableSet(ENCODER_KEYS);
    }

    @Override
    public Set<SupportedType> getSupportedTypes() {
        return Collections.unmodifiableSet(SUPPORTED_TYPES);
    }

    @Override
    public Map<String, Set<SupportedType>> getSupportedResponseFormatObservationTypes() {
        return Collections.singletonMap(OmConstants.CONTENT_TYPE_OM.toString(), getSupportedTypes());
    }

    @Override
    public Set<String> getConformanceClasses(String service, String version) {
        if (SosConstants.SOS.equals(service) && Sos2Constants.SERVICEVERSION.equals(version)) {
            return Collections.unmodifiableSet(CONFORMANCE_CLASSES);
        }
        return Collections.emptySet();
    }

    @Override
    public void addNamespacePrefixToMap(Map<String, String> nameSpacePrefixMap) {
        nameSpacePrefixMap.put(OmConstants.NS_OM, OmConstants.NS_OM_PREFIX);
    }

    @Override
    public boolean isObservationAndMeasurmentV20Type() {
        return false;
    }

    @Override
    public Set<String> getSupportedResponseFormats(String service, String version) {
        if (SUPPORTED_RESPONSE_FORMATS.get(service) != null
                && SUPPORTED_RESPONSE_FORMATS.get(service).get(version) != null) {
            return SUPPORTED_RESPONSE_FORMATS.get(service).get(version);
        }
        return Collections.emptySet();
    }

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

    @Override
    public boolean supportsResultStreamingForMergedValues() {
        return false;
    }

    @Override
    public MediaType getContentType() {
        return OmConstants.CONTENT_TYPE_OM;
    }

    @Override
    public Set<SchemaLocation> getSchemaLocations() {
        return Sets.newHashSet(OmConstants.OM_100_SCHEMA_LOCATION);
    }

    @Override
    public XmlObject encode(Object element, EncodingContext additionalValues)
            throws EncodingException {
        XmlObject encodedObject = null;
        if (element instanceof OmObservation) {
            encodedObject = createObservation((OmObservation) element, additionalValues);
        } else if (element instanceof GetObservationResponse) {
            GetObservationResponse response = (GetObservationResponse) element;
            encodedObject =
                    createObservationCollection(response);
        } else if (element instanceof GetObservationByIdResponse) {
            GetObservationByIdResponse response = (GetObservationByIdResponse) element;
            encodedObject =
                    createObservationCollection(response);
        } else {
            throw new UnsupportedEncoderInputException(this, element);
        }
        XmlHelper.validateDocument(encodedObject, EncodingException::new);
        return encodedObject;
    }

    private XmlObject createObservation(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        String observationType = checkObservationType(sosObservation);
        if (null != observationType) {
            switch (observationType) {
                case OmConstants.OBS_TYPE_MEASUREMENT:
                    return createMeasurement(sosObservation, additionalValues);
                case OmConstants.OBS_TYPE_CATEGORY_OBSERVATION:
                    return createCategoryObservation(sosObservation, additionalValues);
                case OmConstants.OBS_TYPE_COUNT_OBSERVATION:
                    return createCountObservation(sosObservation, additionalValues);
                case OmConstants.OBS_TYPE_TRUTH_OBSERVATION:
                    return createTruthObservation(sosObservation, additionalValues);
                case OmConstants.OBS_TYPE_GEOMETRY_OBSERVATION:
                    return createGeometryObservation(sosObservation, additionalValues);
                default:
                    return createOmObservation(sosObservation, additionalValues);
            }
        } else {
            return createOmObservation(sosObservation, additionalValues);
        }
    }

    private String checkObservationType(OmObservation sosObservation)
            throws EncodingException {
        if (sosObservation.isSetResultType()) {
            return sosObservation.getResultType();
        } else if (sosObservation.getValue() instanceof SingleObservationValue) {
            SingleObservationValue<?> observationValue = (SingleObservationValue<?>) sosObservation.getValue();
            return OMHelper.getObservationTypeFor(observationValue.getValue());
        }
        return OmConstants.OBS_TYPE_OBSERVATION;
    }

    private XmlObject createObservationCollection(AbstractObservationResponse response)
            throws EncodingException {
        ObservationStream sosObservationCollectionIterable = response.getObservationCollection();
        String resultModel =  response.getResultModel();
        ObservationCollectionDocument xbObservationCollectionDoc =
                ObservationCollectionDocument.Factory.newInstance(getXmlOptions());
        ObservationCollectionType xbObservationCollection = xbObservationCollectionDoc.addNewObservationCollection();
        xbObservationCollection.setId(SosConstants.OBS_COL_ID_PREFIX + new DateTime().getMillis());
        if (response.hasExtensions()) {
            createMetadataProperty(xbObservationCollection, response.getExtensions());
        }
        if (sosObservationCollectionIterable != null) {
            List<OmObservation> sosObservationCollection = new LinkedList<>();
            try {
                sosObservationCollectionIterable.forEachRemaining(sosObservationCollection::add);
                ReferencedEnvelope sosEnvelope = getEnvelope(sosObservationCollection);
                if (sosEnvelope.isSetEnvelope()) {
                    Encoder<XmlObject, ReferencedEnvelope> envEncoder = getEncoder(GmlConstants.NS_GML, sosEnvelope);
                    xbObservationCollection.addNewBoundedBy().addNewEnvelope().set(envEncoder.encode(sosEnvelope));
                }
                for (OmObservation sosObservation : sosObservationCollection) {
                    String observationType = checkObservationType(sosObservation);
                    if (Strings.isNullOrEmpty(resultModel)
                            || (!Strings.isNullOrEmpty(resultModel) && observationType.equals(resultModel))) {
                        if (sosObservation.getValue() instanceof StreamingValue) {
                            StreamingValue<?> streamingValue = (StreamingValue<?>) sosObservation.getValue();
                            while (streamingValue.hasNext()) {
                                xbObservationCollection.addNewMember()
                                        .set(createObservation(streamingValue.next(), null));
                            }

                        } else {
                            xbObservationCollection.addNewMember().set(createObservation(sosObservation, null));
                        }
                    } else {
                        throw new EncodingException(
                                "The requested resultModel '%s' is invalid for the resulting observations!",
                                OMHelper.getEncodedResultModelFor(resultModel));
                    }
                }
            } catch (OwsExceptionReport owse) {
                throw new EncodingException(owse);
            }
        } else {
            ObservationPropertyType xbObservation = xbObservationCollection.addNewMember();
            xbObservation.setHref(GmlConstants.NIL_INAPPLICABLE);
        }
        XmlHelper.makeGmlIdsUnique(xbObservationCollectionDoc.getDomNode());
        N52XmlHelper.setSchemaLocationsToDocument(xbObservationCollectionDoc,
                Sets.newHashSet(N52XmlHelper.getSchemaLocationForSOS100(), N52XmlHelper.getSchemaLocationForOM100(),
                        N52XmlHelper.getSchemaLocationForSA100()));
        return xbObservationCollectionDoc;
    }

    private ReferencedEnvelope getEnvelope(List<OmObservation> sosObservationCollection) {
        ReferencedEnvelope sosEnvelope = new ReferencedEnvelope();
        sosObservationCollection.stream()
                .map(o -> (AbstractSamplingFeature) o.getObservationConstellation().getFeatureOfInterest())
                .forEach(f -> {
                    if (f.isSetGeometry()) {
                        sosEnvelope.setSrid(f.getGeometry().getSRID());
                        sosEnvelope.expandToInclude(f.getGeometry().getEnvelopeInternal());
                    }
                });
        return sosEnvelope;
    }

    private XmlObject createMeasurement(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        MeasurementDocument xbMeasurementDoc = MeasurementDocument.Factory.newInstance(getXmlOptions());
        MeasurementType xbObs = xbMeasurementDoc.addNewMeasurement();
        addValuesToObservation(xbObs, sosObservation, additionalValues);
        addSingleObservationToResult(xbObs.addNewResult(), sosObservation);
        return xbMeasurementDoc;
    }

    private XmlObject createCategoryObservation(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        CategoryObservationDocument xbCategoryObservationDoc =
                CategoryObservationDocument.Factory.newInstance(getXmlOptions());
        CategoryObservationType xbObs = xbCategoryObservationDoc.addNewCategoryObservation();
        addValuesToObservation(xbObs, sosObservation, additionalValues);
        addSingleObservationToResult(xbObs.addNewResult(), sosObservation);
        return xbCategoryObservationDoc;
    }

    private XmlObject createCountObservation(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        CountObservationDocument xbCountObservationDoc = CountObservationDocument.Factory.newInstance(getXmlOptions());
        CountObservationType xbObs = xbCountObservationDoc.addNewCountObservation();
        addValuesToObservation(xbObs, sosObservation, additionalValues);
        addSingleObservationToResult(xbObs.addNewResult(), sosObservation);
        return xbCountObservationDoc;
    }

    private XmlObject createTruthObservation(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        TruthObservationDocument xbTruthObservationDoc = TruthObservationDocument.Factory.newInstance(getXmlOptions());
        TruthObservationType xbObs = xbTruthObservationDoc.addNewTruthObservation();
        addValuesToObservation(xbObs, sosObservation, additionalValues);
        addSingleObservationToResult(xbObs.addNewResult(), sosObservation);
        return xbTruthObservationDoc;
    }

    private XmlObject createGeometryObservation(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        GeometryObservationDocument xbGeometryObservationDoc =
                GeometryObservationDocument.Factory.newInstance(getXmlOptions());
        GeometryObservationType xbObs = xbGeometryObservationDoc.addNewGeometryObservation();
        addValuesToObservation(xbObs, sosObservation, additionalValues);
        addSingleObservationToResult(xbObs.addNewResult(), sosObservation);
        return xbGeometryObservationDoc;
    }

    private XmlObject createOmObservation(OmObservation sosObservation, EncodingContext additionalValues)
            throws EncodingException {
        ObservationDocument xbObservationDoc = ObservationDocument.Factory.newInstance(getXmlOptions());
        ObservationType xbObs = xbObservationDoc.addNewObservation();
        List<OmObservableProperty> phenComponents = addValuesToObservation(xbObs, sosObservation, additionalValues);
        addResultToObservation(xbObs.addNewResult(), sosObservation, phenComponents);
        return xbObservationDoc;
    }

    private List<OmObservableProperty> addValuesToObservation(ObservationType xbObs, OmObservation sosObservation,
            EncodingContext additionalValues)
            throws EncodingException {
        xbObs.setId(OBSERVATION_ID_PREFIX + Long.toString(System.currentTimeMillis()));
        if (!sosObservation.isSetObservationID()) {
            sosObservation.setObservationID(xbObs.getId().replace(OBSERVATION_ID_PREFIX, ""));
        }
        String observationID = sosObservation.getObservationID();
        // set samplingTime
        Time samplingTime = sosObservation.getPhenomenonTime();
        if (samplingTime.getGmlId() == null) {
            samplingTime.setGmlId(OmConstants.PHENOMENON_TIME_NAME + "_" + observationID);
        }
        addSamplingTime(xbObs, samplingTime);
        // set resultTime
        addResultTime(xbObs, sosObservation);

        // set procedure
        xbObs.addNewProcedure().setHref(sosObservation.getObservationConstellation().getProcedure().getIdentifier());
        // set observedProperty (phenomenon)
        List<OmObservableProperty> phenComponents = null;
        if (sosObservation.getObservationConstellation().getObservableProperty() instanceof OmObservableProperty) {
            xbObs.addNewObservedProperty()
                    .setHref(sosObservation.getObservationConstellation().getObservableProperty().getIdentifier());
            phenComponents = new ArrayList<>(1);
            phenComponents
                    .add((OmObservableProperty) sosObservation.getObservationConstellation().getObservableProperty());
        } else if (sosObservation.getObservationConstellation()
                .getObservableProperty() instanceof OmCompositePhenomenon) {
            OmCompositePhenomenon compPhen =
                    (OmCompositePhenomenon) sosObservation.getObservationConstellation().getObservableProperty();
            xbObs.addNewObservedProperty().setHref(compPhen.getIdentifier());
            phenComponents = compPhen.getPhenomenonComponents();
        }
        // set feature
        addFeatureOfInterest(xbObs, sosObservation.getObservationConstellation().getFeatureOfInterest());
        return phenComponents;
    }

    private void addSamplingTime(ObservationType xbObservation, Time iTime)
            throws EncodingException {
        XmlObject xmlObject = encodeObjectToXml(GmlConstants.NS_GML, iTime);
        XmlObject substitution = xbObservation.addNewSamplingTime().addNewTimeObject()
                .substitute(GmlHelper.getGml311QnameForITime(iTime), xmlObject.schemaType());
        substitution.set(xmlObject);
    }

    private void addResultTime(ObservationType xbObs, OmObservation sosObservation)
            throws EncodingException {
        Time phenomenonTime = sosObservation.getPhenomenonTime();
        if (sosObservation.isSetResultTime()) {
            if (sosObservation.getResultTime().equals(phenomenonTime)) {
                xbObs.addNewResultTime().setHref("#".concat(phenomenonTime.getGmlId()));
            } else {
                TimeInstant resultTime = sosObservation.getResultTime();
                if (!resultTime.isSetGmlId()) {
                    resultTime.setGmlId(RESULT_TIME_ID_PREFIX.concat(sosObservation.getObservationID()));
                }
                addResultTime(xbObs, resultTime);
            }
        } else {
            if (phenomenonTime instanceof TimeInstant) {
                xbObs.addNewResultTime().setHref("#".concat(phenomenonTime.getGmlId()));
            } else if (phenomenonTime instanceof TimePeriod) {
                TimeInstant resultTime = new TimeInstant(((TimePeriod) sosObservation.getPhenomenonTime()).getEnd());
                resultTime.setGmlId(RESULT_TIME_ID_PREFIX + sosObservation.getObservationID());
                addResultTime(xbObs, resultTime);
            }
        }
    }

    private void addResultTime(ObservationType xbObs, TimeInstant iTime)
            throws EncodingException {
        XmlObject xmlObject = encodeObjectToXml(GmlConstants.NS_GML, iTime);
        XmlObject substitution = xbObs.addNewResultTime().addNewTimeObject()
                .substitute(GmlHelper.getGml311QnameForITime(iTime), xmlObject.schemaType());
        substitution.set(xmlObject);
    }

    private void addResultToObservation(XmlObject xbResult, OmObservation sosObservation,
            List<OmObservableProperty> phenComponents)
            throws EncodingException {
        // TODO if OM_SWEArrayObservation and get ResultEncoding and
        // ResultStructure exists,
        if (sosObservation.getValue() instanceof SingleObservationValue) {
            addSingleObservationToResult(xbResult, sosObservation);
        } else if (sosObservation.getValue() instanceof MultiObservationValues) {
            addMultiObservationValueToResult(xbResult, sosObservation);
        }
    }

    // FIXME String.equals(QName) !?
    private void addSingleObservationToResult(XmlObject xbResult, OmObservation sosObservation)
            throws EncodingException {
        String observationType = sosObservation.getObservationConstellation().getObservationType();
        SingleObservationValue<?> observationValue = (SingleObservationValue<?>) sosObservation.getValue();
        if (observationValue.getValue() instanceof QuantityValue) {
            QuantityValue quantityValue = (QuantityValue) observationValue.getValue();
            xbResult.set(encodeObjectToXml(GmlConstants.NS_GML, quantityValue));
        } else if (observationValue.getValue() instanceof CountValue) {
            CountValue countValue = (CountValue) observationValue.getValue();
            XmlInteger xbInteger = XmlInteger.Factory.newInstance(getXmlOptions());
            if (countValue.getValue() != null && countValue.getValue() != Integer.MIN_VALUE) {
                xbInteger.setBigIntegerValue(new BigInteger(countValue.getValue().toString()));
            } else {
                xbInteger.setNil();
            }
            xbResult.set(xbInteger);
        } else if (observationValue.getValue() instanceof TextValue) {
            TextValue textValue = (TextValue) observationValue.getValue();
            XmlString xbString = XmlString.Factory.newInstance(getXmlOptions());
            if (textValue.getValue() != null && !textValue.getValue().isEmpty()) {
                xbString.setStringValue(textValue.getValue());
            } else {
                xbString.setNil();
            }
            xbResult.set(xbString);
        } else if (observationValue.getValue() instanceof BooleanValue) {
            BooleanValue booleanValue = (BooleanValue) observationValue.getValue();
            XmlBoolean xbBoolean = XmlBoolean.Factory.newInstance(getXmlOptions());
            if (booleanValue.getValue() != null) {
                xbBoolean.setBooleanValue(booleanValue.getValue());
            } else {
                xbBoolean.setNil();
            }
            xbResult.set(xbBoolean);
        } else if (observationValue.getValue() instanceof CategoryValue) {
            CategoryValue categoryValue = (CategoryValue) observationValue.getValue();
            if (categoryValue.getValue() != null && !categoryValue.getValue().isEmpty()) {
                xbResult.set(encodeObjectToXml(GmlConstants.NS_GML, categoryValue, EncodingContext.of(
                        XmlBeansEncodingFlags.GMLID, SosConstants.OBS_ID_PREFIX + sosObservation.getObservationID())));
            } else {
                xbResult.setNil();
            }
        } else if (observationValue.getValue() instanceof GeometryValue) {
            GeometryValue geometryValue = (GeometryValue) observationValue.getValue();
            if (geometryValue.getValue() != null) {
                xbResult.set(encodeObjectToXml(GmlConstants.NS_GML, geometryValue.getValue(), EncodingContext.of(
                        XmlBeansEncodingFlags.GMLID, SosConstants.OBS_ID_PREFIX + sosObservation.getObservationID())));
            } else {
                xbResult.setNil();
            }
        } else if (OmConstants.OBS_TYPE_SWE_ARRAY_OBSERVATION.equals(observationType)
                || OmConstants.RESULT_MODEL_OBSERVATION.getLocalPart().equals(observationType)) {
            SweDataArray dataArray = sweHelper.createSosSweDataArray(sosObservation);
            xbResult.set(encodeObjectToXml(SweConstants.NS_SWE_101, dataArray,
                    EncodingContext.of(XmlBeansEncodingFlags.FOR_OBSERVATION)));
        }
    }

    private void addMultiObservationValueToResult(XmlObject xbResult, OmObservation sosObservation)
            throws EncodingException {
        SweDataArray dataArray = sweHelper.createSosSweDataArray(sosObservation);
        xbResult.set(encodeObjectToXml(SweConstants.NS_SWE_101, dataArray,
                EncodingContext.of(XmlBeansEncodingFlags.FOR_OBSERVATION)));
    }

    /**
     * Encodes a SosAbstractFeature to an SpatialSamplingFeature under
     * consideration of duplicated SpatialSamplingFeature in the XML document.
     *
     * @param observation
     *            XmlObject O&M observation
     * @param feature
     *            SOS observation
     *
     * @throws EncodingException
     *             if encoding of the feature fails
     */
    private void addFeatureOfInterest(ObservationType observation, AbstractFeature feature)
            throws EncodingException {
        EncodingContext ctx = EncodingContext.of(XmlBeansEncodingFlags.ENCODE, feature.getDefaultElementEncoding());
        XmlObject encodeObjectToXml = encodeObjectToXml(GmlConstants.NS_GML, feature, ctx);
        observation.addNewFeatureOfInterest().set(encodeObjectToXml);
    }

    private void createMetadataProperty(ObservationCollectionType xbObservationCollection, Extensions extensions)
            throws EncodingException {
        for (Extension<?> extension : extensions.getExtensions()) {
            if (extension.getValue() instanceof SweAbstractDataComponent) {
                xbObservationCollection.addNewMetaDataProperty()
                        .set(encodeGML311(new GenericMetaData(extension.getValue())));
            }
        }
    }

    protected XmlObject encodeGML311(Object o)
            throws EncodingException {
        return encodeObjectToXml(GmlConstants.NS_GML, o);
    }

    protected XmlObject encodeGML311(Object o, EncodingContext context)
            throws EncodingException {
        return encodeObjectToXml(GmlConstants.NS_GML, o, context);
    }
}