/*
 * 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.
 *
 */

var debug         = require('debug')('lib/Bank/Controller');
var CoreBase      = require('../Core/Base');
var fs            = require('fs');
var { cloneDeep } = require('lodash');
var rgb           = require('../Graphics/Image').rgb;

var BankActionController   = require('./ActionController');
var BankFeedbackController = require('./FeedbackController');

class BankController extends CoreBase {

	static CurrentVersion = 2;

	/** @type {BaseActionController} */
	actions;
	/** @type {Object} */
	config;
	/** @type {BankFeedbackController} */
	feedback;
	/** @type {Object} */
	fields;

	constructor(registry) {
		super(registry, 'bank');

		this.actions  = new BankActionController(registry);
		this.feedback = new BankFeedbackController(registry);

		this.config = {};

		this.fields = {
			'png': [
				{
					type: 'textinput',
					id: 'text',
					label: 'Text',
					width: 7,
					default: ''
				},
				{
					type: 'dropdown',
					id: 'size',
					label: 'Font size',
					default: 'auto',
					choices: [ 
						{ id: '7',    label: '7pt' },
						{ id: '14',   label: '14pt' },
						{ id: '18',   label: '18pt' },
						{ id: '24',   label: '24pt' },
						{ id: '30',   label: '30pt' },
						{ id: '44',   label: '44pt' },
						{ id: 'auto', label: 'Auto'}
					],
					width: 3
				},
				{
					type: 'filepicker',
					id: 'png',
					label: '72x58 PNG',
					accept: 'image/png',
					width: 2,
					imageMinWidth: 72,
					imageMinHeight: 58,
					imageMaxWidth: 72,
					imageMaxHeight: 58
				},
				{
					type: 'alignmentcontrol',
					id: 'alignment',
					label: 'Text Alignment',
					width: 2,
					default: 'center:center'
				},
				{
					type: 'alignmentcontrol',
					id: 'pngalignment',
					label: 'PNG Alignment',
					width: 2,
					default: 'center:center'
				},
				{
					type: 'colorpicker',
					id: 'color',
					label: 'Color',
					width: 2,
					default: rgb(255,255,255)
				},
				{
					type: 'colorpicker',
					id: 'bgcolor',
					label: 'Background',
					width: 2,
					default: rgb(0,0,0)
				},
				{
					type: 'checkbox',
					id: 'latch',
					label: 'Latch/Toggle',
					width: 2,
					default: false
				},
				{
					type: 'checkbox',
					id: 'relative_delay',
					label: 'Relative Delays',
					width: 2,
					default: false
				}
			]
		}

		//this.checkVersion();

		this.loadDB();

		/* Variable jiu jitsu */
		this.system.on('variable_changed', this.variableChanged.bind(this));

		//this.system.on('bank_update', this.updateConfig.bind(this));

		this.system.on('bank_set_key', this.setBankField.bind(this));

		this.system.on('bank_changefield', this.updateBankField.bind(this));

		this.system.on('import_bank', this.importBank.bind(this));

		this.system.on('io_connect', (client) => {

			this.actions.clientConnect(client);

			client.on('graphics_preview_generate', (config, id) => {
				this.system.emit('graphics_preview_generate', config, (img) => {
					client.emit('graphics_preview_generate:' + id, img);
				});
			});

			client.on('bank_reset', (page, bank) => {
				this.system.emit('bank_reset', page, bank);
				client.emit('bank_reset', page, bank);
			});

			client.on('get_all_banks', () => {
				client.emit('get_all_banks:result', this.config);
			});

			client.on('get_bank', (page, bank) => {

				this.system.emit('get_bank', page, bank, (config) => {
					var fields = [];
					if (config.style !== undefined && this.fields[config.style] !== undefined) {
						fields = this.fields[config.style];
					}

					client.emit('get_bank:results', page, bank, config, fields);
					this.system.emit('skeleton-log', 'Running actions for ' + page + '.' + bank + ' - triggered by GUI');

				});
			});

			client.on('hot_press', (page, button, direction) => {
				debug("being told from gui to hot press",page,button,direction);
				this.system.emit('bank_pressed', page, button, direction);
			});

			client.on('bank_set_png', (page, bank, dataurl) => {

				if (!dataurl.match(/data:.*?image\/png/)) {
					this.system.emit('skeleton-log', 'Error saving png for bank ' + page + '.' + bank + ". Not a PNG file");
					client.emit('bank_set_png:result', 'error');
					return;
				}

				var data = dataurl.replace(/^.*base64,/,'');
				this.config[page][bank].png64 = data;
				//this.system.emit('bank_update', this.config);

				client.emit('bank_set_png:result', 'ok');
				this.graphics().invalidateBank(page, bank);
			});

			client.on('bank_changefield', this.setBankField.bind(this));

			client.on('bank_copy', this.copyBank.bind(this, client));

			client.on('bank_move', this.moveBank.bind(this, client));

			client.on('bank_style', (page, bank, style) => {
				this.system.emit('bank_style', page, bank, style, (fields) => {
					client.emit('bank_style:results', page, bank, this.config[page][bank], fields);
				});
			});
		});


		this.system.on('bank_style', this.setBankStyle.bind(this));

		this.system.on('bank_reset', this.resetBank.bind(this));

		this.system.on('bank_rename_variables', this.renameVariables.bind(this));

		this.system.on('get_bank', (page, bank, cb) => {
			cb(this.getBank(page, bank))
		});

		//this.system.on('bank_update_request', () => {
		//	this.system.emit('bank_update', this.config);
		//});

		this.system.on('bank_get15to32', (key, cb) => {
			cb(this.convertKey15to32(key));
		});
	}

