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

import java.util.*;

import org.eclipse.persistence.internal.queries.ContainerPolicy;

/**
 * Change record used by DirectMapMapping.
 * Only needs to track unique keys added/removed.
 */
public class DirectMapChangeRecord extends DeferrableChangeRecord {
    protected HashMap addObjectsList;
    protected HashMap removeObjectsList;

    public DirectMapChangeRecord() {
        super();
    }

    public DirectMapChangeRecord(ObjectChangeSet owner) {
        this.owner = owner;
    }

    /**
     * Returns true if the change set has changes.
     */
    public boolean hasChanges() {
        return (!((this.addObjectsList == null || this.addObjectsList.isEmpty()) && ((this.removeObjectsList == null || this.removeObjectsList.isEmpty())))) || getOwner().isNew();
    }

    /**
     * This method will be used to merge one record into another.
     */
    public void mergeRecord(ChangeRecord mergeFromRecord, UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) {
        if (((DeferrableChangeRecord)mergeFromRecord).isDeferred()){
            if (this.hasChanges()){
                //merging into existing change record need to combine changes
                ((DeferrableChangeRecord)mergeFromRecord).getMapping().calculateDeferredChanges(mergeFromRecord, mergeToChangeSet.getSession());
            }else{
                this.isDeferred = true;
                this.originalCollection = ((DeferrableChangeRecord)mergeFromRecord).originalCollection;
                this.latestCollection = ((DeferrableChangeRecord)mergeFromRecord).latestCollection;
                return;
            }
        }
        Iterator addKeys = ((DirectMapChangeRecord)mergeFromRecord).getAddObjects().keySet().iterator();
        while (addKeys.hasNext()) {
            Object key = addKeys.next();

            if (!this.getAddObjects().containsKey(key)) {
                if (this.getRemoveObjects().containsKey(key)) {
                    this.getRemoveObjects().remove(key);
                } else {
                    this.getAddObjects().put(key, ((DirectMapChangeRecord)mergeFromRecord).getAddObjects().get(key));
                }
            }
        }

        Iterator removeKeys = ((DirectMapChangeRecord)mergeFromRecord).getRemoveObjects().keySet().iterator();
        while (removeKeys.hasNext()) {
            Object key = removeKeys.next();

            if (!this.getRemoveObjects().containsKey(key)) {
                if (this.getAddObjects().containsKey(key)) {
                    this.getAddObjects().remove(key);
                } else {
                    this.getRemoveObjects().put(key, ((DirectMapChangeRecord)mergeFromRecord).getRemoveObjects().get(key));
                }
            }
        }
    }

    /**
     * Adds the items that were added to the collection.
     */
    public void addAdditionChange(HashMap additions) {
        if (getAddObjects().size() == 0) {
            addObjectsList = additions;
            return;
        }

        for (Iterator i = additions.keySet().iterator(); i.hasNext(); ) {
            Object key = i.next();
            if (getAddObjects().containsKey(key)) {
                getAddObjects().put(key, additions.get(key));
            } else if (additions.get(key).equals(getAddObjects().get(key))) {
                getAddObjects().put(key, additions.get(key));
            }
        }
    }

    /**
     * Adds the items that were removed from the collection.
     */
    public void addRemoveChange(HashMap subtractions) {
        if (getRemoveObjects().size() == 0) {
            this.removeObjectsList = subtractions;
            return;
        }

        for (Iterator i = subtractions.keySet().iterator(); i.hasNext(); ) {
            Object key = i.next();
            if (!getRemoveObjects().containsKey(key)) {
                getRemoveObjects().put(key, subtractions.get(key));
            } else if (subtractions.get(key).equals(getRemoveObjects().get(key))) {
                getRemoveObjects().put(key, subtractions.get(key));
            }
        }
    }

    /**
     * Adds the items that were added to the collection.
     */
    public void addAdditionChange(Object key, Object value) {
        if ( getRemoveObjects().containsKey(key) ) {
            if ( value.equals(getRemoveObjects().get(key)) ) {
                getRemoveObjects().remove(key);
            }else {
                getAddObjects().put(key, value);
            }
        } else {
            getAddObjects().put(key, value);
        }
    }

    /**
     * Adds the items that were removed from the collection.
     */
    public void addRemoveChange(Object key, Object value) {
        //if an entry already exists in the remove it must remain untill added
        // as it contains the original removal.
        if (getAddObjects().containsKey(key)) {
            getAddObjects().remove(key);
        } else if (!getRemoveObjects().containsKey(key)) {
            getRemoveObjects().put(key, value);
        }
    }

    /**
     * Sets the added items list.
     */
    public void setAddObjects(HashMap addObjects) {
        this.addObjectsList = addObjects;
    }

    /**
     * Returns the added items list.
     */
    public HashMap getAddObjects() {
        if (addObjectsList == null) {
            addObjectsList = new HashMap();
        }
        return addObjectsList;
    }

    /**
     * Sets the removed items list.
     */
    public void setRemoveObjects(HashMap removeObjects) {
        this.removeObjectsList = removeObjects;
    }

    /**
     * Returns the removed items list.
     */
    public HashMap getRemoveObjects() {
        if (removeObjectsList == null) {
            removeObjectsList = new HashMap();
        }

        return removeObjectsList;
    }

    /**
     * This method will be used to update the objectsChangeSets references.
     */
    public void updateReferences(UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) {
        // Nothing for this record type to do as it does not reference any changesets.
    }

   /**
    * Recreates the original state of the collection.
    */
  public void internalRecreateOriginalCollection(Object currentMap, AbstractSession session) {
      ContainerPolicy cp = this.mapping.getContainerPolicy();
       if(this.removeObjectsList != null) {
           Iterator it = this.removeObjectsList.entrySet().iterator();
           while(it.hasNext()) {
               Map.Entry entry = (Map.Entry)it.next();
               cp.addInto(entry.getKey(), entry.getValue(), currentMap, session);
           }
       }
       if(this.addObjectsList != null) {
           Iterator it = this.addObjectsList.entrySet().iterator();
           while(it.hasNext()) {
               Map.Entry entry = (Map.Entry)it.next();
               cp.removeFrom(entry.getKey(), entry.getValue(), currentMap, session);
           }
       }
   }

  /**
   * Clears info about added / removed objects set by change tracker.
   */
  public void clearChanges() {
      if(this.removeObjectsList != null) {
          this.removeObjectsList.clear();
      }
      if(this.addObjectsList != null) {
          this.addObjectsList.clear();
      }
  }
}
