/*******************************************************************************
 * Copyright (c) 2010, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     René Brandstetter - Bug 411821 - [QuickAccess] Contribute SearchField
 *                                      through a fragment or other means
 *     Patrik Suzzi <psuzzi@gmail.com> - Bug 474865
 *******************************************************************************/
package org.eclipse.e4.ui.model.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.application.ui.MContext;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EGenericType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.ETypeParameter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.UsageCrossReferencer;

public class ModelUtils {
	//public static final String CONTAINING_CONTEXT = "ModelUtils.containingContext";
	public static final String CONTAINING_PARENT = "ModelUtils.containingParent";

	public static EClassifier getTypeArgument(EClass eClass,
			EGenericType eGenericType) {
		ETypeParameter eTypeParameter = eGenericType.getETypeParameter();

		if( eTypeParameter != null ) {
			for (EGenericType eGenericSuperType : eClass.getEAllGenericSuperTypes()) {
				EList<ETypeParameter> eTypeParameters = eGenericSuperType
						.getEClassifier().getETypeParameters();
				int index = eTypeParameters.indexOf(eTypeParameter);
				if (index != -1
						&& eGenericSuperType.getETypeArguments().size() > index) {
					return getTypeArgument(eClass, eGenericSuperType
							.getETypeArguments().get(index));
				}
			}
			return null;
		} else {
			return eGenericType.getEClassifier();
		}
	}

	public static MApplicationElement findElementById(MApplicationElement element, String id) {
		if (id == null || id.length() == 0) {
			return null;
		}
		// is it me?
		if (id.equals(element.getElementId())) {
			return element;
		}
		// Recurse if this is a container
		EList<EObject> elements = ((EObject) element).eContents();
		for (EObject childElement : elements) {
			if (!(childElement instanceof MApplicationElement)) {
				continue;
			}
			MApplicationElement result = findElementById((MApplicationElement) childElement, id);
			if (result != null) {
				return result;
			}
		}
		return null;
	}

	public static List<MApplicationElement> merge(MApplicationElement container, EStructuralFeature feature, List<MApplicationElement> elements, String positionInList) {
		EObject eContainer = (EObject) container;

		if( feature.isMany() ) {
			List<MApplicationElement> copy = new ArrayList<MApplicationElement>(elements);

			@SuppressWarnings("unchecked")
			List<MApplicationElement> list= (List<MApplicationElement>)eContainer.eGet(feature);
			boolean flag = true;
			if( positionInList != null && positionInList.trim().length() != 0 ) {
				int index = -1;

				PositionInfo posInfo = PositionInfo.parse(positionInList);

				if( posInfo != null ){
					switch (posInfo.getPosition()){
					case FIRST:
						index = 0;
						break;

					case INDEX:
						index = posInfo.getPositionReferenceAsInteger();
						break;

					case BEFORE:
					case AFTER:
						int tmpIndex = -1;
						String elementId = posInfo.getPositionReference();

						for( int i = 0; i < list.size(); i++ ) {
							if( elementId.equals((list.get(i)).getElementId()) ) {
								tmpIndex = i;
								break;
							}
						}

						if( tmpIndex != -1 ) {
							if( posInfo.getPosition() == Position.BEFORE ) {
								index = tmpIndex;
							} else {
								index = tmpIndex + 1;
							}
						} else {
							System.err.println("Could not find element with Id '"+elementId+"'");
						}

					case LAST:
					default:
						// both no special operation, because the default is adding it at the last position
						break;
					}
				} else {
					System.err.println("Not a valid list position.");
				}


				if( index >= 0 && list.size() > index ) {
					flag = false;
					mergeList(list,  elements, index);
				}
			}

			// If there was no match append it to the list
			if( flag ) {
				mergeList(list,  elements, -1);
			}

			return copy;
		} else {
			if( elements.size() >= 1 ) {
				if( elements.size() > 1 ) {
					//FIXME Pass the logger
					System.err.println("The feature is single valued but a list of values is passed in.");
				}
				MApplicationElement e = elements.get(0);
				eContainer.eSet(feature, e);
				return Collections.singletonList(e);
			}
		}

		return Collections.emptyList();
	}

	private static void mergeList(List<MApplicationElement> list, List<MApplicationElement> elements, int index) {
		MApplicationElement[] tmp = new MApplicationElement[elements.size()];
		elements.toArray(tmp);
		for(MApplicationElement element : tmp) {
			String elementID = element.getElementId();
			boolean found = false;
			if ((elementID != null) && (elementID.length() != 0)) {
				for(Object existingObject : list) {
					if (!(existingObject instanceof MApplicationElement)) {
						continue;
					}
					MApplicationElement existingEObject = (MApplicationElement) existingObject;
					if (!elementID.equals(existingEObject.getElementId())) {
						continue;
					}
					if (EcoreUtil.equals((EObject)existingEObject, (EObject)element)) {
						found = true; // skip
						break;
					} else { // replace
						EObject root = EcoreUtil.getRootContainer((EObject) existingEObject);
						// Replacing the object in the container
						EcoreUtil.replace((EObject)existingEObject, (EObject)element);
						// Replacing the object in other references than the container.
						Collection<Setting> settings = UsageCrossReferencer.find((EObject) existingEObject, root);
						for (Setting setting : settings) {
							setting.set(element);
						}
						found = true;
					}
				}
			}
			if (!found) {
				if (index == -1) {
					list.add(element);
				} else {
					list.add(index, element);
				}
			}
		}
	}

	static MApplicationElement getParent(MApplicationElement element) {
		if ( (element instanceof MUIElement) && ((MUIElement)element).getCurSharedRef() != null) {
			return ((MUIElement)element).getCurSharedRef().getParent();
		} else if (element != null && element.getTransientData() != null
				&& element.getTransientData().get(CONTAINING_PARENT) instanceof MApplicationElement) {
			return (MApplicationElement) element.getTransientData().get(CONTAINING_PARENT);
		} else if (element instanceof EObject) {
			EObject eContainer = ((EObject) element).eContainer();
			if (eContainer instanceof MApplicationElement) {
				return (MApplicationElement) eContainer;
			}
		}
		return null;
	}

	public static IEclipseContext getContainingContext(MApplicationElement element) {
		MApplicationElement curParent = getParent(element);

		while (curParent != null) {
			if (curParent instanceof MContext) {
				return ((MContext) curParent).getContext();
			}
			curParent = getParent(curParent);
		}

		return null;
	}
}
