/*
 * 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 myservice.mynamespace.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Link;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.ODataApplicationException;

public class TransactionalEntityManager {
  
  private Map<String, List<Entity>> entities = new HashMap<String, List<Entity>>();
  private Map<String, List<Entity>> backupEntities = new HashMap<String, List<Entity>>();
  private Map<String, IdentityHashMap<Entity, Entity>> copyMap = new HashMap<String, IdentityHashMap<Entity, Entity>>();
  private boolean isInTransaction = false;
  private Edm edm;
  
  public TransactionalEntityManager(final Edm edm) {
    this.edm = edm;
  }

  public List<Entity> getEntityCollection(final String entitySetName) {
    if(!entities.containsKey(entitySetName)) {
      entities.put(entitySetName, new ArrayList<Entity>());
    }
    
    return entities.get(entitySetName);
  }
  
  public void beginTransaction() throws ODataApplicationException {
    if(!isInTransaction) {
      isInTransaction = true;
      copyCurrentState();
    } else {
      throw new ODataApplicationException("Transaction already in progress", 
          HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ENGLISH);
    }
  }
  
  public void rollbackTransaction() throws ODataApplicationException {
    if(isInTransaction) {
      entities = backupEntities;
      backupEntities = new HashMap<String, List<Entity>>();
      isInTransaction = false;
    } else {
      throw new ODataApplicationException("No transaction in progress", 
          HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ENGLISH);
    }
  }
  
  public void commitTransaction() throws ODataApplicationException {
    if(isInTransaction) {
      backupEntities.clear();
      isInTransaction = false;
    } else {
      throw new ODataApplicationException("No transaction in progress", 
          HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ENGLISH);
    }
  }
  
  private void copyCurrentState() {
    copyMap.clear();
    backupEntities.clear();
    
    for(final String entitySetName : entities.keySet()) {
      final List<Entity> entityList = entities.get(entitySetName);
      backupEntities.put(entitySetName, new ArrayList<Entity>());
      final List<Entity> backupEntityList = backupEntities.get(entitySetName);
      
      for(final Entity entity : entityList) {
        final EdmEntitySet entitySet = edm.getEntityContainer().getEntitySet(entitySetName);
        backupEntityList.add(copyEntityRecursively(entitySet, entity));
      }
    }
  }
  
  private Entity copyEntityRecursively(final EdmEntitySet edmEntitySet, final Entity entity) {
    // Check if entity is already copied
    if(containsEntityInCopyMap(edmEntitySet.getName(), entity)) {
      return getEntityFromCopyMap(edmEntitySet.getName(), entity);
    } else {
      final Entity newEntity = copyEntity(entity);
      addEntityToCopyMap(edmEntitySet.getName(), entity, newEntity);
      
      // Create nested entities recursively
      for(final Link link : entity.getNavigationLinks()) {
        newEntity.getNavigationLinks().add(copyLink(edmEntitySet, link));
      }
      
      return newEntity;
    }
  }

  private Link copyLink(final EdmEntitySet edmEntitySet, final Link link) {
    final Link newLink = new Link();
    newLink.setBindingLink(link.getBindingLink());
    newLink.setBindingLinks(new ArrayList<String>(link.getBindingLinks()));
    newLink.setHref(link.getHref());
    newLink.setMediaETag(link.getMediaETag());
    newLink.setRel(link.getRel());
    newLink.setTitle(link.getTitle());
    newLink.setType(link.getType());
    
    // Single navigation link
    if(link.getInlineEntity() != null) {
      final EdmEntitySet linkedEdmEntitySet = (EdmEntitySet) edmEntitySet.getRelatedBindingTarget(link.getTitle());
      newLink.setInlineEntity(copyEntityRecursively(linkedEdmEntitySet, link.getInlineEntity()));
    }      
    
    // Collection navigation link
    if(link.getInlineEntitySet() != null) {
      final EdmEntitySet linkedEdmEntitySet = (EdmEntitySet) edmEntitySet.getRelatedBindingTarget(link.getTitle());
      final EntityCollection inlineEntitySet = link.getInlineEntitySet();
      final EntityCollection newInlineEntitySet = new EntityCollection();
      newInlineEntitySet.setBaseURI(inlineEntitySet.getBaseURI());
      newInlineEntitySet.setCount(inlineEntitySet.getCount());
      newInlineEntitySet.setDeltaLink(inlineEntitySet.getDeltaLink());
      newInlineEntitySet.setId(inlineEntitySet.getId());
      newInlineEntitySet.setNext(inlineEntitySet.getNext());
      
      for(final Entity inlineEntity : inlineEntitySet.getEntities()) {
        newInlineEntitySet.getEntities().add(copyEntityRecursively(linkedEdmEntitySet, inlineEntity));
      }
      
      newLink.setInlineEntitySet(newInlineEntitySet);
    }
    
    return newLink;
  }

  private Entity copyEntity(final Entity entity) {
    final Entity newEntity = new Entity();
    newEntity.setBaseURI(entity.getBaseURI());
    newEntity.setEditLink(entity.getEditLink());
    newEntity.setETag(entity.getETag());
    newEntity.setId(entity.getId());
    newEntity.setMediaContentSource(entity.getMediaContentSource());
    newEntity.setMediaContentType(entity.getMediaContentType());
    newEntity.setSelfLink(entity.getSelfLink());
    newEntity.setMediaETag(entity.getMediaETag());
    newEntity.setType(entity.getType());
    newEntity.getProperties().addAll(entity.getProperties());
    
    return newEntity;
  }

  private void addEntityToCopyMap(final String entitySetName, final Entity srcEntity, final Entity destEntity) {
    if(!copyMap.containsKey(entitySetName)) {
      copyMap.put(entitySetName, new IdentityHashMap<Entity, Entity>());
    }
    
    copyMap.get(entitySetName).put(srcEntity, destEntity);
  }
  
  private boolean containsEntityInCopyMap(final String entitySetName, final Entity srcEntity) {
    return getEntityFromCopyMap(entitySetName, srcEntity) != null;
  }
  
  private Entity getEntityFromCopyMap(final String entitySetName, final Entity srcEntity) {
    if(!copyMap.containsKey(entitySetName)) {
      return null;
    }
    
    return copyMap.get(entitySetName).get(srcEntity);
  }
}
