/*******************************************************************************
 * Copyright (c) 2009, 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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember;

/**
 * Utility class used to resolve {@link IReference}s
 *
 * @since 1.0.0
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public final class ReferenceResolver {

	/**
	 * Constructor Private constructor, no instantiate
	 */
	private ReferenceResolver() {
	}

	/**
	 * Resolves retained references.
	 *
	 * @param references list of {@link IReference} to resolve
	 * @throws CoreException if something goes wrong
	 */
	public static void resolveReferences(List<IReference> references) throws CoreException {
		// sort references by target type for 'shared' resolution
		int refcount = references.size();
		Map<String, List<IReference>> sigtoref = new LinkedHashMap<>(refcount);

		List<IReference> refs = null;
		String key = null;
		List<Reference> methodDecls = new ArrayList<>(refcount);
		long start = System.currentTimeMillis();
		for (IReference ref : references) {
			if (ref.getReferenceKind() == IReference.REF_OVERRIDE) {
				methodDecls.add((Reference) ref);
			} else {
				key = createSignatureKey(ref);
				refs = sigtoref.get(key);
				if (refs == null) {
					refs = new ArrayList<>(20);
					sigtoref.put(key, refs);
				}
				refs.add(ref);
			}
		}

		long end = System.currentTimeMillis();
		if (ApiPlugin.DEBUG_REFERENCE_RESOLVER) {
			System.out.println("Reference resolver: split into " + methodDecls.size() + " method overrides and " + sigtoref.size() + " unique references (" + (end - start) + "ms)"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$
		}
		// resolve references
		start = System.currentTimeMillis();
		resolveReferenceSets(sigtoref);
		end = System.currentTimeMillis();
		if (ApiPlugin.DEBUG_REFERENCE_RESOLVER) {
			System.out.println("Reference resolver: resolved unique references in " + (end - start) + "ms"); //$NON-NLS-1$//$NON-NLS-2$
		}
		// resolve method overrides
		start = System.currentTimeMillis();
		for (Reference reference : methodDecls) {
			reference.resolve();
		}
		end = System.currentTimeMillis();
		if (ApiPlugin.DEBUG_REFERENCE_RESOLVER) {
			System.out.println("Reference resolver: resolved method overrides in " + (end - start) + "ms"); //$NON-NLS-1$//$NON-NLS-2$
		}
	}

	/**
	 * Resolves the collect sets of references.
	 *
	 * @param map the mapping of keys to sets of {@link IReference}s
	 * @throws CoreException if something bad happens
	 */
	private static void resolveReferenceSets(Map<String, List<IReference>> map) throws CoreException {
		IReference ref = null;
		for (List<IReference> refs : map.values()) {
			ref = refs.get(0);
			((Reference) ref).resolve();
			IApiMember resolved = ref.getResolvedReference();
			if (resolved != null) {
				for (IReference ref2 : refs) {
					((Reference) ref2).setResolution(resolved);
				}
			}
		}
	}

	/**
	 * Creates a unique string key for a given reference. The key is of the form
	 * "component X references type/member"
	 *
	 * <pre>
	 * [component_id]#[type_name](#[member_name]#[member_signature])
	 * </pre>
	 *
	 * @param reference reference
	 * @return a string key for the given reference.
	 */
	private static String createSignatureKey(IReference reference) {
		StringBuilder buffer = new StringBuilder();
		buffer.append(reference.getMember().getApiComponent().getSymbolicName());
		buffer.append("#"); //$NON-NLS-1$
		buffer.append(reference.getReferencedTypeName());
		if (reference.getReferenceType() == IReference.T_FIELD_REFERENCE) {
			buffer.append("#"); //$NON-NLS-1$
			buffer.append(reference.getReferencedMemberName());

		} else if (reference.getReferenceType() == IReference.T_METHOD_REFERENCE) {
			buffer.append("#"); //$NON-NLS-1$
			buffer.append(reference.getReferencedMemberName());
			buffer.append("#"); //$NON-NLS-1$
			buffer.append(reference.getReferencedSignature());
		}
		return buffer.toString();
	}
}
