/*******************************************************************************
 * Copyright (c) 2008, 2013 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.pde.api.tools.internal.builder;

import java.util.HashMap;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.Factory;
import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers;
import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiType;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemTypes;

/**
 * Detects when a type illegally implements another type.
 *
 * @since 1.1
 */
public class IllegalImplementsProblemDetector extends AbstractIllegalTypeReference {

	/**
	 * Map of directly implemented interfaces to implement restricted
	 * super-interfaces
	 */
	private final HashMap<String, IApiType> fRestrictedInterfaces = new HashMap<>();

	@Override
	public int getReferenceKinds() {
		return IReference.REF_IMPLEMENTS;
	}

	@Override
	protected int getProblemKind() {
		return IApiProblem.ILLEGAL_IMPLEMENT;
	}

	@Override
	protected String getSeverityKey() {
		return IApiProblemTypes.ILLEGAL_IMPLEMENT;
	}

	@Override
	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
		try {
			if (super.considerReference(reference, monitor)) {
				return true;
			}
			IApiType type = (IApiType) reference.getMember();
			IApiType[] inters = type.getSuperInterfaces();
			IApiType inter = null;
			for (IApiType interLoop : inters) {
				if (interLoop.getName().equals(reference.getReferencedTypeName())) {
					inter = interLoop;
					break;
				}
			}
			if (inter != null && findRestrictedSuperinterfaces(type.getApiComponent(), reference.getReferencedTypeName(), inter)) {
				retainReference(reference);
				return true;
			}
		} catch (CoreException ce) {
			if (ApiPlugin.DEBUG_PROBLEM_DETECTOR) {
				ApiPlugin.log(ce);
			}
			checkIfDisposed(reference.getMember().getApiComponent(), monitor);
		}
		return false;
	}

	@Override
	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
		try {
			if (isIllegalType(reference)) {
				return super.isProblem(reference, monitor);
			}
			if (fRestrictedInterfaces.size() > 0) {
				IApiMember member = reference.getMember();
				if (member.getType() == IApiElement.TYPE) {
					IApiType itype = fRestrictedInterfaces.get(reference.getReferencedTypeName());
					return itype != null && !isImplemented(((IApiType) member).getSuperclass(), itype.getName());
				}
			}
			return true;
		} catch (CoreException ce) {
			if (ApiPlugin.DEBUG_PROBLEM_DETECTOR) {
				ApiPlugin.log(ce);
			}
			IApiMember member = reference.getMember();
			if (member != null) {
				checkIfDisposed(member.getApiComponent(), monitor);
			}
		}
		return super.isProblem(reference, monitor);
	}

	@Override
	protected String[] getMessageArgs(IReference reference) throws CoreException {
		String[] args = super.getMessageArgs(reference);
		if (!isIllegalType(reference) && fRestrictedInterfaces.size() > 0) {
			IApiType type = (IApiType) reference.getResolvedReference();
			IApiType inter = fRestrictedInterfaces.get(type.getName());
			if (inter != null) {
				String[] newargs = new String[args.length + 1];
				System.arraycopy(args, 0, newargs, 0, args.length);
				newargs[args.length] = getSimpleTypeName(inter);
				return newargs;
			}
		}
		return args;
	}

	@Override
	protected String[] getQualifiedMessageArgs(IReference reference) throws CoreException {
		String[] args = super.getQualifiedMessageArgs(reference);
		if (!isIllegalType(reference) && fRestrictedInterfaces.size() > 0) {
			IApiType type = (IApiType) reference.getResolvedReference();
			IApiType inter = fRestrictedInterfaces.get(type.getName());
			if (inter != null) {
				String[] newargs = new String[args.length + 1];
				System.arraycopy(args, 0, newargs, 0, args.length);
				newargs[args.length] = inter.getName();
				return newargs;
			}
		}
		return args;
	}

	@Override
	protected int getProblemFlags(IReference reference) {
		if (isIllegalType(reference)) {
			return super.getProblemFlags(reference);
		}
		IApiType type = (IApiType) reference.getMember();
		if (type.isLocal()) {
			return IApiProblem.INDIRECT_LOCAL_REFERENCE;
		}
		return IApiProblem.INDIRECT_REFERENCE;
	}

	/**
	 * Returns if the given type implements any of the given interfaces anywhere
	 * in its lineage
	 *
	 * @return true if all of the interfaces are implemented, false otherwise
	 */
	private boolean isImplemented(IApiType type, final String iname) throws CoreException {
		if (type == null) {
			return false;
		}
		if (isImplemented(iname, type.getSuperInterfaces())) {
			return true;
		}
		return isImplemented(type.getSuperclass(), iname);
	}

	/**
	 * Inspects the hierarchy of super-interfaces to determine if an interface
	 * with the given name is implemented or not
	 *
	 * @param iname the name of the interface to find
	 * @param interfaces the collection of interfaces to inspect
	 * @return true if the interface is implemented, false otherwise
	 */
	private boolean isImplemented(final String iname, IApiType[] interfaces) throws CoreException {
		if (interfaces.length == 0) {
			return false;
		}
		for (IApiType interfaceLoop : interfaces) {
			if (interfaceLoop.getName().equals(iname)) {
				return true;
			}
			if (isImplemented(iname, interfaceLoop.getSuperInterfaces())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Finds all of the implements restricted interfaces in the hierarchy of
	 * this given type
	 *
	 * @param originalcomponent the original {@link IApiComponent}
	 * @param entryinterface the name of the interface we originally entered the
	 *            recursion with
	 * @param type the {@link IApiType} to inspect the interfaces of
	 */
	private boolean findRestrictedSuperinterfaces(final IApiComponent originalcomponent, final String entryinterface, IApiType type) throws CoreException {
		IApiType[] inters = type.getSuperInterfaces();
		if (inters.length == 0) {
			return false;
		}
		IApiAnnotations annot = null;
		IApiComponent comp = null;
		for (IApiType inter : inters) {
			comp = inter.getApiComponent();
			if (comp == null) {
				continue;
			}
			if (!comp.equals(originalcomponent)) {
				annot = comp.getApiDescription().resolveAnnotations(Factory.typeDescriptor(inter.getName()));
				if (annot != null && RestrictionModifiers.isImplementRestriction(annot.getRestrictions())) {
					fRestrictedInterfaces.put(entryinterface, inter);
					return true;
				}
			}
			return findRestrictedSuperinterfaces(originalcomponent, entryinterface, inter);
		}
		return false;
	}
}
