//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.library.edit.command;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;

/**
 * This command is used to change the features of multiple objects in
 * a single batch.
 * 
 * @author Phong Nguyen Le
 * @since  1.0
 */
public class BatchCommand extends AbstractCommand {
	private Map objectToOldFeatureValuesMap;
	private Map objectToNewFeatureValuesMap;
	private boolean clear;
	
	/**
	 * @param clear if true will clear the many-valued feature before adding new values 
	 */
	public BatchCommand(boolean clear) {
		super();
		this.clear = clear;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.AbstractCommand#canExecute()
	 */
	public boolean canExecute() {
		return objectToNewFeatureValuesMap != null && !objectToNewFeatureValuesMap.isEmpty();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.Command#execute()
	 */
	public void execute() {
		if(objectToNewFeatureValuesMap == null || objectToNewFeatureValuesMap.isEmpty()) {
			return;
		}
		
		if(objectToOldFeatureValuesMap == null) {
			objectToOldFeatureValuesMap = new HashMap();
		}

		for (Iterator iter = objectToNewFeatureValuesMap.entrySet()
				.iterator(); iter.hasNext();) {
			Map.Entry entry = (Map.Entry) iter.next();
			EObject eObj = (EObject) entry.getKey();
			Map featureMap = (Map) entry.getValue();
			for (Iterator iterator = featureMap.entrySet().iterator(); iterator
					.hasNext();) {
				entry = (Map.Entry) iterator.next();
				EStructuralFeature feature = (EStructuralFeature) entry
						.getKey();
				if (feature.isMany()) {
					if(clear) {
						eObj.eSet(feature, entry.getValue());
					}
					else {
						((List) eObj.eGet(feature)).addAll((Collection) entry.getValue());
					}
				} else {
					Object oldValue = eObj.eGet(feature);
					if (oldValue != entry.getValue()) {
						saveOldFeatureValue(eObj, feature);
						eObj.eSet(feature, entry.getValue());
					}
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.Command#redo()
	 */
	public void redo() {
		execute();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.AbstractCommand#undo()
	 */
	public void undo() {
		if (objectToNewFeatureValuesMap != null && !objectToNewFeatureValuesMap.isEmpty()) {
			for (Iterator iter = objectToNewFeatureValuesMap.entrySet()
					.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				EObject eObj = (EObject) entry.getKey();
				Map featureMap = (Map) entry.getValue();
				for (Iterator iterator = featureMap.entrySet().iterator(); iterator
						.hasNext();) {
					entry = (Map.Entry) iterator.next();
					EStructuralFeature feature = (EStructuralFeature) entry
							.getKey();
					if (feature.isMany()) {
						((List) eObj.eGet(feature))
								.removeAll((Collection) entry.getValue());
					}
				}
			}			
		}

		// restore old value
		//
		if (objectToOldFeatureValuesMap != null && !objectToOldFeatureValuesMap.isEmpty()) {
			for (Iterator iter = objectToOldFeatureValuesMap.entrySet()
					.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				EObject eObj = (EObject) entry.getKey();
				Map featureMap = (Map) entry.getValue();
				for (Iterator iterator = featureMap.entrySet().iterator(); iterator
						.hasNext();) {
					entry = (Map.Entry) iterator.next();
					EStructuralFeature feature = (EStructuralFeature) entry
							.getKey();
					eObj.eSet(feature, entry.getValue());
				}
			}
			objectToOldFeatureValuesMap.clear();
		}
	}
	
	public void addFeatureValue(EObject obj, EStructuralFeature feature, Object value) {
		addFeatureValue(getObjectToNewFeatureValuesMap(), obj, feature, value);
	}
	
	public void addFeatureValues(EObject obj, EStructuralFeature feature, Collection values) {
		addFeatureValues(getObjectToNewFeatureValuesMap(), obj, feature, values);
	}

	public Map getObjectToNewFeatureValuesMap() {
		if(objectToNewFeatureValuesMap == null) {
			objectToNewFeatureValuesMap = new HashMap();
		}
		return objectToNewFeatureValuesMap;
	}
	
	protected void saveOldFeatureValue(EObject eObj,
			EStructuralFeature feature) {
		Object value = eObj.eGet(feature);
		if(feature.isMany()) {
			value = new ArrayList((List)value);
		}
		Map featureMap = (Map) objectToOldFeatureValuesMap.get(eObj);
		if (featureMap == null) {
			featureMap = new HashMap();
			objectToOldFeatureValuesMap.put(eObj, featureMap);
		}
		featureMap.put(feature, value);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.emf.common.command.AbstractCommand#dispose()
	 */
	public void dispose() {
		if(objectToNewFeatureValuesMap != null) {
			objectToNewFeatureValuesMap.clear();
			objectToNewFeatureValuesMap = null;
		}
		
		if(objectToOldFeatureValuesMap != null) {
			objectToOldFeatureValuesMap.clear();
			objectToOldFeatureValuesMap = null;
		}
	}

	public static void addFeatureValue(Map descriptorToNewFeatureValuesMap,
			EObject object, EStructuralFeature feature, Object value) {
		Map featureMap = (Map) descriptorToNewFeatureValuesMap.get(object);
		if (featureMap == null) {
			featureMap = new HashMap();
			descriptorToNewFeatureValuesMap.put(object, featureMap);
		}
		if (feature.isMany()) {
			List list = (List) featureMap.get(feature);
			if (list == null) {
				list = new ArrayList();
				featureMap.put(feature, list);
			}
			list.add(value);
		} else {
			featureMap.put(feature, value);
		}
	}

	public static void addFeatureValues(Map descriptorToNewFeatureValuesMap,
			EObject object, EStructuralFeature feature, Collection values) {
		Map featureMap = (Map) descriptorToNewFeatureValuesMap.get(object);
		if (featureMap == null) {
			featureMap = new HashMap();
			descriptorToNewFeatureValuesMap.put(object, featureMap);
		}
		List list = (List) featureMap.get(feature);
		if (list == null) {
			list = new ArrayList();
			featureMap.put(feature, list);
		}
		list.addAll(values);
	}
}
