/*******************************************************************************
 * Copyright (c) 2016 EclipseSource Muenchen GmbH 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:
 * Eugen Neufeld - initial API and implementation
 ******************************************************************************/

package org.eclipse.e4.ui.internal.workbench;

import java.util.Comparator;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.fragment.MStringModelFragment;
import org.eclipse.e4.ui.model.internal.ModelUtils;
import org.eclipse.e4.ui.model.internal.PositionInfo;

/**
 * Custom {@link Comparator} that operates on {@link MStringModelFragment} model
 * fragments wrapped in a {@link ModelFragmentWrapper}. The comparator will
 * order the fragments in the logical way in which they should be merged, based
 * on their defined {@link MStringModelFragment#getPositionInList()
 * positionInList} , as follows:
 * <li><b>Index</b> - all elements with an index are merged first. Lowest to
 * highest index.
 * <li><b>No position defined</b> - all contributions without any information
 * are sorted in the order in which they are read from the extension points and
 * added to the end of the list
 * <li><b>First/Last</b> - all elements with a first/last tag. If there are
 * several elements in the same container with the same tag, then those elements
 * are sorted in the order as they come form the extension registry.
 * <li><b> After/Before</b> - the after and before contributions are ordered
 * considering potential dependencies between fragments. If the dependency is
 * not resolvable eg points to the application model the element is sorted to
 * the front of the after/before list.
 *
 */
public class ModelFragmentComparator implements Comparator<ModelFragmentWrapper> {

	private final MApplication application;

	public ModelFragmentComparator(MApplication application) {
		super();
		this.application = application;
	}

	@Override
	public int compare(ModelFragmentWrapper o1, ModelFragmentWrapper o2) {
		if (o1 == o2)
			return 0;

		PositionDescription posInfo1 = getPositionDescription(o1);
		PositionDescription posInfo2 = getPositionDescription(o2);

		switch (posInfo1.getPlace()) {
		case NONE:
			switch (posInfo2.getPlace()) {
			case INDEX:
			case NONE:
				return 1;
			default:
				return -1;
			}
		case ABSOLUTE:
			switch (posInfo2.getPlace()) {
			case RELATIVE:
				return -1;
			default:
				return 1;
			}
		case INDEX:
			switch (posInfo2.getPlace()) {
			case INDEX:
				int result = posInfo1.getPositionReferenceAsInteger() - posInfo2.getPositionReferenceAsInteger();
				if (result == 0)
					return 1;
				return result;
			default:
				return -1;
			}
		case RELATIVE:
			switch (posInfo2.getPlace()) {
			case RELATIVE:
				boolean hasElement = false;
				for (MApplicationElement element : o2.getModelFragment().getElements()) {
					hasElement |= ModelUtils.findElementById(element, posInfo1.getReference()) != null;
					if (hasElement)
						break;
				}
				if (hasElement)
					return 1;

				hasElement = false;
				for (MApplicationElement element : o1.getModelFragment().getElements()) {
					hasElement |= ModelUtils.findElementById(element, posInfo2.getReference()) != null;
					if (hasElement)
						break;
				}
				if (hasElement)
					return -1;

				hasElement = ModelUtils.findElementById(application, posInfo1.getReference()) != null;
				if (hasElement)
					return -1;

				return 1;
			default:
				return 1;
			}
		default:
			// should never be reached
			return 1;
		}
	}

	private PositionDescription getPositionDescription(ModelFragmentWrapper wrapper) {
		if (!MStringModelFragment.class.isInstance(wrapper.getModelFragment()))
			return new PositionDescription(PositionPlace.NONE, null);
		MStringModelFragment stringFragment = (MStringModelFragment) wrapper.getModelFragment();
		if (stringFragment.getPositionInList() == null)
			return new PositionDescription(PositionPlace.NONE, null);
		String posInList = stringFragment.getPositionInList().trim();
		PositionInfo posInfo = PositionInfo.parse(posInList);
		if (posInfo == null)
			return new PositionDescription(PositionPlace.NONE, null);
		switch (posInfo.getPosition()) {
		case AFTER:
		case BEFORE:
			return new PositionDescription(PositionPlace.RELATIVE, posInfo.getPositionReference());
		case FIRST:
		case LAST:
			return new PositionDescription(PositionPlace.ABSOLUTE, posInfo.getPositionReference());
		case INDEX:
			return new PositionDescription(PositionPlace.INDEX, posInfo.getPositionReference());
		default:
			return new PositionDescription(PositionPlace.NONE, null);
		}
	}

	/**
	 * Inner Class used to describe the Position to which the fragment should be
	 * sorted.
	 */
	private static class PositionDescription {
		private final PositionPlace place;
		private final String reference;

		PositionDescription(PositionPlace place, String reference) {
			this.place = place;
			this.reference = reference;
		}

		public PositionPlace getPlace() {
			return place;
		}

		public String getReference() {
			return reference;
		}

		public int getPositionReferenceAsInteger() {
			return Integer.parseInt(reference);
		}
	}

	/**
	 * Inner class used to describe the position to merge to.
	 */
	private enum PositionPlace {
		NONE, ABSOLUTE, INDEX, RELATIVE
	}
}
