/**
 * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact Volker Schukai.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { Base } from "../types/base.mjs";
import { isFunction } from "../types/is.mjs";

export { Comparator };

/**
 * The comparator allows a comparison function to be abstracted.
 *
 * The following are some examples of the application of the class.
 *
 * ```
 * new Comparator().lessThanOrEqual(2, 5) // ↦ true
 * new Comparator().greaterThan(4, 2) // ↦ true
 * new Comparator().equal(4, 4) // ↦ true
 * new Comparator().equal(4, 5) // ↦ false
 * ```
 *
 * You can also pass your own comparison function, and thus define the comparison function.
 *
 * ```
 * new Comparator(function (a, b) {
 *      if (a.v === b.v) return 0;
 *         return a.v < b.v ? -1 : 1;
 *      }).equal({v: 2}, {v: 2});  // ↦ true
 * ```
 *
 * @example /example/comparator/simple/ Simple example
 * @license AGPLv3
 * @since 1.3.0
 */
class Comparator extends Base {
	/**
	 * create new comparator
	 *
	 * @param {Monster.Util~exampleCallback} [callback] Comparator callback
	 * @throw {TypeError} unsupported type
	 * @throw {TypeError} impractical comparison
	 */
	constructor(callback) {
		super();

		if (isFunction(callback)) {
			this.compare = callback;
		} else if (callback !== undefined) {
			throw new TypeError("unsupported type");
		} else {
			// default compare function

			/**
			 *
			 * @param {*} a
			 * @param {*} b
			 * @return {integer} -1, 0 or 1
			 */
			this.compare = function (a, b) {
				if (typeof a !== typeof b) {
					throw new TypeError("impractical comparison", "types/comparator.mjs");
				}

				if (a === b) {
					return 0;
				}
				return a < b ? -1 : 1;
			};
		}
	}

	/**
	 * changes the order of the operators
	 *
	 * @return {Comparator}
	 */
	reverse() {
		const original = this.compare;
		this.compare = (a, b) => original(b, a);
		return this;
	}

	/**
	 * Checks if two variables are equal.
	 *
	 * @param {*} a
	 * @param {*} b
	 *
	 * @return {boolean}
	 */
	equal(a, b) {
		return this.compare(a, b) === 0;
	}

	/**
	 * Checks if variable `a` is greater than `b`
	 *
	 * @param {*} a
	 * @param {*} b
	 *
	 * @return {boolean}
	 */
	greaterThan(a, b) {
		return this.compare(a, b) > 0;
	}

	/**
	 * Checks if variable `a` is greater than or equal to `b`
	 *
	 * @param {*} a
	 * @param {*} b
	 *
	 * @return {boolean}
	 */
	greaterThanOrEqual(a, b) {
		return this.greaterThan(a, b) || this.equal(a, b);
	}

	/**
	 * Checks if variable `a` is less than or equal to `b`
	 *
	 * @param {*} a
	 * @param {*} b
	 *
	 * @return {boolean}
	 */
	lessThanOrEqual(a, b) {
		return this.lessThan(a, b) || this.equal(a, b);
	}

	/**
	 * Checks if variable a is less than b
	 *
	 * @param {*} a
	 * @param {*} b
	 *
	 * @return {boolean}
	 */
	lessThan(a, b) {
		return this.compare(a, b) < 0;
	}
}

/**
 * This is the description for the callback function used by the operator
 *
 * ```
 * new Comparator(function (a, b) {
 *      if (a.v === b.v) return 0;
 *         return a.v < b.v ? -1 : 1;
 *      }).equal({v: 2}, {v: 2});  // ↦ true
 * ```
 *
 * @callback Monster.Util~exampleCallback
 * @param {*} a
 * @param {*} b
 * @return {integer} -1, 0 or 1
 * @see Monster.Util.Comparator
 */
