/**
 *   This file is part of Skript.
 *
 *  Skript is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Skript is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Skript.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Copyright 2011-2017 Peter Güttinger and contributors
 */
package ch.njol.skript.classes;

import ch.njol.skript.classes.data.DefaultComparators;
import ch.njol.skript.registrations.Comparators;

/**
 * Used to compare two objects of a different or the same type.
 * 
 * @author Peter Güttinger
 * @param <T1> ,
 * @param <T2> the types to compare
 * @see Comparators#registerComparator(Class, Class, Comparator)
 * @see DefaultComparators
 */
public interface Comparator<T1, T2> {
	
	/**
	 * represents a relation between two objects.
	 */
	public static enum Relation {
		EQUAL, NOT_EQUAL, GREATER, GREATER_OR_EQUAL, SMALLER, SMALLER_OR_EQUAL;
		
		/**
		 * Returns EQUAL for true or NOT_EQUAL for false
		 * 
		 * @param b
		 * @return <tt>b ? Relation.EQUAL : Relation.NOT_EQUAL</tt>
		 */
		public static Relation get(final boolean b) {
			return b ? Relation.EQUAL : Relation.NOT_EQUAL;
		}
		
		/**
		 * Gets a Relation from a difference: If i is 0, EQUAL is returned, if i is greater than 0, GREATER is returned, otherwise SMALLER.
		 * 
		 * @param i
		 * @return <tt>i == 0 ? Relation.EQUAL : i > 0 ? Relation.GREATER : Relation.SMALLER</tt>
		 */
		public static Relation get(final int i) {
			return i == 0 ? Relation.EQUAL : i > 0 ? Relation.GREATER : Relation.SMALLER;
		}
		
		/**
		 * Gets a Relation from a difference: If d is 0, EQUAL is returned, if d is greater than 0, GREATER is returned, otherwise SMALLER.
		 * 
		 * @param d
		 * @return <tt>d == 0 ? Relation.EQUAL : d > 0 ? Relation.GREATER : Relation.SMALLER</tt>
		 */
		public static Relation get(final double d) {
			return d == 0 ? Relation.EQUAL : d > 0 ? Relation.GREATER : Relation.SMALLER;
		}
		
		/**
		 * Test whether this relation is fulfilled if another is, i.e. if the parameter relation fulfils <code>X rel Y</code>, then this relation fulfils <code>X rel Y</code> as
		 * well.
		 * 
		 * @param other
		 * @return Whether this relation is part of the given relation, e.g. <code>GREATER_OR_EQUAL.is(EQUAL)</code> returns true.
		 */
		public boolean is(final Relation other) {
			if (other == this)
				return true;
			switch (this) {
				case EQUAL:
					return false;
				case NOT_EQUAL:
					return other == SMALLER || other == GREATER;
				case GREATER:
					return false;
				case GREATER_OR_EQUAL:
					return other == GREATER || other == EQUAL;
				case SMALLER:
					return false;
				case SMALLER_OR_EQUAL:
					return other == SMALLER || other == EQUAL;
			}
			assert false;
			return false;
		}
		
		/**
		 * Returns this relation's string representation, which is similar to "equal to" or "greater than".
		 */
		@Override
		public String toString() {
			switch (this) {
				case EQUAL:
					return "equal to";
				case NOT_EQUAL:
					return "not equal to";
				case GREATER:
					return "greater than";
				case GREATER_OR_EQUAL:
					return "greater than or equal to";
				case SMALLER:
					return "smaller than";
				case SMALLER_OR_EQUAL:
					return "smaller than or equal to";
			}
			assert false;
			return "";
		}
		
		/**
		 * Gets the inverse of this relation, i.e if this relation fulfils <code>X rel Y</code>, then the returned relation fulfils <code>!(X rel Y)</code>.
		 * 
		 * @return !this
		 */
		public Relation getInverse() {
			switch (this) {
				case EQUAL:
					return NOT_EQUAL;
				case NOT_EQUAL:
					return EQUAL;
				case GREATER:
					return SMALLER_OR_EQUAL;
				case GREATER_OR_EQUAL:
					return SMALLER;
				case SMALLER:
					return GREATER_OR_EQUAL;
				case SMALLER_OR_EQUAL:
					return GREATER;
			}
			assert false;
			return NOT_EQUAL;
		}
		
		/**
		 * Gets the relation which has switched arguments, i.e. if this relation fulfils <code>X rel Y</code>, then the returned relation fulfils <code>Y rel X</code>.
		 * 
		 * @return siht
		 */
		public Relation getSwitched() {
			switch (this) {
				case EQUAL:
					return EQUAL;
				case NOT_EQUAL:
					return NOT_EQUAL;
				case GREATER:
					return SMALLER;
				case GREATER_OR_EQUAL:
					return SMALLER_OR_EQUAL;
				case SMALLER:
					return GREATER;
				case SMALLER_OR_EQUAL:
					return GREATER_OR_EQUAL;
			}
			assert false;
			return NOT_EQUAL;
		}
		
		public boolean isEqualOrInverse() {
			return this == Relation.EQUAL || this == Relation.NOT_EQUAL;
		}
		
		public int getRelation() {
			switch (this) {
				case EQUAL:
				case NOT_EQUAL:
					return 0;
				case GREATER:
				case GREATER_OR_EQUAL:
					return 1;
				case SMALLER:
				case SMALLER_OR_EQUAL:
					return -1;
			}
			assert false;
			return 0;
		}
	}
	
	/**
	 * holds information about a comparator.
	 * 
	 * @param <T1> see {@link Comparator}
	 * @param <T2> dito
	 */
	public static class ComparatorInfo<T1, T2> {
		
		public Class<T1> c1;
		public Class<T2> c2;
		public Comparator<T1, T2> c;
		
		public ComparatorInfo(final Class<T1> c1, final Class<T2> c2, final Comparator<T1, T2> c) {
			this.c1 = c1;
			this.c2 = c2;
			this.c = c;
		}
		
		public Class<?> getType(final boolean first) {
			return first ? c1 : c2;
		}
		
	}
	
	Comparator<?, ?> equalsComparator = new Comparator<Object, Object>() {
		@Override
		public Relation compare(final Object o1, final Object o2) {
			return Relation.get(o1.equals(o2));
		}
		
		@Override
		public boolean supportsOrdering() {
			return false;
		}
	};
	
	/**
	 * Compares the given objects which may not be null. Returning GREATER/SMALLER means that the first parameter is greater/smaller.
	 * 
	 * @param o1 Non-null object
	 * @param o2 Non-null object
	 * @return the relation of the objects. Should neither return GREATER_OR_EQUAL nor SMALLER_OR_EQUAL.
	 */
	public Relation compare(T1 o1, T2 o2);
	
	/**
	 * @return whether this comparator supports ordering of elements or not.
	 */
	public boolean supportsOrdering();
	
}