	copyBank(client, pagefrom, bankfrom, pageto, bankto) {
		let exp = this.exportBank(pagefrom, bankfrom);
		this.importBank(pageto, bankto, exp);

		client.emit('bank_copy:result', null, 'ok');
	}

	convertKey15to32(key) {
		var rows = Math.floor(key / 5);
		var col = (key % 5) + 1;
		var res = (rows * 8) + col;

		if (res >= 32) {
			debug('assert: old config had bigger pages than expected');
			return 31;
		}

		return res;
	}

	exportBank(page, bank, extras = true) {
		let exp = {};

		exp.config = this.getBank(page, bank, true);

		exp.actions = this.actions.getBankActions(page, bank, true);
		exp.release_actions = this.actions.getBankReleaseActions(page, bank, true);
		exp.feedbacks = this.feedback.getBankFeedbacks(page, bank, true);

		if (extras === true) {
			exp.version = this.registry.getFileVersion();
			exp.type = 'bank';

			exp.instances = {};

			for (let key in exp.actions) {
				let item = exp.actions[key];

				if (exp.instances[action.instance] === undefined) {
					if (this.instance[item.instance] !== undefined) {
						exp.instances[item.instance] = this.instance().getInstanceConfig(item.instance);
					}
				}
			}

			for (let key in exp.release_actions) {
				let item = exp.release_actions[key];

				if (exp.instances[item.instance] === undefined) {
					if (this.instance[item.instance] !== undefined) {
						exp.instances[item.instance] = this.instance().getInstanceConfig(item.instance);
					}
				}
			}

			for (let key in exp.feedbacks) {
				let item = exp.feedbacks[key];

				if (exp.instances[item.instance] === undefined) {
					if (this.instance[item.instance] !== undefined) {
						exp.instances[item.instance] = this.instance().getInstanceConfig(item.instance);
					}
				}
			}
		}

		debug('Exported config for bank ' + page + '.' + bank);

		// This should be removed in the future
		if (cb !== undefined && typeof cb == 'function') {
			cb(exp);
		}

		return cb;
	}

	exportOldConfig(page, bank) {
		var exp = {};

		exp.config = cloneDeep(old_config[page][bank]);
		exp.instances = {};

		if (old_bank_actions[page] !== undefined) {
			exp.actions = cloneDeep(old_bank_actions[page][bank]);
		}

		if (old_bank_release_actions[page] !== undefined) {
			exp.release_actions = cloneDeep(old_bank_release_actions[page][bank]);
		}

		if (old_feedbacks[page] !== undefined) {
			exp.feedbacks = cloneDeep(old_feedbacks[page][bank]);
		}

		return exp;
	}

