/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.olingo.odata2.client.core.ep.serializer;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmCustomizableFeedMappings;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.edm.EdmFacets;
import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
import org.apache.olingo.odata2.api.edm.EdmMapping;
import org.apache.olingo.odata2.api.edm.EdmMultiplicity;
import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
import org.apache.olingo.odata2.api.edm.EdmSimpleType;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
import org.apache.olingo.odata2.api.edm.EdmTargetPath;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.client.api.ep.Entity;
import org.apache.olingo.odata2.client.api.ep.EntityCollection;
import org.apache.olingo.odata2.client.api.ep.EntityCollectionSerializerProperties;
import org.apache.olingo.odata2.client.api.ep.EntitySerializerProperties;
import org.apache.olingo.odata2.core.commons.ContentType;
import org.apache.olingo.odata2.core.commons.Encoder;
import org.apache.olingo.odata2.core.edm.EdmDateTimeOffset;
import org.apache.olingo.odata2.core.ep.EntityProviderProducerException;
import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo;
import org.apache.olingo.odata2.core.ep.util.FormatXml;

/**
 * Serializes an ATOM entry.
 * 
 */
public class AtomEntryEntitySerializer {
  private final EntitySerializerProperties properties;
  private static final String VALUE = "/$value";

  /**
   * 
   * @param properties
   */
  public AtomEntryEntitySerializer(final EntitySerializerProperties properties) {
    this.properties = properties == null ? EntitySerializerProperties.serviceRoot(null).build() : properties;
  }

