/**
 * 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 { instanceSymbol } from "../constants.mjs";

export { Base };

/**
 * This is the base class from which the most classes are derived.
 *
 * This class has besides a `toString` which returns the json representation of the object
 * also a functionality to check if an object is an instance of a class.
 *
 * Therefore, the class has a static method <code>[Symbol.hasInstance](that)</code> which returns true if the object
 * is an instance of the class.
 *
 * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance">MDN Symbol.hasInstance</a>
 *
 * Derived classes should implement a static getter `instanceSymbol` which returns a unique symbol.
 *
 * <code lang="javascript">
 * static get [instanceSymbol]() {
 *    return Symbol.for("@schukai/monster/types/base");
 * }
 * </code>
 *
 * The class was formerly called Object.
 *
 * @since 1.5.0
 * @copyright Volker Schukai
 * @summary The base class for the most classes in the monster library
 */
class Base extends Object {
	/**
	 * @return {string}
	 */
	toString() {
		return JSON.stringify(this);
	}

	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 * @since 2.1.0
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/types/base");
	}

	/**
	 * This method is called by the `instanceof` operator.
	 * @param that
	 * @return {boolean}
	 * @since 2.1.0
	 */
	static [Symbol.hasInstance](that) {
		if (
			that === undefined ||
			that === null ||
			(typeof that !== "object" && typeof that !== "function")
		) {
			return false;
		}

		const thatClass = Object.getPrototypeOf(that);
		if (
			thatClass === undefined ||
			thatClass === null ||
			(typeof thatClass !== "object" && typeof thatClass !== "function")
		) {
			return false;
		}

		if (checkInstanceSymbol.apply(this, [thatClass]) === true) {
			return true;
		}

		// this call the static method of the super class, if there is one
		return super[Symbol.hasInstance](that);
	}
}

/**
 * this function checks if the class has a static getter <code>instanceSymbol</code>,
 * and if the value of this getter is equal to the
 *
 * @private
 * @param obj
 * @return {boolean|any|boolean}
 * @since 2.1.0
 */
function checkInstanceSymbol(obj) {
	if (this.hasOwnProperty(instanceSymbol) === false) {
		return false;
	}

	const proto = obj?.constructor;
	if (
		proto === undefined ||
		proto === null ||
		(typeof proto !== "object" && typeof proto !== "function")
	) {
		return false;
	}

	if (proto.hasOwnProperty(instanceSymbol) !== true) {
		return checkInstanceSymbol.apply(this, [obj.__proto__]);
	}

	const symbol = proto[instanceSymbol];
	if (symbol === undefined) {
		if (obj.__proto__) {
			return checkInstanceSymbol(obj.__proto__);
		} else {
			return false;
		}
	}

	if (symbol === this[instanceSymbol]) {
		return true;
	}

	return checkInstanceSymbol.apply(this, [obj.__proto__]);
}
