import { writeJson } from 'fs-extra';
import * as path from 'path'
import { Undecked } from '../Core';
import { Deck } from '../Decks/Deck';
import { Log } from '../Logger';

declare var Undecked: Undecked;

module.exports = class Page {
	pageFilePath: string;

	pageID: string;
	name: string;

	keys: Page_Config_Keys;

	saveTimeout: any;

	constructor(settings: Page_Config) {
		this.pageFilePath = path.join(Undecked.dataPath, 'pages', `${settings.pageID}.json`);

		this.pageID = settings.pageID;
		this.name = settings.name || `Unnamed page (${this.pageID})`;

		this.keys = settings.keys || {};

		this.ensureKeys();
	}

	save(callback?: () => void) {
		clearTimeout(this.saveTimeout);
		this.saveTimeout = setTimeout(() => {
			writeJson(this.pageFilePath, this.export(), (err) => {
				if (err) Log('error', `Error whilst saving page ${this.pageID}`, err.message);
				else Log('info', `Page ${this.pageID} has been saved`);
				if (callback) callback();
			});
		}, 10 * 1000);
	}

	export(): Page_Config {
		var keys = JSON.parse(JSON.stringify(this.keys));
		for (var y in keys) {
			for (var x in keys[y]) {
				var key: Page_Key = keys[y][x];
				if (key.state != undefined && key.state.type == 'ghost' && key.state.masterID != undefined) {
					var location = Undecked.Pages.KeyManager.getLocation(key.state.masterID);

					var page = Undecked.Pages.get(location.pageID);
					if (page) {
						var ghostMaster = page.getKey(location.x, location.y);

						key.appearence = ghostMaster.appearence;
						key.actions = ghostMaster.actions;
					}
				}
			}
		}
		return {
			pageID: this.pageID,
			name: this.name,
			keys
		};
	}

	getID(): string {
		return this.pageID;
	}

	ensureKeys() {
		for (let y = 0; y < 4; y++) {
			if (this.keys[y] == undefined) this.keys[y] = {};
			for (let x = 0; x < 8; x++) {
				if (this.keys[y][x] == undefined)
					this.keys[y][x] = {
						...defaultKey,
						id: null
					};

				if (this.keys[y][x].id == null || this.keys[y][x].id == undefined)
					this.keys[y][x].id = Undecked.Pages.KeyManager.generateNew();
				else Undecked.Pages.KeyManager.register(this.keys[y][x].id);

				if (this.keys[y][x].actions == undefined) this.keys[y][x].actions = {};

				if (this.keys[y][x].actions.up == undefined) this.keys[y][x].actions.up = {};

				if (this.keys[y][x].actions.down == undefined) this.keys[y][x].actions.down = {};

				if (this.keys[y][x].actions.latch == undefined) this.keys[y][x].actions.latch = {};

				if (this.keys[y][x].actions.unlatch == undefined) this.keys[y][x].actions.unlatch = {};

				if (this.keys[y][x].state.type == 'ghost') {
					if (this.keys[y][x].state.masterID != undefined) {
						var location = Undecked.Pages.KeyManager.getLocation(this.keys[y][x].state.masterID);
						var page = Undecked.Pages.get(location.pageID);
						if (page) {
							var key = page.getKey(location.x, location.y);
							if (key.state.ghostIDs == undefined) key.state.ghostIDs = [];
							key.state.ghostIDs.push(this.keys[y][x].id);
						}
					}
				}
			}
		}
	}

	setName(name: string) {
		this.name = name;
		this.save();

		Undecked.SocketServer.broadcastTo('home', 'page', 'updatename', this.pageID, name);
	}


	broadcastKeyUpdate(x: string, y: string, responseToken: string = '-1', key?: Page_Key) {
		Undecked.SocketServer.broadcastTo(
			'home',
			'page',
			'updatekey',
			this.pageID,
			x,
			y,
			key != undefined ? key : this.keys[y][x],
			responseToken
		);
	}

	updateKey(
		x: string,
		y: string,
		key: Page_Key,
		responseToken: string,
		originQuery?: string,
		force: boolean = false
	) {
		if (this.keys[y] != undefined && this.keys[y][x] != undefined) {
			//FIXME:GHOST still not working oging to bed now, I think i should rewrite it tomorrow.....
			/* When updating key check if key is a ghost, if so recall the function with the master key. Update all properties on master key as usual. If a key has ghostIDs, update the appearence and actions of the key. */

			//----------------------------------Validating key
			var masterResponseToken =
				originQuery == undefined
					? responseToken
					: `${this.pageID}_${x}_${y}` == originQuery ? responseToken : '-1';
			originQuery = `${this.pageID}_${x}_${y}`;

			var decks = Undecked.Decks.decks;
			var currentKey: Page_Key = this.keys[y][x];

			var grabBest = <d extends keyof any>(
				object: string,
				value: any,
				defaultValue: any,
				incommingFirst: boolean = true
			): any[d] => {
				if (key.state.type == 'empty') return defaultValue;

				if ((incommingFirst == true || force == true) && key[object] != undefined)
					if (value != null && key[object][value] != undefined) return key[object][value];
					else if (value == null) return key[object];

				if (currentKey[object] != undefined)
					if (value != null && currentKey[object][value] != undefined) return currentKey[object][value];
					else if (value == null) return currentKey[object];

				if (incommingFirst == false && key[object] != undefined)
					if (value != null && key[object][value] != undefined) return key[object][value];
					else if (value == null) return key[object];
				return defaultValue;
			};

			if (currentKey.state.type != 'ghost' && key.state.type == 'ghost') {
				//Becoming ghost
			} else if (currentKey.state.type == 'ghost' && (key.state.type == 'ghost' || key.state.type == 'custom')) {
				//Editing ghost
				if (key.state.type == 'custom') key.state.type = 'ghost';

				var masterID = currentKey.state.masterID;
				var masterLocation = Undecked.Pages.KeyManager.getLocation(masterID);
				var masterPage = Undecked.Pages.get(masterLocation.pageID);

				if (masterPage) {
					var masterKeyClone = JSON.parse(
						JSON.stringify(masterPage.getKey(masterLocation.x, masterLocation.y))
					);

					return masterPage.updateKey(
						masterLocation.x,
						masterLocation.y,
						{
							...masterKeyClone,
							state: {
								...masterKeyClone.state,
								confirm: key.state.confirm,
								toggle: key.state.toggle
							},
							appearence: key.appearence
						},
						responseToken,
						originQuery
					);
				}

				return console.log('Unable to find ghost master key');
			} else if (currentKey.state.type == 'ghost' && key.state.type != 'ghost') {
				var masterID = currentKey.state.masterID;
				var masterLocation = Undecked.Pages.KeyManager.getLocation(masterID);
				var masterPage = Undecked.Pages.get(masterLocation.pageID);

				if (masterPage) {
					var masterKey = masterPage.getKey(masterLocation.x, masterLocation.y);

					if (masterKey.state != undefined && masterKey.state.ghostIDs != undefined) {
						if (masterKey.state.ghostIDs.includes(currentKey.id))
							masterKey.state.ghostIDs.splice(masterKey.state.ghostIDs.indexOf(currentKey.id), 1);
					}
				}

				//Reverting from ghost
			} else if (
				currentKey.state.type == 'custom' &&
				key.state.type != 'custom' &&
				currentKey.state.ghostIDs != undefined &&
				currentKey.state.ghostIDs.length > 0
			) {
				//Master becoming other key
				var ghostIDs = currentKey.state.ghostIDs;
				var removeGhost = (ghostID: string) => {
					var ghostLocation = Undecked.Pages.KeyManager.getLocation(ghostID);
					var ghostPage = Undecked.Pages.get(ghostLocation.pageID);
					if (ghostPage) {
						var ghostKey = ghostPage.getKey(ghostLocation.x, ghostLocation.y);
						if (ghostKey.state != undefined && ghostKey.state.masterID == currentKey.id) {
							if (
								ghostPage.keys[ghostLocation.y] != undefined &&
								ghostPage.keys[ghostLocation.y][ghostLocation.x]
							) {
								ghostPage.keys[ghostLocation.y][ghostLocation.x] = {
									id: ghostKey.id,
									appearence: {},
									state: { confirm: false, toggle: false, type: 'empty' }
								};

								ghostPage.broadcastKeyUpdate(ghostLocation.x, ghostLocation.y);
							}
						}
					}
				};

				for (let i = 0; i < ghostIDs.length; i++) {
					removeGhost(ghostIDs[i]);
					continue;
				}
				currentKey.state.ghostIDs = null;
			} else {
				//Normal change
			}

			var id = currentKey.id != undefined ? currentKey.id : Undecked.Pages.KeyManager.generateNew();
			var _internal = currentKey._internal != undefined ? currentKey._internal : {};
			var state: Page_Key_State = {
				confirm: grabBest('state', 'confirm', false),
				toggle: grabBest('state', 'toggle', false),
				type: grabBest('state', 'type', 'empty'),
				masterID: grabBest('state', 'masterID', null, false),
				ghostIDs: grabBest('state', 'ghostIDs', null, false)
			};
			var actions: Page_Key_ActionsList =
				state.type == 'empty'
					? { down: {}, up: {}, latch: {}, unlatch: {} }
					: force == true
						? key.actions
						: currentKey.actions != undefined
							? currentKey.actions
							: { up: {}, down: {}, latch: {}, unlatch: {} };
			var appearence: Page_Key_Appearence = {
				background: grabBest('appearence', 'background', {}),
				image: grabBest('appearence', 'image', {}),
				text: grabBest('appearence', 'text', {})
			};

			this.keys[y][x] = {
				id,
				_internal,
				actions,
				state,
				appearence
			};

			//----------------------------------Updating decks & web ui
			var deckPages: { [pageID: string]: Deck[] } = {};
			for (var serialNumber in decks) {
				var deck = decks[serialNumber];

				if (deckPages[deck.getPageID()] == undefined) deckPages[deck.getPageID()] = [];
				deckPages[deck.getPageID()].push(deck);

				if (deck.getPageID() == this.pageID) {
					deck.updateKey(parseInt(x), parseInt(y));
				}
			}

			if (this.keys[y][x].state.type == 'ghost') {
				var masterLocation = Undecked.Pages.KeyManager.getLocation(this.keys[y][x].state.masterID);
				var masterPage = Undecked.Pages.get(masterLocation.pageID);

				if (masterPage) {
					var masterClone: Page_Key = JSON.parse(
						JSON.stringify(masterPage.getKey(masterLocation.x, masterLocation.y))
					);
					this.broadcastKeyUpdate(x, y, masterResponseToken, {
						...key,
						state: {
							...key.state,
							confirm: masterClone.state.confirm,
							toggle: masterClone.state.toggle
						},
						appearence: masterClone.appearence
					});
				}
			} else this.broadcastKeyUpdate(x, y, masterResponseToken);

			if (
				currentKey.state.type == 'custom' &&
				currentKey.state.ghostIDs != undefined &&
				currentKey.state.ghostIDs.length > 0
			) {
				//Updating ghost of this key
				var ghostAppearence = JSON.parse(JSON.stringify(appearence));
				ghostAppearence.system = { ghost: true };

				for (let i = 0; i < currentKey.state.ghostIDs.length; i++) {
					var ghostID = currentKey.state.ghostIDs[i];
					var ghostLocation = Undecked.Pages.KeyManager.getLocation(ghostID);
					var ghostPage = Undecked.Pages.get(ghostLocation.pageID);
					if (ghostPage) {
						var ghostKeyClone: Page_Key = JSON.parse(
							JSON.stringify(ghostPage.getKey(ghostLocation.x, ghostLocation.y))
						);
						if (ghostKeyClone.state != undefined && ghostKeyClone.state.masterID == id) {
							var responseTokenGhost =
								originQuery == undefined
									? responseToken
									: `${ghostLocation.pageID}_${ghostLocation.x}_${ghostLocation.y}` == originQuery
										? responseToken
										: '-1';

							var newGhostKey: Page_Key = {
								...ghostKeyClone,
								appearence: ghostAppearence
							};

							ghostPage.broadcastKeyUpdate(
								ghostLocation.x,
								ghostLocation.y,
								responseTokenGhost,
								newGhostKey
							);
							if (deckPages[ghostPage.getID()] != undefined)
								for (let i = 0; i < deckPages[ghostPage.getID()].length; i++) {
									var deck = deckPages[ghostPage.getID()][i];
									deck.setKey(ghostLocation.x, ghostLocation.y, newGhostKey);
								}
						} else {
							currentKey.state.ghostIDs.splice(i, 1);
							i--;
						}
					}
				}
			}

			this.save();
		}
	}

	setKeyInternal(x: number | string, y: number | string, property: string, value: any) {
		if (this.keys[y] != undefined && this.keys[y][x] != undefined) {
			this.keys[y][x]._internal[property] = value;

			this.save();
		}
	}

	getKey(x: number | string, y: number | string): Page_Key {
		if (this.keys[y] != undefined && this.keys[y][x] != undefined) return this.keys[y][x];

		return null;
	}

	requestKey(x: number | string, y: number | string): Page_Key {
		if (this.keys[y] != undefined && this.keys[y][x] != undefined) {
			if (
				this.keys[y][x].state != undefined &&
				this.keys[y][x].state.type == 'ghost' &&
				this.keys[y][x].state.masterID != undefined
			) {
				var key = JSON.parse(JSON.stringify(this.keys[y][x]));
				var location = Undecked.Pages.KeyManager.getLocation(key.state.masterID);

				var page = Undecked.Pages.get(location.pageID);
				if (page) {
					var ghostMasterClone: Page_Key = JSON.parse(JSON.stringify(page.getKey(location.x, location.y)));

					key.state = {
						...this.keys[y][x].state,
						confirm: ghostMasterClone.state.confirm,
						toggle: ghostMasterClone.state.toggle
					};
					key.appearence = ghostMasterClone.appearence;
					key.actions = ghostMasterClone.actions;

					return key;
				}
			} else return this.keys[y][x];
		}

		return null;
	}

	hasKey(x: number | string, y: number | string) {
		return this.keys[y] != undefined && this.keys[y][x] != undefined;
	}

	hasKeyWithID(keyID): boolean {
		for (let y = 0; y < 4; y++)
			for (let x = 0; x < 8; x++)
				if (this.keys[y] != undefined && this.keys[y][x] != undefined)
					if (this.keys[y][x].id == keyID) return true;

		return false;
	}

	getKeyLocationWithID(keyID): { x: number; y: number } {
		for (let y = 0; y < 4; y++)
			for (let x = 0; x < 8; x++)
				if (this.keys[y] != undefined && this.keys[y][x] != undefined)
					if (this.keys[y][x].id == keyID) return { x, y };

		return null;
	}

	getKeyTextList(): { x: number; y: number; text: string; id: string }[] {
		var list: { x: number; y: number; text: string; id: string }[] = [];

		for (let y = 0; y < 4; y++)
			for (let x = 0; x < 8; x++) {
				if (this.keys[y] != undefined && this.keys[y][x] != undefined) {
					var key: Page_Key = this.keys[y][x];
					if (key.appearence != undefined && key.appearence.text != undefined) {
						if (key.appearence.text.value != undefined && key.appearence.text.value.length > 0) {
							list.push({ x, y, text: key.appearence.text.value, id: key.id });
							continue;
						}
					}

					list.push({ x, y, text: null, id: key.id });
				} else list.push({ x, y, text: null, id: null });
			}

		return list;
	}

	getActionInstance(key: Page_Key, actionInstanceID: string): Page_Key_Action {
		if (key.actions != undefined) {
			if (key.actions.up != undefined && key.actions.up[actionInstanceID] != undefined)
				return key.actions.up[actionInstanceID];
			if (key.actions.down != undefined && key.actions.down[actionInstanceID] != undefined)
				return key.actions.down[actionInstanceID];
			if (key.actions.latch != undefined && key.actions.latch[actionInstanceID] != undefined)
				return key.actions.latch[actionInstanceID];
			if (key.actions.unlatch != undefined && key.actions.unlatch[actionInstanceID] != undefined)
				return key.actions.unlatch[actionInstanceID];
		}
		return null;
	}
};

