/*
 * This file is part of the Companion project
 * Copyright (c) 2018 Bitfocus AS
 * Authors: William Viker <william@bitfocus.io>, Håkon Nessjøen <haakon@bitfocus.io>
 *
 * This program is free software.
 * You should have received a copy of the MIT licence as well as the Bitfocus
 * Individual Contributor License Agreement for companion along with
 * this program.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the Companion software without
 * disclosing the source code of your own applications.
 *
 */

const BankItemsBase = require('./ItemsBase');
const { isEqual, cloneDeep }  = require('lodash');

/**
 * Class used by the feedback controller to manage the feedback items
 * 
 * @extends BankItemsBase
 * @author Håkon Nessjøen <haakon@bitfocus.io>
 * @author Keith Rocheck <keith.rocheck@gmail.com>
 * @author William Viker <william@bitfocus.io>
 * @since 2.2.0
 */
class BankFeedbackItems extends BankItemsBase {

	/**
	 * The debugger for this class
	 * @type {debug.Debugger}
	 */
	debug = require('debug')('lib/Bank/FeedbackItems');

	/**
	 * Creates an item base object
	 * @param {Registry} registry - the core registry
	 * @param {(BankActionController|BankFeedbackController)} controller - the item's parent controller
	 * @param {string} logSource - module name to be used in logs
	 * @param {string} dbKey - the key to fetch from the database
	 */
	constructor(registry, controller, logSource, dbKey) {
		super(registry, controller, logSource, dbKey);
		this.styles = {};
	}

	/**
	 * Scrub a bank's feedback items to process the current status/style
	 * @param {number} page - the bank's page
	 * @param {number} bank - the bank number
	 */
	checkBankStatus(page, bank) {

		if (this.items[page] !== undefined && this.items[page][bank] !== undefined) {
			// find all instance-ids in items for bank
			for (let i = 0; i < this.items[page][bank].length; ++i) {
				this.checkStatus(page, bank, i);
			}
		}
	}

	/**
	 * Check an item's status and update its style
	 * @param {number} page - the item's page
	 * @param {number} bank - the item's bank
	 * @param {number} index - the item's index
	 */
	checkStatus(page, bank, index) {
		let result;

		if (this.items[page] !== undefined && this.items[page][bank] !== undefined && this.items[page][bank][index] !== undefined) {
			let item = this.items[page][bank][index];
			let instance = this.instance().getInstance(item.instance);
			let bankObj = this.bank().getBank(page, bank);

			if (instance !== undefined && bankObj !== undefined) {
				let definition;
				item.instance_id = item.instance; // backwards compatibility

				if (this.definitions[instance.id] !== undefined && this.definitions[instance.id][item.type] !== undefined) {
					definition = this.definitions[instance.id][item.type];
				}

				try {
					// Ask instance to check bank for custom styling
					if (definition !== undefined && definition.callback !== undefined && typeof definition.callback == 'function') {
						result = definition.callback(item, bankObj);
						this.setStyle(page, bank, index, result);
					} else if (typeof instance.feedback == 'function') {
						result = instance.feedback(item, bankObj);
						this.setStyle(page, bank, index, result);
					} else {
						this.debug('ERROR: instance ' + instance.label + ' does not have a feedback() function');
					}
				}
				catch(e) {
					this.system.emit('log', 'instance('+instance.label+')', 'warn', 'Error checking feedback: ' + e.message);
				}
			}
		}

		return result;
	}

	/**
	 * Delete an item
	 * @param {number} page - the item's page
	 * @param {number} bank - the item's bank
	 * @param {number} index - the item's index
	 * @access public
	 */
	deleteItem(page, bank, index) {
		super.deleteItem(page, bank, index);

		if (this.styles[page] !== undefined && this.styles[page][bank] !== undefined && this.styles[page][bank][index] !== undefined) {
			this.styles[page][bank].splice(index, 1);
		}

		this.controller.checkBankStyle(page, bank);
	}

	/**
	 * Get the items in a bank via a client socket
	 * @param {IO.Socket} client - the client socket sending the request
	 * @param {string} result - the name of the call to send the results back to the client
	 * @param {number} page - the bank's page
	 * @param {number} bank - the bank number
	 * @access public
	 */
	getBankByClient(client, result, page, bank) {
		let items = this.getBank(page, bank);
		
		for (let item in items) { // Backwards compatibility
			items[item].instance_id = items[item].instance;
		}

		client.emit(result, page, bank, items);
	}