  /**
   * This serializes the xml payload entry
   * @param writer
   * @param eia
   * @param data
   * @param isRootElement
   * @param isFeedPart
   * @throws EntityProviderException
   */
  public void append(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Entity data,
      final boolean isRootElement, final boolean isFeedPart) throws EntityProviderException {

    try {
      if (properties.getServiceRoot() == null) {
        throw new EntityProviderProducerException(EntityProviderException.MANDATORY_WRITE_PROPERTY);
      }
      writer.writeStartElement(FormatXml.ATOM_ENTRY);

      if (isRootElement) {
        writer.writeDefaultNamespace(Edm.NAMESPACE_ATOM_2005);
        writer.writeNamespace(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08);
        writer.writeNamespace(Edm.PREFIX_D, Edm.NAMESPACE_D_2007_08);
      }
      if (!isFeedPart) {
        writer.writeAttribute(Edm.PREFIX_XML, Edm.NAMESPACE_XML_1998, FormatXml.XML_BASE, properties.getServiceRoot()
            .toASCIIString());
      }

      String selfLink = null;
      if (properties.isIncludeMetadata()) {
        // write all atom infos (mandatory and optional)
        selfLink = createSelfLink(eia, data.getProperties(), null, properties.isKeyAutoGenerated(), false);
        appendAtomMandatoryParts(writer, eia, data.getProperties());
        appendAtomOptionalParts(writer, eia, data.getProperties());
        appendAtomEditLink(writer, eia, selfLink);
        if (eia.getEntityType().hasStream()) {
          appendAtomContentLink(writer, eia, data.getProperties(), selfLink);
        }
      }

      appendNavigationLinks(writer, eia, data);
      appendCustomProperties(writer, eia, data.getProperties());

      if (eia.getEntityType().hasStream()) {
        if (properties.isIncludeMetadata()) {
          appendAtomContentPart(writer, eia, data.getProperties(), selfLink);
        }
        appendProperties(writer, eia, data.getProperties());
      } else {
        writer.writeStartElement(FormatXml.ATOM_CONTENT);
        writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_XML.toString());
        appendProperties(writer, eia, data.getProperties());
        writer.writeEndElement();
      }
      writer.writeEndElement();
      writer.flush();

    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    } catch (EdmException e) {
      throw new EntityProviderProducerException(e.getMessageReference(), e);
    } catch (URISyntaxException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

  @SuppressWarnings("unchecked")
  private void appendNavigationLinks(final XMLStreamWriter writer, //NOSONAR
      final EntityInfoAggregator eia,
      final Entity data) 
      throws EntityProviderException, EdmException, URISyntaxException, XMLStreamException { 

    for (Entry<String, Object> entry : data.getNavigations().entrySet()) {
      final EntityInfoAggregator targetEntityInfo = EntityInfoAggregator.create(
          eia.getEntitySet().getRelatedEntitySet(
              (EdmNavigationProperty) eia.getEntityType().getProperty(entry.getKey())));
      final boolean isFeed =
          (eia.getNavigationPropertyInfo(entry.getKey()).getMultiplicity() == EdmMultiplicity.MANY);
      if (entry.getValue() == null) {
        throw new EntityProviderProducerException(EntityProviderProducerException.NULL_VALUE);
      } else if (entry.getValue() instanceof Map) {
        Map<String, Object> navigationKeyMap = (Map<String, Object>) entry.getValue();
        if (navigationKeyMap != null && !navigationKeyMap.isEmpty()) {
          appendAtomNavigationLink(writer, createSelfLink(targetEntityInfo, navigationKeyMap, null, 
              properties.isKeyAutoGenerated(), false), entry.getKey(), isFeed);
          writer.writeEndElement();
        }
      } else if (entry.getValue() instanceof Entity) {
        Entity navigationEntity = (Entity) entry.getValue();
        Map<String, Object> navigationKeyMap = navigationEntity.getProperties();
        if (navigationKeyMap != null && !navigationKeyMap.isEmpty()) {
          String navigationPropertyName = entry.getKey();
          String selfLink = createSelfLink(eia, data.getProperties(), navigationPropertyName,
              properties.isKeyAutoGenerated(), false);
          appendNavigationLink(writer, selfLink, navigationPropertyName);

          writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_ENTRY.toString());
          appendInlineEntry(writer, navigationPropertyName, eia, data);
          writer.writeEndElement();

        }
      } else if (entry.getValue() instanceof EntityCollection) {
        String navigationPropertyName = entry.getKey();
        String selfLink = createSelfLink(eia, data.getProperties(), navigationPropertyName, 
            properties.isKeyAutoGenerated(), false);
        if (!((EntityCollection) entry.getValue()).getEntities().isEmpty()) {
          appendNavigationLink(writer, selfLink, navigationPropertyName);

          writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_FEED.toString());
          appendInlineFeed(writer, navigationPropertyName, eia, data);
          writer.writeEndElement();
        }

      } else{
        throw new EntityProviderProducerException(EntityProviderProducerException.INCORRECT_NAVIGATION_TYPE);
        
      }
    }
  }

  private void appendNavigationLink(XMLStreamWriter writer, String selfLink, String navigationPropertyName)
      throws XMLStreamException {

    writer.writeStartElement(FormatXml.ATOM_LINK);
    writer.writeAttribute(FormatXml.ATOM_HREF, selfLink);
    writer.writeAttribute(FormatXml.ATOM_REL, Edm.NAMESPACE_REL_2007_08 + navigationPropertyName);
    writer.writeAttribute(FormatXml.ATOM_TITLE, navigationPropertyName);
  }

  private void appendCustomProperties(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data) throws EntityProviderException {
    List<String> noneSyndicationTargetPaths = eia.getNoneSyndicationTargetPathNames();
    for (String tpName : noneSyndicationTargetPaths) {
      EntityPropertyInfo info = eia.getTargetPathInfo(tpName);
      final String name = info.getName();
      XmlPropertyEntitySerializer aps = new XmlPropertyEntitySerializer(properties);
      aps.appendCustomProperty(writer, name, info, data.get(name));
    }
  }

  private void appendAtomNavigationLink(final XMLStreamWriter writer, final String target,
      final String navigationPropertyName, final boolean isFeed) 
          throws EntityProviderException, EdmException, URISyntaxException { //NOSONAR
    try {
      writer.writeStartElement(FormatXml.ATOM_LINK);
      writer.writeAttribute(FormatXml.ATOM_HREF, target);
      writer.writeAttribute(FormatXml.ATOM_REL, Edm.NAMESPACE_REL_2007_08 + navigationPropertyName);
      writer.writeAttribute(FormatXml.ATOM_TITLE, navigationPropertyName);
      if (isFeed) {
        writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_FEED.toString());
      } else {
        writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_ENTRY.toString());
      }
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

  private void appendInlineFeed(final XMLStreamWriter writer, final String navigationPropertyName,
      final EntityInfoAggregator eia, final Entity data)
      throws EntityProviderException, XMLStreamException, EdmException {

    if (eia.getNavigationPropertyNames().contains(navigationPropertyName) && 
        data != null && data.getNavigations().containsKey(navigationPropertyName)) {

        EdmNavigationProperty navProp = (EdmNavigationProperty) eia.getEntityType().getProperty(navigationPropertyName);

        if (navProp == null) {
          throw new EntityProviderProducerException(EntityProviderException.EXPANDNOTSUPPORTED);
        }
        EntityCollection inlineData;
        inlineData = (EntityCollection) data.getNavigation(navigationPropertyName);
        if (inlineData == null) {
          inlineData = new EntityCollection();
        }

        if (inlineData.getEntities().isEmpty()) {
          return;
        }
        writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);

        EntityCollectionSerializerProperties inlineProperties = inlineData.getCollectionProperties() == null
            ? EntityCollectionSerializerProperties.serviceRoot(data.getWriteProperties().getServiceRoot()).build()
            : inlineData.getCollectionProperties();
        EdmEntitySet inlineEntitySet = eia.getEntitySet().getRelatedEntitySet(navProp);
        AtomFeedSerializer inlineFeedProducer = new AtomFeedSerializer(inlineProperties);
        inlineData.setCollectionProperties(inlineProperties);
        EntityInfoAggregator inlineEia =
            EntityInfoAggregator.create(inlineEntitySet, null);
        inlineFeedProducer.append(writer, inlineEia, inlineData, true);

        writer.writeEndElement();

    }
  }

  private void appendInlineEntry(final XMLStreamWriter writer, final String navigationPropertyName,
      final EntityInfoAggregator eia, final Entity data) throws EntityProviderException,
      XMLStreamException, EdmException {

    if (data.getNavigations() != null && data.getNavigations().containsKey(navigationPropertyName)) {

      EdmNavigationProperty navProp = (EdmNavigationProperty) eia.getEntityType().getProperty(navigationPropertyName);

      Entity inlineData = (Entity) data.getNavigation(navigationPropertyName);

      if ((inlineData == null) || inlineData.getProperties().size() == 0) {
        return;
      }

      writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);
      if (inlineData != null && !(inlineData.getProperties().isEmpty())) {
        inlineData.setWriteProperties(inlineData.getWriteProperties() == null ? data.getWriteProperties() : inlineData
            .getWriteProperties());
        EdmEntitySet inlineEntitySet = eia.getEntitySet().getRelatedEntitySet(navProp);
        AtomEntryEntitySerializer inlineProducer = new AtomEntryEntitySerializer(inlineData.getWriteProperties());
        EntityInfoAggregator inlineEia =
            EntityInfoAggregator.create(inlineEntitySet, null);
        inlineProducer.append(writer, inlineEia, inlineData, false, false);
      }

      writer.writeEndElement();
    }
  }


  private void appendAtomEditLink(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final String selfLink) throws EntityProviderException {
    try {
      writer.writeStartElement(FormatXml.ATOM_LINK);
      writer.writeAttribute(FormatXml.ATOM_HREF, selfLink);
      writer.writeAttribute(FormatXml.ATOM_REL, Edm.LINK_REL_EDIT);
      writer.writeAttribute(FormatXml.ATOM_TITLE, eia.getEntityType().getName());
      writer.writeEndElement();
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    } catch (EdmException e) {
      throw new EntityProviderProducerException(e.getMessageReference(), e);
    }
  }

  private void appendAtomContentLink(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data, final String selfLink) throws EntityProviderException, EdmException {
    try {
      String mediaResourceMimeType = null;
      EdmMapping entityTypeMapping = eia.getEntityType().getMapping();
      if (entityTypeMapping != null) {
        String mediaResourceMimeTypeKey = entityTypeMapping.getMediaResourceMimeTypeKey();
        if (mediaResourceMimeTypeKey != null) {
          mediaResourceMimeType = (String) data.get(mediaResourceMimeTypeKey);
        }
      }
      if (mediaResourceMimeType == null) {
        mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
      }

      writer.writeStartElement(FormatXml.ATOM_LINK);
      writer.writeAttribute(FormatXml.ATOM_HREF, selfLink + VALUE);
      writer.writeAttribute(FormatXml.ATOM_REL, Edm.LINK_REL_EDIT_MEDIA);
      writer.writeAttribute(FormatXml.ATOM_TYPE, mediaResourceMimeType);
      writer.writeEndElement();
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

  private void appendAtomContentPart(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data, final String selfLink) throws EntityProviderException, EdmException {
    try {

      EdmMapping entityTypeMapping = eia.getEntityType().getMapping();
      String self = null;
      String mediaResourceMimeType = null;

      if (entityTypeMapping != null) {
        String mediaResourceSourceKey = entityTypeMapping.getMediaResourceSourceKey();
        if (mediaResourceSourceKey != null) {
          self = (String) data.get(mediaResourceSourceKey);
        }
        if (self == null) {
          self = selfLink + VALUE;
        }
        String mediaResourceMimeTypeKey = entityTypeMapping.getMediaResourceMimeTypeKey();
        if (mediaResourceMimeTypeKey != null) {
          mediaResourceMimeType = (String) data.get(mediaResourceMimeTypeKey);
        }
        if (mediaResourceMimeType == null) {
          mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
        }
      } else {
        self = selfLink + VALUE;
        mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
      }

      writer.writeStartElement(FormatXml.ATOM_CONTENT);
      writer.writeAttribute(FormatXml.ATOM_TYPE, mediaResourceMimeType);
      writer.writeAttribute(FormatXml.ATOM_SRC, self);
      writer.writeEndElement();
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

  private void appendAtomMandatoryParts(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data) throws EntityProviderException {
    try {
      writer.writeStartElement(FormatXml.ATOM_ID);
      String idlocation = properties.getServiceRoot().toASCIIString() + createSelfLink(
          eia, data, null, properties.isKeyAutoGenerated(), true);;
      writer.writeCharacters(idlocation);
      writer.writeEndElement();

      writer.writeStartElement(FormatXml.ATOM_TITLE);
      writer.writeAttribute(FormatXml.ATOM_TYPE, FormatXml.ATOM_TEXT);
      EntityPropertyInfo titleInfo = eia.getTargetPathInfo(EdmTargetPath.SYNDICATION_TITLE);
      if (titleInfo != null) {
        EdmSimpleType st = (EdmSimpleType) titleInfo.getType();
        Object object = data.get(titleInfo.getName());
        String title = null;
        try { //NOSONAR
          title = st.valueToString(object, EdmLiteralKind.DEFAULT, titleInfo.getFacets());
        } catch (EdmSimpleTypeException e) {
          throw new EntityProviderProducerException(EdmSimpleTypeException.getMessageReference(
              e.getMessageReference()).updateContent(e.getMessageReference().getContent(), titleInfo.getName()), e);
        }
        if (title != null) {
          writer.writeCharacters(title);
        }
      } else {
        writer.writeCharacters(eia.getEntitySetName());
      }
      writer.writeEndElement();

      writer.writeStartElement(FormatXml.ATOM_UPDATED);

      writer.writeCharacters(getUpdatedString(eia, data));

      writer.writeEndElement();
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    } catch (EdmSimpleTypeException e) {
      throw new EntityProviderProducerException(e.getMessageReference(), e);
    }
  }

  String getUpdatedString(final EntityInfoAggregator eia, final Map<String, Object> data)
      throws EdmSimpleTypeException, EntityProviderProducerException {
    Object updateDate = null;
    EdmFacets updateFacets = null;
    EntityPropertyInfo updatedInfo = eia.getTargetPathInfo(EdmTargetPath.SYNDICATION_UPDATED);
    if (updatedInfo != null) {
      updateDate = data.get(updatedInfo.getName());
      if (updateDate != null) {
        updateFacets = updatedInfo.getFacets();
      }
    }
    if (updateDate == null) {
      updateDate = new Date();
    }
    try {
      return EdmDateTimeOffset.getInstance().valueToString(updateDate, EdmLiteralKind.DEFAULT, updateFacets);
    } catch (final EdmSimpleTypeException e) {
      throw new EntityProviderProducerException(
          EdmSimpleTypeException.getMessageReference(e.getMessageReference()).
          updateContent(e.getMessageReference().getContent(),
              updatedInfo == null ? null : updatedInfo.getName()), e);
    }
  }

  private String getTargetPathValue(final EntityInfoAggregator eia, final String targetPath,
      final Map<String, Object> data) throws EntityProviderException {
    EntityPropertyInfo info = null;
    try {
      info = eia.getTargetPathInfo(targetPath);
      if (info != null) {
        EdmSimpleType type = (EdmSimpleType) info.getType();
        Object value = data.get(info.getName());
        return type.valueToString(value, EdmLiteralKind.DEFAULT, info.getFacets());
      }
      return null;
    } catch (final EdmSimpleTypeException e) {
      throw new EntityProviderProducerException(
          EdmSimpleTypeException.getMessageReference(e.getMessageReference()).
          updateContent(e.getMessageReference().getContent(), info.getName()), e);
    }
  }

  private void appendAtomOptionalParts(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data) throws EntityProviderException {
    try {
      String authorEmail = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_AUTHOREMAIL, data);
      String authorName = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_AUTHORNAME, data);
      String authorUri = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_AUTHORURI, data);
      if (authorEmail != null || authorName != null || authorUri != null) {
        writer.writeStartElement(FormatXml.ATOM_AUTHOR);
        appendAtomOptionalPart(writer, FormatXml.ATOM_AUTHOR_NAME, authorName, false);
        appendAtomOptionalPart(writer, FormatXml.ATOM_AUTHOR_EMAIL, authorEmail, false);
        appendAtomOptionalPart(writer, FormatXml.ATOM_AUTHOR_URI, authorUri, false);
        writer.writeEndElement();
      }

      String summary = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_SUMMARY, data);
      appendAtomOptionalPart(writer, FormatXml.ATOM_SUMMARY, summary, true);

      String contributorName = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_CONTRIBUTORNAME, data);
      String contributorEmail = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_CONTRIBUTOREMAIL, data);
      String contributorUri = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_CONTRIBUTORURI, data);
      if (contributorEmail != null || contributorName != null || contributorUri != null) {
        writer.writeStartElement(FormatXml.ATOM_CONTRIBUTOR);
        appendAtomOptionalPart(writer, FormatXml.ATOM_CONTRIBUTOR_NAME, contributorName, false);
        appendAtomOptionalPart(writer, FormatXml.ATOM_CONTRIBUTOR_EMAIL, contributorEmail, false);
        appendAtomOptionalPart(writer, FormatXml.ATOM_CONTRIBUTOR_URI, contributorUri, false);
        writer.writeEndElement();
      }

      String rights = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_RIGHTS, data);
      appendAtomOptionalPart(writer, FormatXml.ATOM_RIGHTS, rights, true);
      String published = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_PUBLISHED, data);
      appendAtomOptionalPart(writer, FormatXml.ATOM_PUBLISHED, published, false);

      String term = eia.getEntityType().getNamespace() + Edm.DELIMITER + eia.getEntityType().getName();
      writer.writeStartElement(FormatXml.ATOM_CATEGORY);
      writer.writeAttribute(FormatXml.ATOM_CATEGORY_TERM, term);
      writer.writeAttribute(FormatXml.ATOM_CATEGORY_SCHEME, Edm.NAMESPACE_SCHEME_2007_08);
      writer.writeEndElement();
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    } catch (EdmException e) {
      throw new EntityProviderProducerException(e.getMessageReference(), e);
    }
  }

  private void appendAtomOptionalPart(final XMLStreamWriter writer, final String name, final String value,
      final boolean writeType) throws EntityProviderException {
    try {
      if (value != null) {
        writer.writeStartElement(name);
        if (writeType) {
          writer.writeAttribute(FormatXml.ATOM_TYPE, FormatXml.ATOM_TEXT);
        }
        writer.writeCharacters(value);
        writer.writeEndElement();
      }
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

  static String createSelfLink(final EntityInfoAggregator eia, final Map<String, Object> data, final String extension, 
      boolean isKeyAutoGenerated, boolean isIdTag)
      throws EntityProviderException {
    StringBuilder sb = new StringBuilder();
    if (!eia.isDefaultEntityContainer()) {
      sb.append(Encoder.encode(eia.getEntityContainerName())).append(Edm.DELIMITER);
    }
    sb.append(Encoder.encode(eia.getEntitySetName()));

    String keyValue = createEntryKey(eia, data, isKeyAutoGenerated);
    if (isIdTag && isKeyAutoGenerated && "".equals(keyValue) && keyValue.length() == 0) {
      sb.append(extension == null ? "" : ("/" + extension));
    } else {
      sb.append("(").append(keyValue).append(")").
      append(extension == null ? "" : ("/" + extension));
    }
    return sb.toString();
  }

  private static String createEntryKey(final EntityInfoAggregator entityInfo, final Map<String, Object> data, 
      boolean isKeyAutoGenerated)
      throws EntityProviderException {
    final List<EntityPropertyInfo> keyPropertyInfos = entityInfo.getKeyPropertyInfos();

    StringBuilder keys = new StringBuilder();
    for (final EntityPropertyInfo keyPropertyInfo : keyPropertyInfos) {
      if (keys.length() > 0) {
        keys.append(',');
      }

      final String name = keyPropertyInfo.getName();
      if (keyPropertyInfos.size() > 1) {
        keys.append(Encoder.encode(name)).append('=');
      }

      final EdmSimpleType type = (EdmSimpleType) keyPropertyInfo.getType();
      try {
        String keyValue = null;
        if (isKeyAutoGenerated && data.get(name) == null) {
          // Every time default values for the key is generated
          // if it is not there and auto generation has to be done by the server
          Object value = fetchDefaultValue(type.getDefaultType());
          keyValue = Encoder.encode(type.valueToString(value, EdmLiteralKind.URI,
              keyPropertyInfo.getFacets()));
        } else {
          keyValue = Encoder.encode(type.valueToString(data.get(name), EdmLiteralKind.URI,
              keyPropertyInfo.getFacets()));
        }
        keys.append(keyValue);
      } catch (final EdmSimpleTypeException e) {
        throw new EntityProviderProducerException(
            EdmSimpleTypeException.getMessageReference(e.getMessageReference()).
            updateContent(e.getMessageReference().getContent(), name), e);
      }
    }

    return keys.toString();
  }

  private static Object fetchDefaultValue(Class<?> edmType) throws EntityProviderProducerException {
    if (edmType == boolean.class || edmType == Boolean.class) {
        return false;
      } else if (edmType == String.class) {
        return "A";
      } else if (edmType == byte.class || edmType == Byte.class) {
        return new Byte(Byte.parseByte("0"));
      } else if (edmType == char.class) {
        return new Character('A');
      } else if (edmType == short.class || edmType == Short.class) {
        return new Short(Short.parseShort("0"));
      } else if (edmType == int.class || edmType == Integer.class) {
        return new Integer(0);
      } else if (edmType == long.class || edmType == Long.class) {
        return new Long(0L);
      } else if (edmType == float.class || edmType == Float.class) {
        return new Float(0.0f);
      } else if (edmType == double.class || edmType == Double.class) {
        return new Double(0.0d);
      } else if (edmType == BigDecimal.class) {
        return BigDecimal.valueOf(0.0);
      } else if (edmType == UUID.class) {
        return UUID.fromString("00000000-0000-0000-0000-000000000000");
      } else if (edmType == Timestamp.class) {
        return new Timestamp(Calendar.getInstance().getTimeInMillis());
      } else if (edmType == Calendar.class) {
        return Calendar.getInstance();
      } else if (edmType == byte[].class || edmType == Byte[].class) {
        return new byte[0];
      } else if (edmType == Date.class) {
        return new Date();
      } else if (edmType == BigInteger.class) {
        return new BigInteger("0");
      } else if (edmType == Time.class) {
        return new Time(0L);
      } else {
        throw new EntityProviderProducerException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED);
      }
    }
  
  private void appendProperties(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data) throws EntityProviderException {
    try {
      {
        if (!data.isEmpty() && data.size() > 0) {
          writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_PROPERTIES);
          for (String propertyName : eia.getPropertyNames()) {
            if (data.containsKey(propertyName)) {
              appendPropertyNameValue(writer, eia, data, propertyName);
            }
          }
          writer.writeEndElement();
        }
      }
    } catch (XMLStreamException e) {
      throw new EntityProviderProducerException(EntityProviderException.COMMON, e);
    }
  }

  /**
   * @param writer
   * @param eia
   * @param data
   * @param propertyName
   * @throws EntityProviderException
   */
  private void appendPropertyNameValue(final XMLStreamWriter writer, final EntityInfoAggregator eia,
      final Map<String, Object> data, String propertyName) throws EntityProviderException {
    EntityPropertyInfo propertyInfo = eia.getPropertyInfo(propertyName);
    if (isNotMappedViaCustomMapping(propertyInfo)) {
      Object value = data.get(propertyName);
      XmlPropertyEntitySerializer aps = new XmlPropertyEntitySerializer(properties);
      aps.append(writer, propertyInfo.getName(), propertyInfo, value);
    }
  }

  private boolean isNotMappedViaCustomMapping(final EntityPropertyInfo propertyInfo) {
    EdmCustomizableFeedMappings customMapping = propertyInfo.getCustomMapping();
    if (customMapping != null && customMapping.isFcKeepInContent() != null) {
      return customMapping.isFcKeepInContent();
    }
    return true;
  }
}