var defaultKey: Page_Key = {
	id: null,
	state: {
		type: 'empty',
		toggle: false,
		confirm: false
	},
	actions: {
		up: {},
		down: {},
		latch: {},
		unlatch: {}
	},
	appearence: {
		text: {
			color: '#ffffff',
			size: 20,
			value: '',
			offsetX: 0,
			offsetY: 0
		}
	},
	_internal: {}
};
export interface Page {
	name: string;
	keys: Page_Config_Keys;

	save: (callback?: () => void) => void;
	export: () => Page_Config;

	getID: () => string;

	setName: (name: string) => void;
	updateKey: (
		x: string | number,
		y: string | number,
		key: Page_Key,
		responseToken: string,
		originQuery?: string,
		force?: boolean
	) => void;
	getKey: (x: number | string, y: number | string) => Page_Key;
	requestKey: (x: number | string, y: number | string) => Page_Key;
	hasKey: (x: number | string, y: number | string) => boolean;
	setKeyInternal: (x: number | string, y: number | string, property: string, value: any) => void;
	hasKeyWithID: (keyID) => boolean;
	getKeyLocationWithID: (keyID) => { x: number; y: number };
	getKeyTextList: () => { x: number; y: number; text: string; id: string }[];
	getActionInstance: (key: Page_Key, actionInstanceID: string) => Page_Key_Action;
	broadcastKeyUpdate: (x: number | string, y: number | string, responseToken?: string, key?: Page_Key) => void;
}

