/*******************************************************************************
 * Copyright (c) 2000, 2011 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.jdt.internal.corext.refactoring.structure.constraints;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.HierarchyType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet;

/**
 * Optimized type sets for supertype constraint problems.
 */
public abstract class SuperTypeSet implements ITypeSet {

	/** Implementation of an empty set */
	private static class SuperTypeEmptySet extends SuperTypeSet {

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#chooseSingleType()
		 */
		@Override
		public final TType chooseSingleType() {
			return null;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#isEmpty()
		 */
		@Override
		public final boolean isEmpty() {
			return true;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#restrictedTo(org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet)
		 */
		@Override
		public final ITypeSet restrictedTo(final ITypeSet set) {
			return this;
		}

		/*
		 * @see java.lang.Object#toString()
		 */
		@Override
		public final String toString() {
			return "EMPTY"; //$NON-NLS-1$
		}
	}

	/** Implementation of a singleton */
	private static class SuperTypeSingletonSet extends SuperTypeSet {

		/** The type */
		private final TType fType;

		/**
		 * Creates a new super type singleton set.
		 *
		 * @param type the type
		 */
		private SuperTypeSingletonSet(final TType type) {
			fType= type;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#chooseSingleType()
		 */
		@Override
		public final TType chooseSingleType() {
			return fType;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#isEmpty()
		 */
		@Override
		public final boolean isEmpty() {
			return false;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#restrictedTo(org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet)
		 */
		@Override
		public final ITypeSet restrictedTo(final ITypeSet set) {
			final TType leftErasure= fType.getErasure();
			if (set instanceof SuperTypeUniverse) {
				return this;
			} else if (set instanceof SuperTypeSingletonSet) {
				if (this == set)
					return this;
				if (fType.isNullType())
					return this;
				final SuperTypeSingletonSet singleton= (SuperTypeSingletonSet) set;
				final TType rightErasure= singleton.fType.getErasure();
				if (leftErasure.isHierarchyType() && rightErasure.isHierarchyType()) {
					if (leftErasure.isGenericType() || rightErasure.isGenericType()) {
						if (rightErasure.equals(leftErasure) || ((HierarchyType) leftErasure).isSubType((HierarchyType) rightErasure))
							return this;
					}
				}
				if (rightErasure.isJavaLangObject())
					return this;
				if (leftErasure.canAssignTo(rightErasure))
					return this;
				return SuperTypeSet.getEmpty();
			} else if (set instanceof SuperTypeTuple) {
				if (fType.isNullType())
					return this;
				final SuperTypeTuple tuple= (SuperTypeTuple) set;
				final TType rightErasure= tuple.fSuperType.getErasure();
				if (leftErasure.isHierarchyType() && rightErasure.isHierarchyType()) {
					if (leftErasure.isGenericType() || rightErasure.isGenericType()) {
						if (rightErasure.equals(leftErasure) || ((HierarchyType) leftErasure).isSubType((HierarchyType) rightErasure))
							return this;
					}
				}
				if (rightErasure.isJavaLangObject())
					return this;
				if (leftErasure.canAssignTo(rightErasure))
					return this;
				return SuperTypeSet.createTypeSet(tuple.fSubType);
			} else if (set instanceof SuperTypeEmptySet) {
				return set;
			} else
				Assert.isTrue(false);
			return null;
		}

		/*
		 * @see java.lang.Object#toString()
		 */
		@Override
		public final String toString() {
			return fType.getPrettySignature();
		}
	}

	/** Implementation of a tuple */
	private static class SuperTypeTuple extends SuperTypeSet {

		/** The other type */
		private final TType fSubType;

		/** The super type */
		private final TType fSuperType;

		/**
		 * Creates a new super type tuple.
		 *
		 * @param subType the sub type
		 * @param superType the super type
		 */
		private SuperTypeTuple(final TType subType, final TType superType) {
			fSubType= subType;
			fSuperType= superType;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#chooseSingleType()
		 */
		@Override
		public final TType chooseSingleType() {
			return fSuperType;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#isEmpty()
		 */
		@Override
		public final boolean isEmpty() {
			return false;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#restrictedTo(org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet)
		 */
		@Override
		public final ITypeSet restrictedTo(final ITypeSet set) {
			if (set instanceof SuperTypeUniverse) {
				return this;
			} else if (set instanceof SuperTypeSingletonSet) {
				final SuperTypeSingletonSet singleton= (SuperTypeSingletonSet) set;
				final TType rightErasure= singleton.fType.getErasure();
				final TType subErasure= fSubType.getErasure();
				final TType superErasure= fSuperType.getErasure();
				if (subErasure.isHierarchyType() && superErasure.isHierarchyType() && rightErasure.isHierarchyType()) {
					if (subErasure.isGenericType() || superErasure.isGenericType() || rightErasure.isGenericType()) {
						if ((rightErasure.equals(subErasure) || ((HierarchyType) subErasure).isSubType((HierarchyType) rightErasure))
								&& (rightErasure.equals(superErasure) || ((HierarchyType) superErasure).isSubType((HierarchyType) rightErasure)))
							return this;
					}
				}
				if (rightErasure.isJavaLangObject())
					return this;
				if (subErasure.canAssignTo(rightErasure) && superErasure.canAssignTo(rightErasure))
					return this;
				return SuperTypeSet.createTypeSet(fSubType);
			} else if (set instanceof SuperTypeTuple) {
				return this;
			} else if (set instanceof SuperTypeEmptySet) {
				return set;
			} else
				Assert.isTrue(false);
			return null;
		}

		/*
		 * @see java.lang.Object#toString()
		 */
		@Override
		public final String toString() {
			return "[" + fSubType.getPrettySignature() + ", " + fSuperType.getPrettySignature() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
	}

	/** Implementation of the type universe */
	private static class SuperTypeUniverse extends SuperTypeSet {

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#chooseSingleType()
		 */
		@Override
		public final TType chooseSingleType() {
			return null;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#isEmpty()
		 */
		@Override
		public final boolean isEmpty() {
			return false;
		}

		/*
		 * @see org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet#restrictedTo(org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeSet)
		 */
		@Override
		public final ITypeSet restrictedTo(final ITypeSet set) {
			return set;
		}

		/*
		 * @see java.lang.Object#toString()
		 */
		@Override
		public final String toString() {
			return "UNIVERSE"; //$NON-NLS-1$
		}
	}

	/** The empty set */
	private static final ITypeSet fgEmpty= new SuperTypeEmptySet();

	/** The universe */
	private static final ITypeSet fgUniverse= new SuperTypeUniverse();

	/**
	 * Creates a new type set.
	 *
	 * @param type the type to contain, or <code>null</code>
	 * @return the type set, or the universe if <code>type</code> is <code>null</code>
	 */
	public static ITypeSet createTypeSet(final TType type) {
		if (type == null)
			return fgUniverse;
		return new SuperTypeSingletonSet(type);
	}

	/**
	 * Creates a new type set.
	 *
	 * @param subType the sub type
	 * @param superType the super type
	 * @return the type set, or the universe if <code>type</code> is <code>null</code>
	 */
	public static ITypeSet createTypeSet(final TType subType, final TType superType) {
		if (subType == null || superType == null)
			return fgUniverse;
		return new SuperTypeTuple(subType, superType);
	}

	/**
	 * Returns the empty set.
	 *
	 * @return the empty set
	 */
	public static ITypeSet getEmpty() {
		return fgEmpty;
	}

	/**
	 * Returns the universe set.
	 *
	 * @return the universe set
	 */
	public static ITypeSet getUniverse() {
		return fgUniverse;
	}

	private SuperTypeSet() {
	}
}
