import * as StreamDeck from '@elgato-stream-deck/node'
import { Undecked } from '../Core';
import * as  Jimp from 'jimp'
import { createCanvas, loadImage, registerFont } from 'canvas';
import * as path from 'path'
import { Page_Key, Page_Key_Actions } from '../Pages/Page';
import { Log } from '../Logger';
import { fileURLToPath } from 'url';


registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-ExtraLight.ttf'), {
	family: 'Montserrat',
	weight: '200'
});
registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-Light.ttf'), {
	family: 'Montserrat',
	weight: '300'
});
registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-Regular.ttf'), {
	family: 'Montserrat',
	weight: '400'
});
registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-Medium.ttf'), {
	family: 'Montserrat',
	weight: '500'
});
registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-SemiBold.ttf'), {
	family: 'Montserrat',
	weight: '600'
});
registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-Bold.ttf'), {
	family: 'Montserrat',
	weight: '700'
});
registerFont(path.join(__filename, '..', '..', '..', '..', 'Static', 'font', 'Montserrat-ExtraBold.ttf'), {
	family: 'Montserrat',
	weight: '800'
});

declare var Undecked: Undecked;

export class Deck {
	Deck: StreamDeck.StreamDeck;

	online: boolean;
	ready: boolean;

	columns: number;
	rows: number;
	keys: number;
	iconsize: number;

	name: string;
	serialNumber: string;
	model: string;

	pageIndex: number;

	captures: {
		confirm: {
			enabled: boolean;
			callback: (result: boolean) => void;
			callbacktriggers: [number, number, any][];
		};
	};

	constructor(settings: Deck_Config, devicePath: string) {
		this.setDevicePath(devicePath);

		this.online = false;
		this.ready = false;

		this.name = settings.name;
		this.serialNumber = settings.serialNumber;
		this.model = settings.model;

		this.pageIndex = 0;

		this.captures = {
			confirm: {
				enabled: false,
				callback: null,
				callbacktriggers: []
			}
		};
	}

	export(): Deck_Config {
		return {
			name: this.name,
			serialNumber: this.serialNumber,
			model: this.model
		};
	}

	init() {
		if (this.online) {
			this.columns = this.Deck.KEY_COLUMNS;
			this.rows = this.Deck.KEY_ROWS;
			this.keys = this.columns * this.rows;
			this.iconsize = this.Deck.ICON_SIZE;

			this.fill({ appearence: { background: { color: '#ffffff' } } });
			setTimeout(() => {
				this.Deck.clearPanel();

				setTimeout(() => {
					var coords = this.getWaveCoords();

					for (let i = 0; i < coords.length; i++)
						this.setKey(
							coords[i][0],
							coords[i][1],
							{
								appearence: {
									image: {
										address: path.join(
											__filename,
											'..',
											'..',
											'..',
											'..',
											'Static',
											'logo',
											'single.png'
										),
										size: 100,
										offsetX: 0,
										offsetY: 0,
										rotation: 0
									}
								}
							},
							coords[i][2]
						);

					setTimeout(() => {
						var coords = this.getWaveCoords();

						for (let i = 0; i < coords.length; i++)
							this.setKey(
								coords[i][0],
								coords[i][1],
								{
									state: {
										type: 'empty',
										toggle: false,
										confirm: false
									},
									appearence: {}
								},
								coords[i][2]
							);

						setTimeout(() => {
							var fadeCounter = 100;
							var fadeInterval = setInterval(() => {
								this.Deck.setBrightness(fadeCounter);
								if (fadeCounter > 0) fadeCounter--;
								else {
									clearInterval(fadeInterval);

									this.ready = true;
									Log('info', `Deck ${this.name} is ready for rendering`);
									this.updateAll();

									fadeInterval = setInterval(() => {
										this.Deck.setBrightness(fadeCounter);
										if (fadeCounter < 100) fadeCounter++;
										else {
											clearInterval(fadeInterval);
										}
									}, 10);
								}
							}, 10);
						}, 1000);
					}, 1000);
				}, 1000);
			}, 300);
		}
	}

	setDevicePath(path: string) {
		if (path) {
			this.Deck = StreamDeck.openStreamDeck(path);
			this.listeners();

			this.online = true;

			this.Deck.on('error', (error) => {
				console.error(`Deck:${this.name}`, error);
				this.online = false;
			});
			this.init();
		}
	}