export interface Page_Config {
	pageID: string;
	name?: string;

	keys?: Page_Config_Keys;
}

export interface Page_Config_Keys {
	'0'?: {
		'0'?: Page_Key;
		'1'?: Page_Key;
		'2'?: Page_Key;
		'3'?: Page_Key;
		'4'?: Page_Key;
		'5'?: Page_Key;
		'6'?: Page_Key;
		'7'?: Page_Key;
		'8'?: Page_Key;
	};
	'1'?: {
		'0'?: Page_Key;
		'1'?: Page_Key;
		'2'?: Page_Key;
		'3'?: Page_Key;
		'4'?: Page_Key;
		'5'?: Page_Key;
		'6'?: Page_Key;
		'7'?: Page_Key;
		'8'?: Page_Key;
	};
	'2'?: {
		'0'?: Page_Key;
		'1'?: Page_Key;
		'2'?: Page_Key;
		'3'?: Page_Key;
		'4'?: Page_Key;
		'5'?: Page_Key;
		'6'?: Page_Key;
		'7'?: Page_Key;
		'8'?: Page_Key;
	};
	'3'?: {
		'0'?: Page_Key;
		'1'?: Page_Key;
		'2'?: Page_Key;
		'3'?: Page_Key;
		'4'?: Page_Key;
		'5'?: Page_Key;
		'6'?: Page_Key;
		'7'?: Page_Key;
		'8'?: Page_Key;
	};
}

