/**
 * File:    AspectEnrichedItemProviderAdapter.java
 * Created: 25.11.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2008-2010 Open Wide SA <www.openwide.fr>
 *
 * 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:
 *    Marc Dutoo, Open Wide SA 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.conf.edit.aspects.provider;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.command.CreateChildCommand;
import org.eclipse.emf.edit.command.CopyCommand.Helper;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.IChangeNotifier;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.jwt.we.conf.edit.aspects.provider.command.AspectCommandParameter;
import org.eclipse.jwt.we.conf.edit.aspects.provider.command.CopyCommandWithAspects;
import org.eclipse.jwt.we.conf.edit.aspects.provider.command.CreateChildAspectCommand;
import org.eclipse.jwt.we.conf.edit.aspects.provider.command.CreateChildCommandWithAspects;
import org.eclipse.jwt.we.conf.edit.aspects.provider.command.InitializeCopyCommandWithAspects;
import org.eclipse.jwt.we.conf.model.Aspect;
import org.eclipse.jwt.we.conf.model.AspectInstance;
import org.eclipse.jwt.we.conf.model.ConfModel;
import org.eclipse.jwt.we.conf.model.ConfPackage;
import org.eclipse.jwt.we.conf.model.aspects.AspectManager;
import org.eclipse.jwt.we.conf.model.aspects.factory.AspectFactory;
import org.eclipse.jwt.we.conf.model.plugin.ConfPlugin;


/**
 * Enriches the inheriting generated EMF ItemProvider with Aspect
 * features.
 * 
 * Those features are : New child aspect commands, Aspect behaviours
 * (through the AspectEventManager).
 * 
 * To enable it, set in the genmodel root the "Provider Root
 * Extends Class" property to this class
 * (i.e. org.eclipse.jwt.we.conf.aspects.provider.AspectEnrichedItemProviderAdapter).
 *  
 * @author mdutoo
 * @since 0.6
 */
public class AspectEnrichedItemProviderAdapter extends ItemProviderAdapter {
	
	protected boolean adapterFactoryInited = false;

	public AspectEnrichedItemProviderAdapter(AdapterFactory adapterFactory) {
		super(adapterFactory);
	}

