/*******************************************************************************
 * Copyright (c) 2004, 2006 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
 *******************************************************************************/
package org.eclipse.core.internal.registry;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.InvalidRegistryObjectException;

// This object is used to keep track on a contributor basis of the extension and extension points being contributed.
// It is mainly used on removal so we can quickly  find objects to remove.
// Each contribution is made in the context of a namespace.
public class Contribution implements KeyedElement {
	static final int[] EMPTY_CHILDREN = new int[] {0, 0};

	//The registry that owns this object
	protected ExtensionRegistry registry;

	// The actual contributor of the contribution
	final protected String contributorId;

	// Value is derived from the contributorId and cached for performance
	private String defaultNamespace = null;

	// indicates if this contribution needs to be saved in the registry cache
	protected boolean persist;

	// This array stores the identifiers of both the extension points and the extensions.
	// The array has always a minimum size of 2.
	// The first element of the array is the number of extension points and the second the number of extensions.
	// [numberOfExtensionPoints, numberOfExtensions, extensionPoint#1, extensionPoint#2, extensionPoint..., ext#1, ext#2, ext#3, ... ].
	// The size of the array is 2 + (numberOfExtensionPoints +  numberOfExtensions).
	private int[] children = EMPTY_CHILDREN;
	static final public byte EXTENSION_POINT = 0;
	static final public byte EXTENSION = 1;

	protected Contribution(String contributorId, ExtensionRegistry registry, boolean persist) {
		this.contributorId = contributorId;
		this.registry = registry;
		this.persist = persist;
	}

	void mergeContribution(Contribution addContribution) {
		Assert.isTrue(contributorId.equals(addContribution.contributorId));
		Assert.isTrue(registry == addContribution.registry);

		// persist?
		// Old New Result
		//  F   F   F
		//  F   T   T	=> needs to be adjusted
		//  T   F   T
		//  T   T   T
		if (shouldPersist() != addContribution.shouldPersist())
			persist = true;

		int[] existing = getRawChildren();
		int[] addition = addContribution.getRawChildren();

		int extensionPoints = existing[EXTENSION_POINT] + addition[EXTENSION_POINT];
		int extensions = existing[EXTENSION] + addition[EXTENSION];
		int[] allChildren = new int[2 + extensionPoints + extensions];

		allChildren[EXTENSION_POINT] = extensionPoints;
		System.arraycopy(existing, 2, allChildren, 2, existing[EXTENSION_POINT]);
		System.arraycopy(addition, 2, allChildren, 2 + existing[EXTENSION_POINT], addition[EXTENSION_POINT]);
		allChildren[EXTENSION] = extensions;
		System.arraycopy(existing, 2 + existing[EXTENSION_POINT], allChildren, 2 + extensionPoints, existing[EXTENSION]);
		System.arraycopy(addition, 2 + addition[EXTENSION_POINT], allChildren, 2 + extensionPoints + existing[EXTENSION], addition[EXTENSION]);

		children = allChildren;
	}

	void setRawChildren(int[] children) {
		this.children = children;
	}

	protected String getContributorId() {
		return contributorId;
	}

	protected int[] getRawChildren() {
		return children;
	}

	protected int[] getExtensions() {
		int[] results = new int[children[EXTENSION]];
		System.arraycopy(children, 2 + children[EXTENSION_POINT], results, 0, children[EXTENSION]);
		return results;
	}

	protected int[] getExtensionPoints() {
		int[] results = new int[children[EXTENSION_POINT]];
		System.arraycopy(children, 2, results, 0, children[EXTENSION_POINT]);
		return results;
	}

	public String getDefaultNamespace() {
		if (defaultNamespace == null)
			defaultNamespace = registry.getObjectManager().getContributor(contributorId).getName();
		return defaultNamespace;
	}

	@Override
	public String toString() {
		return "Contribution: " + contributorId + " in namespace" + getDefaultNamespace(); //$NON-NLS-1$ //$NON-NLS-2$
	}

	//Implements the KeyedElement interface
	@Override
	public int getKeyHashCode() {
		return getKey().hashCode();
	}

	@Override
	public Object getKey() {
		return contributorId;
	}

	@Override
	public boolean compare(KeyedElement other) {
		return contributorId.equals(((Contribution) other).contributorId);
	}

	public boolean shouldPersist() {
		return persist;
	}

	public void unlinkChild(int id) {
		// find index of the child being unlinked:
		int index = -1;
		for (int i = 2; i < children.length; i++) {
			if (children[i] == id) {
				index = i;
				break;
			}
		}
		if (index == -1)
			throw new InvalidRegistryObjectException();

		// copy all array except one element at index
		int[] result = new int[children.length - 1];
		System.arraycopy(children, 0, result, 0, index);
		System.arraycopy(children, index + 1, result, index, children.length - index - 1);

		// fix sizes
		if (index < children[EXTENSION_POINT] + 2)
			result[EXTENSION_POINT]--;
		else
			result[EXTENSION]--;

		children = result;
	}

	/**
	 * Contribution is empty if it has no children.
	 */
	public boolean isEmpty() {
		return (children[EXTENSION_POINT] == 0 || children[EXTENSION] == 0);
	}

	/**
	 * Find if this contribution has a children with ID = id.
	 * @param id possible ID of the child
	 * @return true: contribution has this child
	 */
	public boolean hasChild(int id) {
		for (int i = 2; i < children.length; i++) {
			if (children[i] == id)
				return true;
		}
		return false;
	}
}