	getAll(clone = false) {
		let out;

		if (this.config !== undefined) {
			if (clone === true) {
				out = cloneDeep(this.config);
			}
			else {
				out = this.config;
			}
		}

		return out;
	}

	getBank(page, bank, clone = false) {
		let out;

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

		return out;
	}

	getBankStatus(page, bank) {
		return this.actions.getBankStatus(page, bank);
	}

	getBankWithFeedback(page, bank) {
		let out;

		if (this.config[page] !== undefined && this.config[page][bank] !== undefined && this.config[page][bank].style !== undefined) {
			out = cloneDeep(this.config[page][bank]);

			// Fetch feedback-overrides for bank
			let style = this.feedback.getBankStyle(page, bank);

			if (style !== undefined) {
				for (let key in style) {
					out[key] = style[key];
				}
			}
		}

		return out;
	}

	getPage(page, clone = false) {
		let out;

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

		return out;
	}

	getRunningActions(page, bank) {
		return this.actions.getRunningActions(page, bank);
	}

	importBank(page, bank, imp, cb) {
		this.resetBank(page, bank);

		if (imp.config === undefined) { // this should technically throw an exception
			imp.config = {};
		}

		if (imp.config.style !== undefined && imp.config.style == 'text') { // v2.0.0: 'text' button style is now 'png'
			imp.config.style = 'png';
		}

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

		this.actions.importBank(page, bank, imp.actions, imp.release_actions);
		this.feedback.importBank(page, bank, imp.feedbacks);

		// TODO: Rename variable definitions
		this.config[page][bank] = imp.config;


		this.graphics().invalidateBank(page, bank);
		this.system.emit('bank_update', this.config);

		this.db().setDirty();

		debug('Imported config to bank ' + page + '.' + bank);
		if (cb !== undefined && typeof cb == 'function') {
			cb();
		}
	}

	loadDB() {
		let res = this.db().getKey('bank', {});
		//debug("LOADING ------------",res);
		if (res !== undefined) {
			this.config = res;

			/* Fix pre-v1.1.0 and pre-v2.0.0 config for banks */
			for (var page in this.config) {
				for (var bank in this.config[page]) {
					if (this.config[page][bank].style !== undefined && this.config[page][bank].style.match(/^bigtext|smalltext$/)) {
						this.config[page][bank].size = this.config[page][bank].style == 'smalltext' ? 'small' : 'large';
						this.config[page][bank].style = 'png';
					}

					if (this.config[page][bank].style !== undefined && this.config[page][bank].style == 'text') {
						this.config[page][bank].style = 'png';
					}
				}
			}
		}
		else {
			for (var x = 1; x <= 99; x++) {
				if (this.config[x] === undefined) {
					this.config[x] = {};

					for (var y = 1; y <= global.MAX_BUTTONS; y++) {
						if (this.config[y] === undefined) {
							this.config[x][y] = {};
						}
					}
				}
			}

			this.db().setKey('page_config_version', BankController.CurrentVersion);
		}
	}

	moveBank(client, pagefrom, bankfrom, pageto, bankto) {
		let exp = this.exportBank(pagefrom, bankfrom);
		this.importBank(pageto, bankto, exp);
		this.bankReset(pagefrom, bankfrom);

		client.emit('bank_move:result', null, 'ok');
	}

	renameVariables(from, to) {
		for (var page in this.config) {
			for (var bank in this.config[page]) {
				if (this.config[page][bank].style !== undefined && this.config[page][bank].text !== undefined) {

					this.system.emit('variable_rename_callback', this.config[page][bank].text, from, to, (result) => {

						if (this.config[page][bank].text !== result) {
							debug('rewrote ' +  this.config[page][bank].text + ' to ' + result);
							this.config[page][bank].text = result;
						}
					});
				}
			}
		}
	}

	resetBank(page, bank) {

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

		this.config[page][bank] = {};

		this.actions.resetBank(page, bank);
		this.feedback.resetBank(page, bank);

		this.actions.checkBankStatus(page, bank, false);
		this.feedback.checkBankStyle(page, bank, false);
		this.graphics().invalidateBank(page, bank);
		//this.system.emit('bank_update', this.config);
		debug("bank_reset()", page, bank);
	}