	@Override
	public Object getBackground(Object object) {
		// TODO Auto-generated method stub
		return super.getBackground(object);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Collection<?> getChildren(Object object) {
		Collection children = super.getChildren(object);
		
		// let's add aspect instances as childs, so they will be visible ex. in the outline on the node
		// NB. can't use generics, else cast problem 
		if (object instanceof EObject) {
			EObject modelElement = (EObject) object;
			if (!adapterFactoryInited) {
				initAdapterFactory(modelElement);
			}
			List<AspectInstance> aspectInstances = AspectManager.INSTANCE.getAspectInstances(modelElement);
			children.addAll(aspectInstances);
		}
		return children;
	}

	protected synchronized void initAdapterFactory(EObject modelElement) {
		IChangeNotifier notifyingAdapterFactory = null;
		if (adapterFactory instanceof IChangeNotifier) {
			notifyingAdapterFactory = (IChangeNotifier) adapterFactory;
		}
		
		if (notifyingAdapterFactory != null) {
			/*
			// Adding aspectsEventManager TODO rather custom AspectComposedAdapterFactory ?
			synchronized (notifyingAdapterFactory) {
				// removing it first allows to add it whether or not it was there before
				notifyingAdapterFactory.removeListener(AspectEventManagerNotifyChangedListener.DEFAULT_INSTANCE);
				// adding it
				notifyingAdapterFactory.addListener(AspectEventManagerNotifyChangedListener.DEFAULT_INSTANCE);
				
				adapterFactoryInited = true;
			}
			*/

			/*
			synchronized (this) {
				// removing it first allows to add it whether or not it was there before
				this.removeListener(AspectEventManagerNotifyChangedListener.DEFAULT_INSTANCE);
				// adding it
				this.addListener(AspectEventManagerNotifyChangedListener.DEFAULT_INSTANCE);
				
				adapterFactoryInited = true;
			}
			*/
		}
	}

	@Override
	public void fireNotifyChanged(Notification notification) {
		super.fireNotifyChanged(notification);

		// notifying aspect event manager to have aspect enriched behaviour
		ConfPlugin.getDefaultAspectEventManager().notifyChanged(notification); // TODO not used anymore for onCreated, remove ?
	}

	public Collection<?> getNewChildDescriptors(Object object, EditingDomain editingDomain, Object sibling) {
		Collection<?> collectedNewChildDescriptors = super.getNewChildDescriptors(object, editingDomain, sibling);

		// Let's collect Aspect-specified new child descs.
		// This must be done after all other child descs have been collected, else ccastex in ItemProviderAdapter l. 828,
		// that's why we do it in getNewChildDesc and not in collectNewChildDesc. 
		// NB. it works with child extenders aspects 
		// TODO use EMF decorators rather than this ??
		this.collectNewChildAspectDescriptors(collectedNewChildDescriptors, object);
		
		return collectedNewChildDescriptors;
	}

	/**
	 * Collects new child descriptors that are implied by the conf's aspects
	 * for the given extensible element
	 * @param newChildDescriptors
	 * @param object
	 */
	@SuppressWarnings("unchecked")
	protected void collectNewChildAspectDescriptors(
			Collection newChildDescriptors, Object object) {
		if (object instanceof EObject) { // TODO better, with aspect enriched marker (own ExtensibleElement or custom)
			EObject modelElement = (EObject) object;
			List<Aspect> aspects = AspectManager.INSTANCE.getAspects(modelElement);

			for (Aspect aspect : aspects) {
				if (!aspect.isMultiple()) {
					// if already instanciated, don't propose to create it
					boolean alreadyInstanciated = false;
					// OLD (List<AspectInstance>) modelElement.getAspects()
					for (AspectInstance aspectInstance : AspectManager.INSTANCE.getAspectInstances(modelElement)) {
						if (aspect.getId().equals(aspectInstance.getId())) {
							alreadyInstanciated = true;
							break;
						}
					}
					if (alreadyInstanciated) {
						continue;
					}
				}
				
				AspectInstance aspectInstance = AspectFactory.INSTANCE.createAspectInstance(aspect, (EObject) object); // TODO better no cast
				if (aspectInstance == null) {
					// NB. error logged in Aspects
					continue;
				}

				// adding corresponding new child descriptor and passing
				// the aspect id, so it can be found later
				newChildDescriptors.add(createChildAspectParameter(
						ConfPackage.Literals.CONF_MODEL__ASPECT_INSTANCES, // TODO ?!
						aspectInstance, aspect.getId()));
			}
		}
	}

	/**
	 * This is a convenience method that creates a <code>CommandParameter</code>
	 * for a given parent feature and child object.
	 */
	protected CommandParameter createChildAspectParameter(Object feature,
			Object child, String aspectId) {
		return new AspectCommandParameter(null, feature, child, aspectId);
	}
	

	/**
	 * This override adds handling for creating new child aspect commands.
	 * @override
	 */
	public Command createCommand(Object object, EditingDomain domain,
			Class<? extends Command> commandClass,
			CommandParameter commandParameter) {
		// Commands should operate on the values, not their wrappers.  If the command's values needed to be unwrapped,
		// we'll get back a new CommandParameter.
		commandParameter = unwrapCommandValues(commandParameter, commandClass);


	    if (commandClass == CreateChildCommand.class) {
			EStructuralFeature commandFeature = commandParameter
					.getEStructuralFeature();

			if (commandFeature == null
					&& (commandParameter.getValue() instanceof CommandParameter)
					&& ((CommandParameter) commandParameter.getValue())
							.getFeature() instanceof EStructuralFeature) { // ? always happens
				commandFeature = (EStructuralFeature) ((CommandParameter) commandParameter
						.getValue()).getFeature();

			} else { // should not happen
				// TODO log error
				return super.createCommand(object, domain, commandClass,
						commandParameter);
			}

			// checking whether we're in the new child aspect case
			// TODO better, with aspect enriched marker (own ExtensibleElement or custom)
			// ex. ConfPackage.eINSTANCE.getExtensibleElement().isSuperTypeOf(commandFeature.getEContainingClass())
			if (true // NB. doesn't work : EcorePackage.eINSTANCE.getEObject().isSuperTypeOf(commandFeature.getEContainingClass())
					&& commandFeature.getFeatureID() == ConfPackage.CONF_MODEL__ASPECT_INSTANCES) {

				CommandParameter newChildParameter = (CommandParameter) commandParameter.getValue(); // TODO check	    		
				if (newChildParameter instanceof AspectCommandParameter) {
					AspectCommandParameter newChildAspectParameter = (AspectCommandParameter) commandParameter.getValue();

					// creating corresponding new child command and passing
					// the aspect id, so it can be found later
					// NB. using confModel as owner rather than commandParameter.getEOwner()
					return createCreateChildAspectCommand(domain,
							AspectManager.INSTANCE.getConfModel(commandParameter.getEOwner()),
							newChildParameter.getEStructuralFeature(), newChildParameter
									.getValue(), newChildParameter.getIndex(),
							commandParameter.getCollection(),
							newChildAspectParameter.getAspectId());

				} else {
					// Happens only in mere Property case (not Aspect)
					// TODO log, should be forbidden or at least confble
				}
			}
		}
		return super.createCommand(object, domain, commandClass,
				commandParameter);
	}
	

	/**
	 * Adds aspect handling to factorAddCommand
	 */
	@Override
	protected Command factorAddCommand(EditingDomain domain, CommandParameter commandParameter) {
		// separating the elements to be Added between aspects and non-aspects
		final List<Object> listWithoutAspectInstances = new ArrayList<Object>();
		List<AspectInstance> aspectInstances = null;
		for (Object child : commandParameter.getCollection()) {
			if (child instanceof AspectInstance) {
				AspectInstance aspectInstance = (AspectInstance) child;
				if (aspectInstances == null) {
					aspectInstances = new ArrayList<AspectInstance>();
				}
				aspectInstances.add(aspectInstance);
			} else {
				listWithoutAspectInstances.add(child);
			}
		}

		// doing the factor as usually on non-aspects elements to be Added
		Command commandWhitoutAspects = super.factorAddCommand(domain, new CommandParameter(commandParameter.getOwner(),
				null, listWithoutAspectInstances, commandParameter.getIndex())); // TODO index ??
		if (commandWhitoutAspects == UnexecutableCommand.INSTANCE || aspectInstances == null) {
			return commandWhitoutAspects;
		}
		// preparing compounding
		CompoundCommand compoundAddCommand = null;
		if (commandWhitoutAspects instanceof CompoundCommand) {
			compoundAddCommand = (CompoundCommand) compoundAddCommand;
		} else {
			compoundAddCommand = new CompoundCommand(CompoundCommand.MERGE_COMMAND_ALL);
			compoundAddCommand.append(commandWhitoutAspects);
		}
		// adding the command that will add the aspects to the owner's ConfModel
		// NB. there is only one confModel (for all aspects) : the one of the owner
		ConfModel confModel = AspectManager.INSTANCE.getConfModel(commandParameter.getEOwner());
		compoundAddCommand.append(createAddCommand(domain, confModel,
				(EStructuralFeature) ConfPackage.eINSTANCE.getConfModel_AspectInstances(),
				aspectInstances, 0)); // TODO idnex ??
		return compoundAddCommand;
	}


	/**
	 * Enables Copy for a model element's aspects, by creating a custom
	 * CopyCommandWithAspects.
	 */
	@Override
	protected Command createCopyCommand(EditingDomain domain, EObject owner,
			Helper helper) {
		// used by copyToClipboard
		return new CopyCommandWithAspects(domain, owner, helper);
	}

	/**
	 * Enables Copy for a model element's aspects, by creating a custom
	 * InitializeCopyCommandWithAspects.
	 */
	@Override
	protected Command createInitializeCopyCommand(EditingDomain domain,
			EObject owner, Helper helper) {
		// used by copyToClipboard
		return new InitializeCopyCommandWithAspects(domain, owner, helper);
	}

	/**
	 * Adds auto created aspects to CreateChild, by creating a custom
	 * CreateChildCommandWithAspects.
	 */
	@Override
	protected Command createCreateChildCommand(EditingDomain domain,
			EObject owner, EStructuralFeature feature, Object value, int index,
			Collection<?> collection) {
		return new CreateChildCommandWithAspects(domain, owner, feature, value, index, collection, this);
	}

	/**
	 * This creates a primitive {@link org.eclipse.jwt.we.conf.aspects.emf.edit.command.CreateChildAspectCommand}
	 * that knows which aspect it should create.
	 * @param domain
	 * @param owner
	 * @param feature
	 * @param value
	 * @param index
	 * @param collection
	 * @param aspectId
	 * @return
	 */
	protected Command createCreateChildAspectCommand(EditingDomain domain,
			EObject owner, EStructuralFeature feature, Object value, int index,
			Collection<?> collection, String aspectId) {
		return new CreateChildAspectCommand(domain, owner, feature, value,
				index, collection, this, aspectId);
	}

}
