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

import { Base } from "../types/base.mjs";
import { isInteger } from "../types/is.mjs";
import { validateFunction, validateInteger } from "../types/validate.mjs";

export { DeadMansSwitch };

/**
 * The dead man's switch allows setting a timer which can be reset again
 * and again within a defined period of time.
 *
 * @example /example/util/deadmansswitch/simple/ Simple example
 * @copyright Volker Schukai
 * @license AGPLv3
 * @since 1.29.0
 * @summary Class to be able to execute function chains
 */
class DeadMansSwitch extends Base {
	/**
	 * Create new dead man's switch
	 *
	 * @param {Integer} delay
	 * @param {function} callback
	 * @throw {TypeError} the arguments must be either integer or functions
	 * @throws {TypeError} value is not an integer
	 */
	constructor(delay, callback) {
		super();

		init.call(this, validateInteger(delay), validateFunction(callback));
	}

	/**
	 * @param delay
	 * @returns {DeadMansSwitch}
	 * @throws {Error} has already run
	 * @throws {Error} unsupported argument
	 */
	touch(delay) {
		if (this[internalSymbol]["isAlreadyRun"] === true) {
			throw new Error("has already run");
		}

		if (isInteger(delay)) {
			this[internalSymbol]["delay"] = delay;
		} else if (delay !== undefined) {
			throw new Error("unsupported argument");
		}

		clearTimeout(this[internalSymbol]["timer"]);
		initCallback.call(this);

		return this;
	}

	/**
	 * @throws {Error} has already run
	 * @returns {DeadMansSwitch}
	 */
	defuse() {
		if (this[internalSymbol]["isAlreadyRun"] === true) {
			throw new Error("has already run");
		}

		clearTimeout(this[internalSymbol]["timer"]);
		return this;
	}
}

/**
 * @private
 */
function initCallback() {
	this[internalSymbol]["timer"] = setTimeout(() => {
		this[internalSymbol]["isAlreadyRun"] = true;
		this[internalSymbol]["callback"]();
	}, this[internalSymbol]["delay"]);
}

/**
 * @private
 * @param {integer} delay
 * @param {function} callback
 */
function init(delay, callback) {
	this[internalSymbol] = {
		callback,
		delay,
		isAlreadyRun: false,
		timer: undefined,
	};

	initCallback.call(this);
}
