/*******************************************************************************
 * Copyright (c) 2009, 2019 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:
 *     Mateusz Wenus <mateusz.wenus@gmail.com> - [override method] generate in declaration order [code generation] - https://bugs.eclipse.org/bugs/show_bug.cgi?id=140971
 *     IBM Corporation - bug fixes
 *     Red Hat Inc. - refactored to jdt.core.manipulation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.util;

import java.util.Comparator;

import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;

/**
 * A comparator which sorts methods (IMethodBinding) of a type passed as constructor argument,
 * according to their order in source files. More formally, if <code>m1</code> and <code>m2</code>
 * are methods of type <code>T</code> then according to this comparator <code>m1</code> is less than
 * <code>m2</code> iff one of following holds:
 * <ul>
 * <li><code>m1</code> and <code>m2</code> are defined in the same type (<code>T</code> or any
 * supertype of <code>T</code>), that type has a source attachment and <code>m1</code> appears
 * before <code>m2</code> in source of that type</li>
 * <li><code>m1</code> and <code>m2</code> are defined in the same type (<code>T</code> or any
 * supertype of <code>T</code>), that type doesn't have a source attachment and name of
 * <code>m1</code> alphabetically precedes name of <code>m2</code></li>
 *
 * <li><code>m2</code> is defined in <code>T</code> and <code>m1</code> is defined in any supertype
 * of <code>T</code></li>
 * <li><code>m2</code> is defined in a superclass of <code>T</code> and <code>m1</code> is defined
 * in a superinterface of <code>T</code></li>
 * <li><code>m1</code> and <code>m2</code> are defined in different superclasses of <code>T</code>
 * and a class which defines <code>m1</code> extends class which defines <code>m2</code>
 * <li><code>m1</code> and <code>m2</code> are defined in different superinterfaces of
 * <code>T</code> and an interface which defines <code>m2</code> appears before an interface which
 * defines <code>m1</code> in <code>implements</code> clause of declaration of type <code>T</code></li>
 * </ul>
 */
public class MethodsSourcePositionComparator implements Comparator<IMethodBinding> {

	private final ITypeBinding fTypeBinding;

	public MethodsSourcePositionComparator(ITypeBinding typeBinding) {
		if (typeBinding == null) {
			throw new IllegalArgumentException();
		}
		fTypeBinding= typeBinding;
	}

	@Override
	public int compare(IMethodBinding firstMethodBinding, IMethodBinding secondMethodBinding) {
		if (firstMethodBinding == null || secondMethodBinding == null) {
			return 0;
		}
		ITypeBinding firstMethodType= firstMethodBinding.getDeclaringClass();
		ITypeBinding secondMethodType= secondMethodBinding.getDeclaringClass();

		if (firstMethodType.equals(secondMethodType)) {
			return compareInTheSameType(firstMethodBinding, secondMethodBinding);
		}

		if (firstMethodType.equals(fTypeBinding)) {
			return 1;
		}
		if (secondMethodType.equals(fTypeBinding)) {
			return -1;
		}

		ITypeBinding type= fTypeBinding;
		int count= 0, firstCount= -1, secondCount= -1;
		while ((type= type.getSuperclass()) != null) {
			if (firstMethodType.equals(type)) {
				firstCount= count;
			}
			if (secondMethodType.equals(type)) {
				secondCount= count;
			}
			count++;
		}
		if (firstCount != -1 && secondCount != -1) {
			return (firstCount - secondCount);
		}
		if (firstCount != -1 && secondCount == -1) {
			return 1;
		}
		if (firstCount == -1 && secondCount != -1) {
			return -1;
		}

		ITypeBinding[] interfaces= fTypeBinding.getInterfaces();
		for (ITypeBinding interface1 : interfaces) {
			if (firstMethodType.equals(interface1)) {
				return 1;
			}
			if (secondMethodType.equals(interface1)) {
				return -1;
			}
		}
		return 0;
	}

	private int compareInTheSameType(IMethodBinding firstMethodBinding, IMethodBinding secondMethodBinding) {
		try {
			IMethod firstMethod= (IMethod)firstMethodBinding.getJavaElement();
			IMethod secondMethod= (IMethod)secondMethodBinding.getJavaElement();
			if (firstMethod == null || secondMethod == null) {
				return 0;
			}
			ISourceRange firstSourceRange= firstMethod.getSourceRange();
			ISourceRange secondSourceRange= secondMethod.getSourceRange();

			if (!SourceRange.isAvailable(firstSourceRange) || !SourceRange.isAvailable(secondSourceRange)) {
				return firstMethod.getElementName().compareTo(secondMethod.getElementName());
			} else {
				return firstSourceRange.getOffset() - secondSourceRange.getOffset();
			}
		} catch (JavaModelException e) {
			return 0;
		}
	}
}