export interface Page_Key {
	id?: string;
	state?: Page_Key_State;
	actions?: Page_Key_ActionsList;
	appearence: Page_Key_Appearence;
	_internal?: Page_Key_Internal;
}

export interface Page_Key_State {
	type: 'empty' | 'custom' | 'pageup' | 'pagedown' | 'currentpage' | 'ghost';
	toggle: boolean;
	confirm: boolean;
	masterID?: string;
	ghostIDs?: string[];
}

export interface Page_Key_ActionsList {
	up: Page_Key_Actions;
	down: Page_Key_Actions;
	latch: Page_Key_Actions;
	unlatch: Page_Key_Actions;
}
export interface Page_Key_Actions {
	[actionInstanceID: string]: Page_Key_Action;
}
export interface Page_Key_Action {
	integrationID: string;
	actionID: string;
	actionInstanceID: string;
	properties: { [property: string]: any };
	logs: { timestamp: number; type: 'error' | 'info' | 'warning'; text: string }[];
}

export interface Page_Key_Appearence {
	text?: {
		value: string;
		size: number;
		color: string;
		offsetX: number;
		offsetY: number;
	};
	image?: {
		address?: string;
		iconid?: string;
		iconstyle?: 'black' | 'white';
		size: number;
		offsetX: number;
		offsetY: number;
		rotation: number;
	};
	background?: {
		color: string;
	};
	system?: {
		border?: {
			color: string;
			thickness: number;
		};
		ghost?: boolean;
	};
}
export interface Page_Key_Internal {
	_toggle?: boolean;
}
