var Assert = require('assert');

/**
* @typedef module:libresignage/ui/controller.UIElem#UIElemSettings
*
* @property {HTMLElement|function} elem    The HTML element to attach to. This
*                                          can also be a function that returns
*                                          a HTMLElement.
* @property {function}             cond    The condition getter. This should
*                                          return true if the input should be
*                                          enabled and false otherwise. A user-
*                                          defined object is passed to this
*                                          function by UIElem.state().
* @property {function}             enabler The element enabler function. This
*                                          is called with true/false as the
*                                          only argument.
* @property {function}             defer   A function used for checking whether
*                                          events should be deferred. If this
*                                          function returns true, the current
*                                          event beind handled is cancelled.
*                                          Otherwise the handler from 'attach'
*                                          is called.
* @property {object}               attach  A dictionary of event handler
*                                          functions. The keys are the event
*                                          names.
*/

/**
* UI element base class.
*
* @memberof module:libresignage/ui/controller
*/
class UIElem {
	/**
	* Construct a new UIElem.
	*
	* You shouldn't directly construct this class. Use derivative classes
	* instead.
	*
	* @param
	*	{module:libresignage/ui/controller.UIElem#UIElemSettings}
	*	settings
	*	A settings object.
	*
	* @throws {AssertionError} If the settings object is invalid.
	*/
	constructor(args) {
		Assert.ok('elem' in args);
		Assert.ok('cond' in args);
		Assert.ok('enabler' in args);
		Assert.ok('attach' in args);
		Assert.ok('defer' in args);

		this.type = null;
		this.elem = args.elem;
		this.attach = args.attach;

		this.cond = args.cond;
		this.defer = args.defer;
		this.enabler = args.enabler;

		if (!this.enabler) { this.enabler = (e, s) => { e.disabled = !s; }}
		if (!this.defer) { this.defer = () => false; }
		if (!this.cond) { this.cond = () => true; }

		// Attach event listeners to 'elem'.
		for (let key in this.attach) {
			this.get_elem().addEventListener(key, (...evargs) => {
				let ret = null;
				if (
					(evargs[0].isTrusted && this.is_event_running())
					|| this.defer()
				) {
					console.log(`UIElem: '${key}' cancelled.`);
				} else {
					this.event_set_fired();

					ret = this.attach[key].apply(null, evargs);

					if (ret instanceof Promise) {
						ret.then(
							() => {
								this.event_set_finished();
							}
						).catch(
							e => {
								this.event_set_finished();
								throw e;
							}
						);
					} else {
						this.event_set_finished();
					}
				}
			});
		}
	}

	init(controller) {
		this.event_set_fired = controller.event_set_fired;
		this.event_set_finished = controller.event_set_finished;
		this.is_event_running = controller.is_event_running;
	}

	event_set_fired() {}
	event_set_finished() {}
	is_event_running() {}

	/**
	* Get the HTML element this UIElem controls.
	*
	* @return {HTMLElement} A HTML element.
	*/
	get_elem() {
		return (typeof this.elem === 'function') ? this.elem() : this.elem
	}

	/**
	* Manually enable/disable a UIElem.
	*
	* @param {boolean} state true = enable, false = disable
	*/
	enabled(state) { this.enabler(this.get_elem(), state); }

	/**
	* Enable/disable a UIElem based the return value of this.cond().
	*
	* @param {object} data A user-defined object passed to this.cond().
	*/
	state(data) {
		this.enabler(this.get_elem(), this.cond(data));
	}
}
module.exports = UIElem;
