/*******************************************************************************
 * Copyright (c) 2006, 2015 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.datamodel;

import org.eclipse.cdt.dsf.concurrent.Immutable;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.service.IDsfService;
import org.eclipse.core.runtime.PlatformObject;

/**
 * Base implementation of the IDMContext interface.  There are two pieces of
 * functionality here: <br>
 * 1) The {@link #getAdapter(Class)} implementation which retrieves model
 * adapters registered with the session. <br>
 * 2) Methods to help compare DM Contexts.  <br>
 * <p>
 * Note: The {@link #equals(Object)} and {@link #hashCode()} methods are
 * made abstract to force the deriving classes to provide a proper
 * implementation.  Data Model Context objects are meant to be used as handles,
 * therefore a proper equals implementation is critical.
 * </p>
 * @param <V> Data Model data type that this context is for.
 *
 * @since 1.0
 */
@Immutable
abstract public class AbstractDMContext extends PlatformObject implements IDMContext {
	private final DsfSession fSession;
	private final IDMContext[] fParents;

	/**
	 * Main constructor provides all data needed to implement the <code>IDMContext</code>
	 * interface.
	 * @since 2.2
	 */
	public AbstractDMContext(DsfSession session, IDMContext[] parents) {
		fSession = session;
		fParents = parents;
		for (IDMContext parent : parents) {
			assert (parent != null);
		}
	}

	/** Convenience constructor */
	public AbstractDMContext(IDsfService service, IDMContext[] parents) {
		this(service.getSession(), parents);
	}

	/** Backward compatibility constructor */
	public AbstractDMContext(String fSessionId, IDMContext[] parents) {
		this(DsfSession.getSession(fSessionId), parents);
	}

	/**
	 * Should be used by the deriving class to compare the basic context object
	 * information.
	 * @param other the other service to compare to
	 * @return true if contexts are equal
	 */
	protected boolean baseEquals(Object other) {
		if (other == null)
			return false;
		if (!(other.getClass().equals(getClass())))
			return false;
		IDMContext otherCtx = (IDMContext) other;
		return getSessionId().equals(otherCtx.getSessionId()) && areParentsEqual(otherCtx.getParents());
	}

	private boolean areParentsEqual(IDMContext[] otherParents) {
		IDMContext[] parents = getParents();
		if (!(parents.length == otherParents.length))
			return false;
		for (int i = 0; i < parents.length; i++) {
			if (!parents[i].equals(otherParents[i])) {
				return false;
			}
		}
		return true;
	}

	protected int baseHashCode() {
		int parentsHash = 0;
		for (Object parent : getParents()) {
			parentsHash += parent.hashCode();
		}
		return getSessionId().hashCode() + parentsHash;
	}

	/**
	 * @return a stringified representation of our parent context. If we have
	 *         more than one parent, the parents are separated by commas, and
	 *         the entire collection is wrapped with parenthesis:
	 *         "(p1,p2,...,pn)"
	 */
	protected String baseToString() {
		StringBuilder retVal = new StringBuilder();
		for (IDMContext parent : getParents()) {
			retVal.append(parent);
			retVal.append(',');
		}
		if (retVal.length() > 0) {
			retVal.deleteCharAt(retVal.length() - 1); // remove trailing comma
		}
		if (getParents().length > 1) {
			retVal.insert(0, '(');
			retVal.insert(retVal.length(), ')');
		}
		return retVal.toString();
	}

	@Override
	public String getSessionId() {
		return fSession.getId();
	}

	@Override
	public IDMContext[] getParents() {
		return fParents;
	}

	/**
	 * Overrides the standard platform getAdapter to provide session-specific
	 * adapters.
	 * <p>
	 * ModelContext is intended to be used in views, which call the
	 * contexts.getAdapter() method to retrieve model-specific content and
	 * label providers.  But since many different sessions could be active
	 * at the same time, each requiring different content providers, the
	 * standard platform <code>IAdapterManager</code> is not sufficient in
	 * handling adapters for the model context object.  This is because
	 * <code>IAdapterManager</code> uses only the class of the adaptable to
	 * select the correct adapter factory, while for model context, the
	 * session is equally important.
	 * @see org.eclipse.runtime.IAdapterManager
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapterType) {
		T retVal = (T) fSession.getModelAdapter(adapterType);
		if (retVal == null) {
			retVal = super.getAdapter(adapterType);
		}
		return retVal;
	}

	/**
	 * Deriving classes must implement proper equals and hashCode operations
	 * based on context data.
	 */
	@Override
	abstract public boolean equals(Object obj);

	/**
	 * Deriving classes must implement proper equals and hashCode operations
	 * based on context data.
	 */
	@Override
	abstract public int hashCode();
}