	handleKeyEvent(triggerKey: Page_Key, event: 'up' | 'down') {
		var instance = this;

		var handleEvent = (key: Page_Key) => {
			switch (key.state.type) {
				case 'custom':
					if (key.state.toggle == false) {
						if (key.state.confirm == false) {
							if (key.actions != undefined && key.actions[event] != undefined) {
								Undecked.Integrations.executeActions(key.actions[event], instance);
							}
						} else {
							if (event == 'up')
								this.showConfirm((result: boolean) => {
									if (result == true) {
										if (key.actions != undefined && key.actions.up != undefined)
											Undecked.Integrations.executeActions(key.actions.up, instance);
									}
								});
						}
					} else {
						if (event == 'up') {
							//When latched = true & When unlatch = false

							var state = false;
							if (key._internal != undefined && key._internal._toggle != undefined)
								state = key._internal._toggle;

							var location = Undecked.Pages.KeyManager.getLocation(key.id);
							if (location) {
								var handle = () => {
									Undecked.Pages
										.get(location.pageID)
										.setKeyInternal(location.x, location.y, '_toggle', !state);

									var namestate = !state ? 'latch' : 'unlatch';

									if (key.actions != undefined && key.actions[namestate] != undefined) {
										Undecked.Integrations.executeActions(key.actions[namestate], instance);
									}
								};

								if (key.state.confirm == true) {
									this.showConfirm((result: boolean) => {
										if (result == true) handle();
									});
								} else handle();
							}
						} else {
							if (key.state.confirm == false)
								if (key.actions != undefined && key.actions[event] != undefined)
									Undecked.Integrations.executeActions(key.actions[event], instance);
						}
					}

					break;

				case 'pageup':
					if (event == 'down') {
						var upPageID = Undecked.Pages.getIdByIndex(this.pageIndex - 1);
						if (upPageID) this.setPageID(upPageID);
					}
					break;

				case 'pagedown':
					if (event == 'down') {
						var downPageID = Undecked.Pages.getIdByIndex(this.pageIndex + 1);
						if (downPageID) this.setPageID(downPageID);
					}
					break;
			}
		};

		if (this.ready == true && triggerKey.state != undefined) {
			if (triggerKey.state.type == 'ghost') {
				if (triggerKey.state.masterID != undefined) {
					var masterLocation = Undecked.Pages.KeyManager.getLocation(triggerKey.state.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 &&
							masterKey.state.ghostIDs.includes(triggerKey.id)
						)
							handleEvent(masterKey);
					}
				}
			} else handleEvent(triggerKey);
		}
	}

	listeners() {
		var instance = this;
		function indexToXY(index: number) {
			var y = Math.floor(index / instance.columns);
			var x = index - Math.floor(index / instance.columns) * instance.columns;
			return { x, y };
		}
		this.Deck.on('down', (keyIndex) => {
			var coords = indexToXY(keyIndex);

			if (Undecked.Pages.exists(instance.getPageID())) {
				var page = Undecked.Pages.get(instance.getPageID());
				var key = page.getKey(String(coords.x), String(coords.y));

				for (var captureType in this.captures) {
					if (this.captures[captureType].enabled == true) {
						return;
					}
				}

				this.handleKeyEvent(key, 'down');
			}
		});

		this.Deck.on('up', (keyIndex) => {
			var coords = indexToXY(keyIndex);

			if (Undecked.Pages.exists(instance.getPageID())) {
				var page = Undecked.Pages.get(instance.getPageID());
				var key = page.getKey(String(coords.x), String(coords.y));

				for (var captureType in this.captures) {
					if (this.captures[captureType].enabled == true) {
						if (this.captures[captureType].callbacktriggers)
							for (let i = 0; i < this.captures[captureType].callbacktriggers.length; i++) {
								var callbacktrigger = this.captures[captureType].callbacktriggers[i];
								if (coords.x == callbacktrigger[0] && coords.y == callbacktrigger[1]) {
									this.captures[captureType].callback(callbacktrigger[2]);
									this.captures[captureType].enabled = false;
									this.captures[captureType].callback = null;
									return this.updateAll();
								}
							}
						return;
					}
				}

				this.handleKeyEvent(key, 'up');
			}
		});
	}

	setPageID(pageID: string) {
		var index = Undecked.Pages.getIndexById(pageID);
		if (index != this.pageIndex) {
			this.pageIndex = index;
			this.updateAll();
		}
	}

	getPageID() {
		return Undecked.Pages.getIdByIndex(this.pageIndex);
	}

	getName() {
		return this.name;
	}

	updateAll() {
		if (Undecked.Pages.exists(this.getPageID())) {
			var page = Undecked.Pages.get(this.getPageID());

			for (let x = 0; x < this.columns; x++) {
				for (let y = 0; y < this.rows; y++) {
					this.setKey(x, y, page.getKey(String(x), String(y)));
				}
			}
		}
	}

	updateKey(x: number, y: number) {
		for (var captureType in this.captures) if (this.captures[captureType].enabled == true) return;

		if (Undecked.Pages.exists(this.getPageID())) {
			var page = Undecked.Pages.get(this.getPageID());
			this.setKey(x, y, page.requestKey(String(x), String(y)));
		}
	}

	fill(key: Page_Key) {
		for (let x = 0; x < this.columns; x++) {
			for (let y = 0; y < this.rows; y++) {
				this.setKey(x, y, key);
			}
		}
	}

	setKey(x: number, y: number, key: Page_Key, delay = 0) {
		if (x < this.columns && y < this.rows) {
			var keyIndex = y * this.columns + x;

			if (keyIndex < this.keys) {
				setTimeout(() => applyChanges(this), delay);
			}

			var applyChanges = (instance: Deck) => {
				var canvas = createCanvas(Undecked.quality, Undecked.quality);
				var context = canvas.getContext('2d');
				context.textBaseline = 'middle';
				context.textAlign = 'center';

				if (key.state == undefined) key.state = <any>{ type: 'custom' };

				if (key.state.type == 'empty') {
					instance.Deck.clearKey(keyIndex);
				} else if (key.state.type == 'custom') {
					var appearence = key.appearence;

					render(appearence);
				} else if (key.state.type == 'ghost') {
					var appearence = key.appearence;
					appearence.system = {
						ghost: true
					};
					render(appearence);
				} else if (key.state.type == 'pageup') {
					render({
						text: { value: 'Up', color: '#ffffff', size: 18, offsetX: 0, offsetY: 25 },
						background: { color: '#4676b7' },
						image: {
							size: 100,
							rotation: 0,
							offsetX: 0,
							offsetY: -15,
							iconid: 'keyboard_arrow_up',
							iconstyle: 'white'
						},
						system: {
							border: {
								color: '#253e5e',
								thickness: 8
							}
						}
					});
				} else if (key.state.type == 'pagedown') {
					render({
						text: { value: 'Down', color: '#ffffff', size: 18, offsetX: 0, offsetY: -25 },
						background: { color: '#4676b7' },
						image: {
							size: 100,
							rotation: 0,
							offsetX: 0,
							offsetY: 15,
							iconid: 'keyboard_arrow_down',
							iconstyle: 'white'
						},
						system: {
							border: {
								color: '#253e5e',
								thickness: 8
							}
						}
					});
				} else if (key.state.type == 'currentpage') {
					render({
						text: {
							value: `Page\\n\\n${instance.pageIndex + 1}`,
							color: '#ffffff',
							size: 22,
							offsetX: 0,
							offsetY: 0
						},
						background: { color: '#4676b7' },
						system: {
							border: {
								color: '#253e5e',
								thickness: 8
							}
						}
					});
				}

				function render(appearence) {
					function background(cb: Function) {
						if (appearence.background != undefined) {
							context.fillStyle = appearence.background.color;
							context.fillRect(0, 0, Undecked.quality, Undecked.quality);
							context.fill();
						}
						cb();
					}

					function image(cb: Function) {
						if (appearence.image != undefined) {
							var imageAddress =
								appearence.image.address != undefined
									? appearence.image.address
									: appearence.image.iconid != undefined
										? Undecked.Icons.getPath(appearence.image.iconid, appearence.image.iconstyle)
										: null;

							var imageSize =
								appearence.image.size != undefined
									? appearence.image.size / 100 * Undecked.quality
									: Undecked.quality;
							if (imageAddress) {
								var centerX = Undecked.quality / 2 + appearence.image.offsetX / 100 * Undecked.quality;
								var centerY = Undecked.quality / 2 + appearence.image.offsetY / 100 * Undecked.quality;

								loadImage(imageAddress).then((image) => {
									context.save();
									context.translate(centerX, centerY);
									context.rotate(appearence.image.rotation * Math.PI / 180);
									context.drawImage(
										image,
										imageSize / 2 - imageSize,
										imageSize / 2 - imageSize,
										imageSize,
										imageSize
									);
									context.restore();
									cb();
								});
							} else cb();
						} else cb();
					}

					function text(cb: Function) {
						if (appearence.text != undefined) {
							context.fillStyle = appearence.text.color;
							context.font = `700 ${appearence.text.size * (Undecked.quality / 100)}px "Montserrat"`;

							var text = appearence.text.value;
							var lineHeight = appearence.text.size * (Undecked.quality / 100);

							var centerX = Undecked.quality / 2 + appearence.text.offsetX / 100 * (Undecked.quality * 2);
							var centerY = Undecked.quality / 2 + appearence.text.offsetY / 100 * Undecked.quality;
							var canvasYCounter = centerY;

							var words = text != undefined ? text.replace(/\\n/g, ' \\n ').split(' ') : '';
							var line = '';

							var totalLineHeight = 0;
							for (var n = 0; n < words.length; n++) {
								if (words[n].length == 0) continue;

								var testLine = line + words[n] + ' ';
								var metrics = context.measureText(testLine);
								var testWidth = metrics.width;
								if (words[n] != '\\n')
									if (testWidth > Undecked.quality && n > 0) {
										line = words[n] + ' ';
										totalLineHeight += lineHeight;
									} else {
										line = testLine;
									}
								else {
									totalLineHeight += lineHeight;
									line = '';
								}
							}

							line = '';
							canvasYCounter = canvasYCounter - totalLineHeight / 2;

							var firstSkip = false;
							for (var n = 0; n < words.length; n++) {
								if (words[n].length == 0) continue;
								var testLine = line + words[n] + ' ';
								var metrics = context.measureText(testLine);
								var testWidth = metrics.width;
								if (words[n] != '\\n')
									if (testWidth > Undecked.quality && n > 0) {
										context.fillText(line, centerX, canvasYCounter);
										line = words[n] + ' ';
										canvasYCounter += lineHeight;
									} else {
										line = testLine;
									}
								else {
									context.fillText(line, centerX, canvasYCounter);
									line = '';

									canvasYCounter += firstSkip ? lineHeight * 2 : lineHeight;
									if (firstSkip) firstSkip = false;
								}
							}
							context.fillText(line, centerX, canvasYCounter);
						}
						cb();
					}

					function system(cb: Function) {
						if (appearence.system != undefined) {
							if (appearence.system.border != undefined) {
								var relativeThickness = appearence.system.border.thickness / 100 * Undecked.quality;
								context.fillStyle = appearence.system.border.color;
								context.fillRect(0, 0, Undecked.quality, relativeThickness);
								context.rect(
									0,
									Undecked.quality - relativeThickness,
									Undecked.quality,
									relativeThickness
								);
								context.rect(0, 0, relativeThickness, Undecked.quality);
								context.rect(
									Undecked.quality - relativeThickness,
									0,
									relativeThickness,
									Undecked.quality
								);
								context.fill();
							}

							if (appearence.system.ghost == true) {
								// var imageAddress = path.join(
								// 	__filename,
								// 	'..',
								// 	'..',
								// 	'..',
								// 	'..',
								// 	'Static',
								// 	'icon',
								// 	'ghost.png'
								// );
								// var size = 50 / 100 * Undecked.quality;
								// loadImage(imageAddress).then((image) => {
								// 	context.save();
								// 	context.globalAlpha = 0.7;
								// 	context.translate(Undecked.quality / 2, Undecked.quality / 2);
								// 	context.drawImage(image, size / 2 - size, size / 2 - size, size, size);
								// 	context.restore();
								// 	cb();
								// });
							}
						}

						cb();
					}

					background(() => {
						image(() => {
							text(() => {
								system(() => {
									Jimp.read(canvas.toBuffer())
										.then((img) => {
											return img.resize(instance.iconsize, instance.iconsize);
										})
										.then((image) => {
											instance.Deck.fillKeyBuffer(keyIndex, image.bitmap.data, {
												format: 'rgba'
											});
										});
								});
							});
						});
					});
				}

				// if (keyData.color != undefined)
				// 	instance.Deck.fillKeyColor(keyIndex, keyData.color.r, keyData.color.g, keyData.color.b);

				// if (keyData.image != undefined) {
			};
		}
	}

	showConfirm(callback: (result: boolean) => void) {
		var cancel: [number, number, any] = [
			0,
			0,
			false
		];
		var deny: [number, number, any] = null;
		var confirm: [number, number, any] = null;

		if (this.columns % 2 == 0) {
			deny = [
				Math.floor(this.columns / 2) - 2,
				2,
				false
			];
			confirm = [
				Math.floor(this.columns / 2) + 1,
				2,
				true
			];
		} else {
			deny = [
				Math.floor(this.columns / 2) - 1,
				2,
				false
			];
			confirm = [
				Math.floor(this.columns / 2) + 1,
				2,
				true
			];
		}

		this.captures.confirm.enabled = true;
		this.captures.confirm.callbacktriggers = [
			cancel,
			deny,
			confirm
		];
		this.captures.confirm.callback = callback;

		this.Deck.clearPanel();

		this.setKey(confirm[0], confirm[1], {
			appearence: {
				image: {
					iconid: 'check',
					iconstyle: 'white',
					offsetX: 0,
					offsetY: 0,
					rotation: 0,
					size: 100
				},
				background: {
					color: '#4caf50'
				}
			}
		});

		this.setKey(deny[0], deny[1], {
			appearence: {
				image: {
					iconid: 'clear',
					iconstyle: 'white',
					offsetX: 0,
					offsetY: 0,
					rotation: 0,
					size: 100
				},
				background: {
					color: '#f44336'
				}
			}
		});

		this.setKey(cancel[0], cancel[1], {
			appearence: {
				image: {
					iconid: 'arrow_back',
					iconstyle: 'white',
					offsetX: 0,
					offsetY: 0,
					rotation: 0,
					size: 100
				},
				background: {
					color: '#000000'
				}
			}
		});

		var instance = this;
		var flickerList: { x: number; data: Page_Key }[] = [];
		function createText(value: string, x: number) {
			var data: Page_Key = {
				appearence: {
					text: {
						color: '#ffffff',
						offsetX: 1,
						offsetY: 0,
						size: 30,
						value
					},
					background: {
						color: '#000000'
					}
				}
			};
			instance.setKey(x, 1, data);
			flickerList.push({ x, data });
		}
		if (this.columns % 2 == 0) {
			createText('Are', 2);
			createText('you', 3);
			createText('sure', 4);
			createText('?', 5);
		} else {
			createText('Are', 1);
			createText('you', 2);
			createText('sure?', 3);
		}
		var flickerInterval = setInterval(() => {
			if (this.captures.confirm.enabled == true) {
				for (let i = 0; i < flickerList.length; i++) {
					var flickeritem = flickerList[i];
					if (flickeritem.data.appearence.background.color == '#000000') {
						flickeritem.data.appearence.background.color = '#ffffff';
						flickeritem.data.appearence.text.color = '#000000';
					} else {
						flickeritem.data.appearence.background.color = '#000000';
						flickeritem.data.appearence.text.color = '#ffffff';
					}
					instance.setKey(flickeritem.x, 1, flickeritem.data);
				}
			} else clearInterval(flickerInterval);
		}, 500);
	}

	getWaveCoords(): [number, number, number][] {
		var coords = [];
		for (let x0 = 0; x0 < 8; x0++) {
			var delay = x0;
			coords.push([
				x0,
				0,
				delay * 200
			]);
		}
		for (let x1 = -1; x1 < 8; x1++) {
			var delay = x1 + 1;
			if (x1 >= 0)
				coords.push([
					x1,
					1,
					delay * 200
				]);
		}
		for (let x2 = -2; x2 < 8; x2++) {
			var delay = x2 + 2;
			if (x2 >= 0)
				coords.push([
					x2,
					2,
					delay * 200
				]);
		}
		for (let x3 = -3; x3 < 8; x3++) {
			var delay = x3 + 3;
			if (x3 >= 0)
				coords.push([
					x3,
					3,
					delay * 200
				]);
		}
		return coords;
	}
};

// export interface Deck {
// 	export: () => Deck_Config;
// 	setDevicePath: (path: string) => void;
// 	getPageID: () => string;
// 	setPageID: (pageID: string) => void;
// 	setKey: (x: number, y: number, key: Page_Key) => void;
// 	updateKey: (x: number, y: number) => void;
// 	updateAll: () => void;
// 	getName: () => string;
// }

export interface Deck_Config {
	name: string;
	serialNumber: string;
	model: string;
}