	setBankField(page, bank, key, val) {

		if (this.config[page] !== undefined && this.config[page][bank] !== undefined) {
			this.config[page][bank][key] = val;
			this.db().setKey('bank', this.config );
			//this.db().setDirty();
		}
	}

	setBankStyle(page, bank, style, cb) {

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

		// If there was an image, delete it
		this.system.emit('configdir_get', (cfgDir) => {
			try {
				fs.unlink(this.cfgDir + '/banks/' + page + '_' + bank + '.png', () => {});
			} catch (e) {}
		});

		if (style == 'none' || this.config[page][bank] === undefined || this.config[page][bank].style === undefined) {
			this.config[page][bank] = undefined;
		}

		if (style == 'none') {
			client.emit('bank_style:results', page, bank, this.config[page][bank], undefined);
			//this.system.emit('bank_update', this.config, undefined);
			this.graphics().invalidateBank(page, bank);
			return;
		}

		else if (style == 'pageup') {
			this.system.emit('bank_reset', page, bank);
		}

		else if (style == 'pagenum') {
			this.system.emit('bank_reset', page, bank);
		}

		else if (style == 'pagedown') {
			this.system.emit('bank_reset', page, bank);
		}

		this.config[page][bank] = {
			style: style
		};

		var fields = [];
		if (this.fields[style] !== undefined) {
			fields = this.fields[style];
		}

		// Install default values
		for (var i = 0; i < fields.length; ++i) {
			if (fields[i].default !== undefined) {
				this.config[page][bank][fields[i].id] = fields[i].default;
			}
		}

		//this.system.emit('bank_update', this.config, fields);
		this.actions.checkBankStatus(page, bank, false);
		this.feedback.checkBankStatus(page, bank, false);
		this.graphics().invalidateBank(page, bank);

		if (cb !== undefined && typeof cb == 'function') {
			cb(fields);
		}
	}

	updateBankField(page, bank, key, val) {
		this.config[page][bank][key] = val;
		//this.system.emit('bank_update', this.config);
		this.graphics().invalidateBank(page, bank);
	}

	/*updateConfig(cfg) {
		debug('bank_update saving');
		this.config = cfg; // in case new reference
		this.db().setKey('bank', cfg );
		//this.system.emit('db_save');
	}*/

	upgrade15to32() {
		var old_config, old_bank_actions, old_bank_release_actions, old_feedbacks;

		old_config = this.getAll(true);
		this.config = this.db().getKey('bank', {});

		old_bank_actions = this.actions.getActions(true);
		old_bank_release_actions = this.actions.getReleaseActions(true);
		old_feedbacks = this.feedback.getFeedback(true);

		if (old_bank_actions === undefined) {
			old_bank_actions = {};
		}
		if (old_bank_actions === undefined) {
			old_bank_actions = {};
		}

		for (var page = 1; page <= 99; ++page) {
			if (this.config[page] === undefined) {
				this.config[page] = {};
			}

			// Reset
			for (var i = 0; i < 32; ++i) {
				this.resetBank(page, i+1);
			}

			// Add navigation keys
			this.importBank(page, 1,  { config: { style: 'pageup' } });
			this.importBank(page, 9,  { config: { style: 'pagenum' } });
			this.importBank(page, 17, { config: { style: 'pagedown' } });

			// Move keys around
			for (var b in old_config[page]) {
				var old = this.exportOldConfig(page, b);

				this.system.emit('import_bank', page, this.convertKey15to32(b-1) + 1, old);
			}
		}

		this.db().setKey('bank', this.config);
		this.db().setKey('page_config_version', BankController.CurrentVersion);
	}

	variableChanged(label, variable) {
		for (var page in this.config) {
			for (var bank in this.config[page]) {
				var data = this.config[page][bank];
				var reg = new RegExp('\\$\\(' + label + ':' + variable + '\\)');

				if (data.text !== undefined && data.text.match(reg)) {
					debug('variable changed in bank ' + page + '.' + bank);
					this.graphics().invalidateBank(page, bank);
				}
			}
		}
	}
}

exports = module.exports = BankController;