/**
 * 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 { Handler } from "./handler.mjs";
import { LogEntry } from "./logentry.mjs";

import { Base } from "../types/base.mjs";
import {
	validateInteger,
	validateObject,
	validateString,
} from "../types/validate.mjs";

export { Logger, ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF };

/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const ALL = 255;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const TRACE = 64;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const DEBUG = 32;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const INFO = 16;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const WARN = 8;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const ERROR = 4;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const FATAL = 2;
/**
 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
 * @type {number}
 */
const OFF = 0;

/**
 * The logger is a class that takes care of logging.
 *
 * @fragments /fragments/libraries/logging/logger/
 *
 * @example /examples/libraries/logging/logger/simple/ Write entries through a console handler
 * @example /examples/libraries/logging/logger/multiple-handlers/ Route the same log entry through multiple handlers
 * @example /examples/libraries/logging/logger/level-filtering/ Filter log output through handler log levels
 * @example /examples/libraries/logging/logger/remove-handler/ Remove a handler and verify that it no longer receives entries
 * @license AGPLv3
 * @since 1.5.0
 * @copyright Volker Schukai
 * @summary A logger for collecting log entries and forwarding them to one or more handlers.
 */
class Logger extends Base {
	/**
	 *
	 */
	constructor() {
		super();
		this.handler = new Set();
	}

	/**
	 *
	 * @param {Handler} handler
	 * @return {Logger}
	 * @throws {Error} the handler must be an instance of Handler
	 */
	addHandler(handler) {
		validateObject(handler);
		if (!(handler instanceof Handler)) {
			throw new Error("the handler must be an instance of Handler");
		}

		this.handler.add(handler);
		return this;
	}

	/**
	 *
	 * @param {Handler} handler
	 * @return {Logger}
	 * @throws {Error} the handler must be an instance of Handler
	 */
	removeHandler(handler) {
		validateObject(handler);
		if (!(handler instanceof Handler)) {
			throw new Error("the handler must be an instance of Handler");
		}

		this.handler.delete(handler);
		return this;
	}

	/**
	 * log Trace message
	 *
	 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF  (ALL = 0xff;OFF = 0x00;
	 *
	 * @param {*} arguments
	 * @return {Logger}
	 * @since 1.5.0
	 */
	logTrace(...args) {
		if (typeof args !== "object" || args[0] === null) {
			throw new Error("the first argument must be an object");
		}

		triggerLog.apply(this, [TRACE, ...args]);
		return this;
	}

	/**
	 * log Debug message
	 *
	 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF  (ALL = 0xff;OFF = 0x00;
	 *
	 * @param {*} arguments
	 * @return {Logger}
	 * @since 1.5.0
	 */
	logDebug(...args) {
		if (typeof args !== "object" || args[0] === null) {
			throw new Error("the first argument must be an object");
		}

		triggerLog.apply(this, [DEBUG, ...args]);
		return this;
	}

	/**
	 * log Info message
	 *
	 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF  (ALL = 0xff;OFF = 0x00;
	 *
	 *
	 * @param {*} arguments
	 * @return {Logger}
	 * @since 1.5.0
	 */
	logInfo(...args) {
		if (typeof args !== "object" || args[0] === null) {
			throw new Error("the first argument must be an object");
		}

		triggerLog.apply(this, [INFO, ...args]);
		return this;
	}

	/**
	 * log Warn message
	 *
	 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF  (ALL = 0xff;OFF = 0x00;
	 *
	 * @param {*} arguments
	 * @return {Logger}
	 * @since 1.5.0
	 */
	logWarn(...args) {
		if (typeof args !== "object" || args[0] === null) {
			throw new Error("the first argument must be an object");
		}

		triggerLog.apply(this, [WARN, ...args]);
		return this;
	}

	/**
	 * log Error message
	 *
	 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF  (ALL = 0xff;OFF = 0x00;
	 *
	 * @param {*} arguments
	 * @return {Logger}
	 * @since 1.5.0
	 */
	logError(...args) {
		if (typeof args !== "object" || args[0] === null) {
			throw new Error("the first argument must be an object");
		}

		triggerLog.apply(this, [ERROR, ...args]);
		return this;
	}

	/**
	 * log Fatal message
	 *
	 * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF  (ALL = 0xff;OFF = 0x00;
	 *
	 * @param {*} arguments
	 * @return {Logger}
	 * @since 1.5.0
	 */
	logFatal(...args) {
		if (typeof args !== "object" || args[0] === null) {
			throw new Error("the first argument must be an object");
		}

		triggerLog.apply(this, [FATAL, ...args]);
		return this;
	}

	/**
	 * Labels
	 *
	 * @param {integer} level
	 * @return {string}
	 */
	getLabel(level) {
		validateInteger(level);

		if (level === ALL) return "ALL";
		if (level === TRACE) return "TRACE";
		if (level === DEBUG) return "DEBUG";
		if (level === INFO) return "INFO";
		if (level === WARN) return "WARN";
		if (level === ERROR) return "ERROR";
		if (level === FATAL) return "FATAL";
		if (level === OFF) return "OFF";

		return "unknown";
	}

	/**
	 * Level
	 *
	 * @param {string} label
	 * @return {integer}
	 */
	getLevel(label) {
		validateString(label);

		if (label === "ALL") return ALL;
		if (label === "TRACE") return TRACE;
		if (label === "DEBUG") return DEBUG;
		if (label === "INFO") return INFO;
		if (label === "WARN") return WARN;
		if (label === "ERROR") return ERROR;
		if (label === "FATAL") return FATAL;
		if (label === "OFF") return OFF;

		return 0;
	}
}

/**
 * Log triggern
 *
 * @param {integer} loglevel
 * @param {*} args
 * @return {Logger}
 * @private
 */
function triggerLog(loglevel, ...args) {
	for (const handler of this.handler) {
		handler.log(new LogEntry(loglevel, args));
	}

	return this;
}
