package org.yakindu.sct.model.sgraph.util;

/*
 * Copyright (c) 2005, 2007 IBM Corporation, Embarcadero Technologies, 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 - initial API and implementation
 *   Kenn Hussey (Embarcadero Technologies) - 204200
 *
 * $Id: DerivedEObjectEList.java,v 1.14 2007/10/22 13:27:56 khussey Exp $
 */

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.NotificationImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.util.AbstractSequentialInternalEList;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.emf.ecore.util.InternalEList;

/**
 * A list whose contents are derived (dynamically using a "smart" iterator) from
 * the values of other features in a trivial way (e.g. by type). The default
 * implementation does not support modification.
 * 
 * @since 1.2
 */
public class DerivedEObjectEList<E> extends AbstractSequentialInternalEList<E>
		implements EStructuralFeature.Setting, InternalEList.Unsettable<E> {

	protected class DerivedListIterator implements ListIterator<E> {

		protected int index = 0;

		protected int featureIndex = 0;

		protected ListIterator<Object> valuesIterator = null;

		protected EStructuralFeature preparedFeature = null;

		protected EList<Object> preparedValues = new UniqueEList.FastCompare<Object>();

		protected int prepared = 0;

		protected boolean scanNext(EStructuralFeature nextFeature,
				ListIterator<Object> nextValuesIterator) {
			boolean isFeatureMap = FeatureMapUtil.isFeatureMap(nextFeature);

			while (nextValuesIterator.hasNext()) {
				Object nextValue = nextValuesIterator.next();

				if (isFeatureMap) {
					FeatureMap.Entry entry = (FeatureMap.Entry) nextValue;
					nextFeature = entry.getEStructuralFeature();
					nextValue = entry.getValue();
				}

				if ((isIncluded(nextFeature) ? nextValue != null
						: isIncluded(nextValue))
						&& ((index < preparedValues.size() && nextValue == preparedValues
								.get(index)) || preparedValues.add(nextValue))) {

					valuesIterator = nextValuesIterator;
					preparedFeature = nextFeature;
					return true;
				}
			}

			return false;
		}

		protected boolean prepareNext() {

			if (valuesIterator == null
					|| !scanNext(preparedFeature, valuesIterator)) {

				while (featureIndex < sourceFeatureIDs.length) {
					int sourceFeatureID = sourceFeatureIDs[featureIndex++];

					if (owner.eIsSet(sourceFeatureID)) {
						EStructuralFeature sourceFeature = getEStructuralFeature(sourceFeatureID);
						Object value = owner.eGet(sourceFeatureID, resolve(),
								true);

						if (sourceFeature.isMany()
								|| FeatureMapUtil.isFeatureMap(sourceFeature)) {

							@SuppressWarnings("unchecked")
							InternalEList<Object> valuesList = (InternalEList<Object>) value;

							if (scanNext(sourceFeature,
									resolve() ? valuesList.listIterator()
											: valuesList.basicListIterator())) {

								prepared = 3;
								return true;
							}
						} else if ((isIncluded(sourceFeature) ? value != null
								: isIncluded(value))
								&& ((index < preparedValues.size() && value == preparedValues
										.get(index)) || preparedValues
										.add(value))) {

							valuesIterator = null;
							preparedFeature = sourceFeature;
							prepared = 2;
							return true;
						}
					}
				}

				prepared = 1;
				return false;
			} else {
				prepared = 3;
				return true;
			}
		}

		public boolean hasNext() {

			switch (prepared) {
			case 3:
			case 2:
				return true;
			case 1:
				return false;
			case -3:
				valuesIterator.next();
			default:
				return prepareNext();
			}
		}

		public E next() {

			if (hasNext()) {
				prepared = 0;
				Object next = preparedValues.get(index++);
				hasNext();
				return derive(next);
			} else {
				throw new NoSuchElementException();
			}
		}

		protected boolean scanPrevious(EStructuralFeature previousFeature,
				ListIterator<Object> previousValuesIterator) {
			boolean isFeatureMap = FeatureMapUtil.isFeatureMap(previousFeature);

			while (previousValuesIterator.hasPrevious()) {
				Object previousValue = previousValuesIterator.previous();

				if (isFeatureMap) {
					FeatureMap.Entry entry = (FeatureMap.Entry) previousValue;
					previousFeature = entry.getEStructuralFeature();
					previousValue = entry.getValue();
				}

				if (index > 0 && previousValue == preparedValues.get(index - 1)) {
					valuesIterator = previousValuesIterator;
					preparedFeature = previousFeature;
					return true;
				}
			}

			return false;
		}

		protected boolean preparePrevious() {

			if (valuesIterator == null
					|| !scanPrevious(preparedFeature, valuesIterator)) {

				while (featureIndex > 0) {
					int sourceFeatureID = sourceFeatureIDs[--featureIndex];

					if (owner.eIsSet(sourceFeatureID)) {
						EStructuralFeature sourceFeature = getEStructuralFeature(sourceFeatureID);
						Object value = owner.eGet(sourceFeatureID, resolve(),
								true);

						if (sourceFeature.isMany()
								|| FeatureMapUtil.isFeatureMap(sourceFeature)) {

							@SuppressWarnings("unchecked")
							InternalEList<Object> valuesList = (InternalEList<Object>) value;
							int valuesListSize = valuesList.size();

							if (scanPrevious(
									sourceFeature,
									resolve() ? valuesList
											.listIterator(valuesListSize)
											: valuesList
													.basicListIterator(valuesListSize))) {

								prepared = -3;
								return true;
							}
						} else if (index > 0
								&& value == preparedValues.get(index - 1)) {

							valuesIterator = null;
							preparedFeature = sourceFeature;
							prepared = -2;
							return true;
						}
					}
				}

				prepared = -1;
				return false;
			} else {
				prepared = -3;
				return true;
			}
		}

		public boolean hasPrevious() {

			switch (prepared) {
			case -3:
			case -2:
				return true;
			case -1:
				return false;
			case 3:
				valuesIterator.previous();
			default:
				return preparePrevious();
			}
		}

		public E previous() {

			if (prepared < -1 || hasPrevious()) {
				prepared = 0;
				Object previous = preparedValues.remove(--index);
				hasPrevious();
				return derive(previous);
			} else {
				throw new NoSuchElementException();
			}
		}

		public int nextIndex() {
			return index;
		}

		public int previousIndex() {
			return index - 1;
		}

		public void remove() {
			throw new UnsupportedOperationException();
		}

		public void set(Object element) {
			throw new UnsupportedOperationException();
		}

		public void add(Object element) {
			throw new UnsupportedOperationException();
		}

		protected boolean resolve() {
			return false;
		}

	}

	protected class EmptyDerivedListIterator extends DerivedListIterator {

		@Override
		public boolean hasNext() {
			return false;
		}

		@Override
		public boolean hasPrevious() {
			return false;
		}
	}

	protected class ResolvingDerivedListIterator extends DerivedListIterator {

		@Override
		protected boolean resolve() {
			return true;
		}

	}

	protected final Class<?> dataClass;

	protected final InternalEObject owner;

	protected final int featureID;

	protected final int[] sourceFeatureIDs;

	public DerivedEObjectEList(Class<?> dataClass, InternalEObject owner,
			int featureID, int[] sourceFeatureIDs) {
		super();

		this.dataClass = dataClass;
		this.owner = owner;
		this.featureID = featureID;
		this.sourceFeatureIDs = sourceFeatureIDs;
	}

	public Object get(boolean resolve) {
		return this;
	}

	public EObject getEObject() {
		return owner;
	}

	public EStructuralFeature getEStructuralFeature() {
		return getEStructuralFeature(featureID);
	}

	public EStructuralFeature getEStructuralFeature(int featureID) {
		return owner.eClass().getEStructuralFeature(featureID);
	}

	public boolean isSet() {
		return !isEmpty();
	}

	@SuppressWarnings("unchecked")
	public void set(Object newValue) {
		clear();
		addAll((List<? extends E>) newValue);
	}

	public void unset() {
		clear();
	}

	@Override
	public ListIterator<E> listIterator(int index) {
		return listIterator(index, true);
	}

	@Override
	public int size() {

		if (sourceFeatureIDs != null) {
			EList<Object> values = new UniqueEList.FastCompare<Object>();

			for (int i = 0; i < sourceFeatureIDs.length; i++) {
				int sourceFeatureID = sourceFeatureIDs[i];

				if (owner.eIsSet(sourceFeatureID)) {
					EStructuralFeature sourceFeature = getEStructuralFeature(sourceFeatureID);
					Object value = owner.eGet(sourceFeatureID, false, true);

					if (FeatureMapUtil.isFeatureMap(sourceFeature)) {
						FeatureMap featureMap = (FeatureMap) value;

						for (int j = 0, size = featureMap.size(); j < size; j++) {
							value = featureMap.getValue(j);

							if (isIncluded(featureMap.getEStructuralFeature(j)) ? value != null
									: isIncluded(value)) {

								values.add(value);
							}
						}
					} else if (isIncluded(sourceFeature)) {

						if (sourceFeature.isMany()) {
							InternalEList<?> valuesList = (InternalEList<?>) value;

							if (valuesList instanceof RandomAccess) {

								for (int j = 0, size = valuesList.size(); j < size; j++) {
									values.add(valuesList.basicGet(j));
								}
							} else {

								for (Iterator<?> v = valuesList.basicIterator(); v
										.hasNext();) {

									values.add(v.next());
								}
							}
						} else if (value != null) {
							values.add(value);
						}
					} else {

						if (sourceFeature.isMany()) {
							InternalEList<?> valuesList = (InternalEList<?>) value;

							if (valuesList instanceof RandomAccess) {

								for (int j = 0, size = valuesList.size(); j < size; j++) {
									value = valuesList.basicGet(j);

									if (isIncluded(value)) {
										values.add(value);
									}
								}
							} else {

								for (Iterator<?> v = valuesList.basicIterator(); v
										.hasNext();) {

									value = v.next();

									if (isIncluded(value)) {
										values.add(value);
									}
								}
							}
						} else if (isIncluded(value)) {
							values.add(value);
						}
					}
				}
			}

			return values.size();
		}

		return 0;
	}

	@Override
	public boolean isEmpty() {

		if (sourceFeatureIDs != null) {

			for (int i = 0; i < sourceFeatureIDs.length; i++) {
				int sourceFeatureID = sourceFeatureIDs[i];

				if (owner.eIsSet(sourceFeatureID)) {
					EStructuralFeature sourceFeature = getEStructuralFeature(sourceFeatureID);
					Object value = owner.eGet(sourceFeatureID, false, true);

					if (FeatureMapUtil.isFeatureMap(sourceFeature)) {
						FeatureMap featureMap = (FeatureMap) value;

						for (int j = 0, size = featureMap.size(); j < size; j++) {

							if (isIncluded(featureMap.getEStructuralFeature(j)) ? featureMap
									.getValue(j) != null
									: isIncluded(featureMap.getValue(j))) {

								return false;
							}
						}
					} else if (isIncluded(sourceFeature)) {

						if (sourceFeature.isMany() ? ((List<?>) value).size() > 0
								: value != null) {

							return false;
						}
					} else {

						if (sourceFeature.isMany()) {
							InternalEList<?> valuesList = (InternalEList<?>) value;

							if (valuesList instanceof RandomAccess) {

								for (int j = 0, size = valuesList.size(); j < size; j++) {

									if (isIncluded(valuesList.basicGet(j))) {
										return false;
									}
								}
							} else {

								for (Iterator<?> v = valuesList.basicIterator(); v
										.hasNext();) {

									if (isIncluded(v.next())) {
										return false;
									}
								}
							}
						} else if (isIncluded(value)) {
							return false;
						}
					}
				}
			}
		}

		return true;
	}

	@Override
	public boolean contains(Object object) {

		if (sourceFeatureIDs != null) {

			for (int i = 0; i < sourceFeatureIDs.length; i++) {
				int sourceFeatureID = sourceFeatureIDs[i];

				if (owner.eIsSet(sourceFeatureID)) {
					EStructuralFeature sourceFeature = getEStructuralFeature(sourceFeatureID);
					Object value = owner.eGet(sourceFeatureID, true, true);

					if (FeatureMapUtil.isFeatureMap(sourceFeature)) {
						FeatureMap featureMap = (FeatureMap) value;

						for (int j = 0, size = featureMap.size(); j < size; j++) {
							value = featureMap.getValue(j);

							if (isIncluded(featureMap.getEStructuralFeature(j)) ? value == object
									: isIncluded(value)
											&& derive(value) == object) {

								return true;
							}
						}
					} else if (isIncluded(sourceFeature)) {

						if (sourceFeature.isMany() ? ((List<?>) value)
								.contains(object) : value == object) {

							return true;
						}
					} else {

						if (sourceFeature.isMany()) {
							InternalEList<?> valuesList = (InternalEList<?>) value;

							if (valuesList instanceof RandomAccess) {

								for (int j = 0, size = valuesList.size(); j < size; j++) {
									value = valuesList.basicGet(j);

									if (isIncluded(value)
											&& derive(value) == object) {

										return true;
									}
								}
							} else {

								for (Iterator<?> v = valuesList.basicIterator(); v
										.hasNext();) {

									value = v.next();

									if (isIncluded(value)
											&& derive(value) == object) {

										return true;
									}
								}
							}
						} else if (isIncluded(value) && derive(value) == object) {
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	@Override
	public List<E> basicList() {
		return new DerivedEObjectEList<E>(dataClass, owner, featureID,
				sourceFeatureIDs) {

			@Override
			public ListIterator<E> listIterator(int index) {
				return basicListIterator(index);
			}
		};
	}

	@Override
	public ListIterator<E> basicListIterator(int index) {
		return listIterator(index, false);
	}

	protected boolean isNotificationRequired() {
		return false;
	}

	protected NotificationImpl createNotification(int eventType,
			Object oldObject, Object newObject, int index, boolean wasSet) {
		return new ENotificationImpl(owner, eventType, featureID, oldObject,
				newObject, index, wasSet);
	}

	protected void dispatchNotification(Notification notification) {
		owner.eNotify(notification);
	}

	@Override
	public void add(int index, E object) {
		addUnique(index, object);
	}

	@Override
	public void addUnique(int index, E object) {

		if (isNotificationRequired()) {
			boolean oldIsSet = isSet();
			super.add(index, validate(index, object));
			NotificationImpl notification = createNotification(
					Notification.ADD, null, object, index, oldIsSet);
			dispatchNotification(notification);
		} else {
			super.add(index, validate(index, object));
		}
	}

	@Override
	public boolean addAll(int index, Collection<? extends E> objects) {
		return addAllUnique(index, objects);
	}

	@Override
	public boolean addAllUnique(int index, Collection<? extends E> objects) {
		int size = objects.size();

		if (size > 0) {

			if (isNotificationRequired()) {
				boolean oldIsSet = isSet();

				if (doAddAllUnique(index, objects)) {
					NotificationImpl notification = size == 1 ? createNotification(
							Notification.ADD, null, objects.iterator().next(),
							index, oldIsSet) : createNotification(
							Notification.ADD_MANY, null, objects, index,
							oldIsSet);
					dispatchNotification(notification);
					return true;
				}
			} else {
				return doAddAllUnique(index, objects);
			}
		}

		return false;
	}

	protected boolean doAddAllUnique(int index, Collection<? extends E> objects) {
		boolean modified = false;
		ListIterator<E> listIterator = listIterator(index);

		for (Iterator<? extends E> o = objects.iterator(); o.hasNext();) {
			listIterator.add(validate(index, o.next()));
			modified = true;
		}

		return modified;
	}

	@Override
	public E remove(int index) {

		if (isNotificationRequired()) {
			boolean oldIsSet = isSet();
			NotificationImpl notification = createNotification(
					Notification.REMOVE, super.remove(index), null, index,
					oldIsSet);
			dispatchNotification(notification);
			@SuppressWarnings("unchecked")
			E oldValue = (E) notification.getOldValue();
			return oldValue;
		} else {
			return super.remove(index);
		}
	}

	@Override
	public E set(int index, E object) {
		return setUnique(index, object);
	}

	@Override
	public E setUnique(int index, E object) {

		if (isNotificationRequired()) {
			boolean oldIsSet = isSet();
			Notification notification = createNotification(Notification.SET,
					super.set(index, validate(index, object)), object, index,
					oldIsSet);
			dispatchNotification(notification);
			@SuppressWarnings("unchecked")
			E oldValue = (E) notification.getOldValue();
			return oldValue;
		} else {
			return super.set(index, validate(index, object));
		}
	}

	/**
	 * Indicates whether all elements from the specified source feature are
	 * included in this list.
	 * 
	 * @param feature
	 *            A source feature.
	 * @return <code>true</code> if the elements are included;
	 *         <code>false</code> otherwise.
	 */
	protected boolean isIncluded(EStructuralFeature feature) {
		return dataClass
				.isAssignableFrom(feature.getEType().getInstanceClass());
	}

	/**
	 * Indicates whether the specified element from a source feature is included
	 * in this list.
	 * 
	 * @param object
	 *            An element from a source feature.
	 * @return <code>true</code> if the element is included; <code>false</code>
	 *         otherwise.
	 */
	protected boolean isIncluded(Object object) {
		return dataClass.isInstance(derive(object));
	}

	/**
	 * Derives a value for this list from the specified element in a source
	 * feature.
	 * 
	 * @param object
	 *            An element from a source feature.
	 * @return The "derived" value of the specified element.
	 */
	@SuppressWarnings("unchecked")
	protected E derive(Object object) {
		return (E) object;
	}

	protected E validate(int index, E object) {

		if (!dataClass.isInstance(object)) {
			throw new IllegalArgumentException(String.valueOf(object));
		}

		return object;
	}

	protected ListIterator<E> newListIterator() {
		return new DerivedListIterator();
	}

	protected ListIterator<E> newResolvingListIterator() {
		return new ResolvingDerivedListIterator();
	}

	protected ListIterator<E> newEmptyListIterator() {
		return new EmptyDerivedListIterator();
	}

	protected ListIterator<E> listIterator(int index, boolean resolve) {

		if (sourceFeatureIDs == null || sourceFeatureIDs.length == 0) {

			if (index != 0) {
				throw new IndexOutOfBoundsException("index = " + index //$NON-NLS-1$
						+ ", size = 0"); //$NON-NLS-1$
			}

			return newEmptyListIterator();
		}

		ListIterator<E> listIterator = resolve ? newResolvingListIterator()
				: newListIterator();

		for (int i = 0; i < index; i++) {
			listIterator.next();
		}

		return listIterator;
	}

}