	/**
	 * Get the current style for a bank's items
	 * @param {number} page - the item's page
	 * @param {number} bank - the item bank
	 * @param {boolean} clone - whether or not the return should be a deep clone
	 * @returns {BankStyle[]} the bank array
	 * @access public
	 */
	getBankStyles(page, bank, clone = false) {
		let out;

		if (this.styles[page] !== undefined && this.styles[page][bank] !== undefined) {
			if (clone === true) {
				out = cloneDeep(this.styles[page][bank]);
			}
			else {
				out = this.styles[page][bank];
			}
		}

		return out;
	}

	/**
	 * Get all the items for a specific instance
	 * @param {string} id - the instance id
	 * @param {boolean} clone - whether or not the return should be a deep clone
	 * @returns {Object} the items array
	 * @access public
	 */
	getInstanceItems(id, clone = false) {
		let items = super.getInstanceItems(id, clone);

		for (let item in items) { // Backwards compatibility
			items[item].instance_id = items[item].instance;
		}

		return items;
	}

	/**
	 * Get the current style for an item
	 * @param {number} page - the item's page
	 * @param {number} bank - the item's bank
	 * @param {number} index - the item's index
	 * @param {boolean} clone - whether or not the return should be a deep clone
	 * @returns {BankStyle} the items array
	 * @access public
	 */
	getStyle(page, bank, index, clone = false) {
		let out;

		if (this.styles[page] !== undefined && this.styles[page][bank] !== undefined && this.styles[page][bank][index] !== undefined) {
			if (clone === true) {
				out = cloneDeep(this.styles[page][bank][index]);
			}
			else {
				out = this.styles[page][bank][index];
			}
		}

		return out;
	}

	/**
	 * Unsubscribe, clear a bank, and save
	 * @param {number} page - the bank's page
	 * @param {number} bank - the bank number
	 * @access public
	 */
	resetBank(page, bank) {
		super.resetBank(page, bank);

		if (this.styles[page] !== undefined && this.styles[page][bank] !== undefined) {
			this.styles[page][bank] = [];
		}
	}

	/**
	 * Save the current return style from a feedback
	 * @param {number} page - the item's page
	 * @param {number} bank - the item's bank
	 * @param {number} index - the item's index
	 * @param {BankStyle} style - the current style
	 * @access protected
	 */
	setStyle(page, bank, index, style) {

		if (this.styles[page] === undefined) {
			this.styles[page] = {};
		}

		if (this.styles[page][bank] === undefined) {
			this.styles[page][bank] = [];
		}

		if (!isEqual(style, this.styles[page][bank][index])) {
			this.debug('Feedback changed style of bank ' + page + '.' + bank);
			this.styles[page][bank][index] = style;
		}
	}

	/**
	 * Update an option for an item, subscribe, and save
	 * @param {number} page - the item's page
	 * @param {number} bank - the item's bank
	 * @param {string} item - the item's id (`item.id`)
	 * @param {string} option - the option id/key
	 * @param {(string|string[]|number|boolean)} value - the new value
	 * @access public
	 */
	updateItemOption(page, bank, item, option, value) {
		super.updateItemOption(page, bank, item, option, value);

		let bp = this.getBank(page, bank);

		if (bp !== undefined) {
			for (let n in bp) {
				if (bp[n] !== undefined && bp[n].id === item) {
					this.checkStatus(page, bank, n);
					break;
				}
			}
		}

		this.controller.checkBankStyle(page, bank);
	}

	/**
	 * Update a bank item order by swapping two keys
	 * @param {number} page - the bank's page
	 * @param {number} bank - the bank number
	 * @param {number} oldIndex - the moving item's index
	 * @param {number} newIndex - the other index to swap with
	 * @access public
	 */
	updateItemOrder(page, bank, oldIndex, newIndex) {
		super.updateItemOrder(page, bank, oldIndex, newIndex);

		this.controller.checkBankStyle(page, bank);
	}
}

exports = module.exports = BankFeedbackItems;