import { Undecked } from './Core';
import { Log } from './Logger';
import { Server } from 'http';
import { Server as SocketIO, Socket } from 'socket.io';
import { Config } from './FileHandler';
import { Page_Key, Page_Key_Action } from './Pages/Page';
import { Action_Settings, Integration_Connection_Field } from './Integrations/IntegrationsManager';
import { EditorAPI_Field } from './Integrations/EditorAPI';

declare var Undecked: Undecked;
declare var Config: Config;

export class SocketServer {
	io: Socket;
	constructor() {}

	start(server: Server, callback: () => void) {
		Log('info', 'Preparing SocketServer');
		this.io = require('socket.io')(server);

		this.listeners();

		var port = Config.ports.http;
		server.listen(port, () => {
			Log('info', `WebServer & SocketServer running at port ${port}`);
			callback();
		});
	}

	listeners() {
		this.io.on('connection', (socket: Socket) => {
			Log('info', 'Client connected');

			socket.on('init', (page: 'home') => {
				if (
					[
						'home'
					].includes(page)
				) {
					socket.join(page);

					switch (page) {
						case 'home':
							socket.emit('quality', Undecked.quality);
							socket.emit('pagelist', Undecked.Pages.getNames());
							break;
					}
				}
			});

			socket.on('page', (query: string, ...args: any) => {
				switch (query) {
					case 'request':
						var pageID: string = args[0];
						var callback: (err?: string, page?: any) => void = args[1];

						if (Undecked.Pages.exists(pageID)) {
							var page = Undecked.Pages.get(pageID);

							callback(null, page.export());
						} else callback('Page does not exist.');
						break;

					case 'setname':
						var pageID: string = args[0];
						var newName: string = args[1];

						if (Undecked.Pages.exists(pageID)) {
							var page = Undecked.Pages.get(pageID);
							page.setName(newName);
						}
						break;

					case 'setkey':
						var pageID: string = args[0];
						var x: string = args[1];
						var y: string = args[2];
						var key: Page_Key = args[3];
						var responseToken: string = args[4];

						if (Undecked.Pages.exists(pageID)) {
							var page = Undecked.Pages.get(pageID);
							page.updateKey(x, y, key, responseToken);
						}
						break;

					case 'getkey':
						var pageID: string = args[0];
						var x: string = args[1];
						var y: string = args[2];
						var getkey_callback: (key: Page_Key) => void = args[3];

						if (Undecked.Pages.exists(pageID)) {
							var page = Undecked.Pages.get(pageID);
							if (page.hasKey(x, y)) getkey_callback(page.requestKey(x, y));
						}
						break;

					case 'executekey':
						var pageID: string = args[0];
						var x: string = args[1];
						var y: string = args[2];

						if (Undecked.Pages.exists(pageID)) {
							var page = Undecked.Pages.get(pageID);
							if (page.hasKey(x, y)) {
								if (
									page.getKey(x, y).actions != undefined &&
									page.getKey(x, y).actions.up != undefined
								) {
									Undecked.Integrations.executeActions(page.getKey(x, y).actions.up, null);
								}
							}
						}
						break;

					case 'create':
						var pageName: string = args[0];

						Undecked.Pages.create(pageName, () =>
							Undecked.SocketServer.broadcastTo('home', 'pagelist', Undecked.Pages.getNames())
						);

						break;

					case 'setorder':
						var order: string[] = args[0];
						if (typeof order.includes == 'function') Undecked.Pages.setOrder(order);

						break;

					case 'operation':
						var operation: 'cut' | 'copy' | 'delete' = args[0];
						var originPageID = args[1];
						var originX = args[2];
						var originY = args[3];
						var destinationPageID = args[4];
						var destinationX = args[5];
						var destinationY = args[6];
						Undecked.Pages.handleOperation(
							operation,
							originPageID,
							originX,
							originY,
							destinationPageID,
							destinationX,
							destinationY
						);
						break;
				}
			});

			socket.on('actioneditor', (query: string, ...args: any[]) => {
				switch (query) {
					case 'start':
						var settings: Action_Settings = args[0];
						var callback: (error: string, actionEditorID: string) => void = args[1];

						var pageID: string = settings.pageID;
						var keyX: number = settings.keyX;
						var keyY: number = settings.keyY;

						var start = () => {
							var response = Undecked.Integrations.startEditor(settings);
							callback(response.error, response.actionEditorID);
						};

						var page = Undecked.Pages.get(pageID);
						if (page && page.hasKey(keyX, keyY)) {
							var key = page.getKey(keyX, keyY);

							if (key.state != undefined && key.state.type == 'ghost') {
								if (key.state.masterID != undefined) {
									var masterLocation = Undecked.Pages.KeyManager.getLocation(key.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(key.id)
										) {
											settings.pageID = masterLocation.pageID;
											settings.keyX = masterLocation.x;
											settings.keyY = masterLocation.y;
											start();
										}
									}
								}
							} else start();
						}

						break;

					case 'remove':
						var pageID: string = args[0];
						var x: string = args[1];
						var y: string = args[2];
						var actionInstanceID: string = args[3];
						var type: string = args[4];

						var remove = (key: Page_Key) => {
							if (key.actions != undefined && key.actions[type] != undefined) {
								if (key.actions[type][actionInstanceID] != undefined) {
									delete key.actions[type][actionInstanceID];
									page.save();
								}
							}
						};

						if (Undecked.Pages.exists(pageID)) {
							var page = Undecked.Pages.get(pageID);
							if (page.hasKey(x, y)) {
								var key = page.getKey(x, y);

								if (key.state != undefined && key.state.type == 'ghost') {
									if (key.state.masterID != undefined) {
										var masterLocation = Undecked.Pages.KeyManager.getLocation(key.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(key.id)
											)
												remove(masterKey);
										}
									}
								} else remove(key);
							}
						}
						break;

					case 'create':
						var pageID: string = args[0];
						var x: string = args[1];
						var y: string = args[2];
						var createtype: 'up' | 'down' | 'latch' | 'unlatch' = args[3];
						var integrationID: string = args[4];
						var actionID: string = args[5];
						var createCallback: (action: Page_Key_Action) => void = args[6];

						var create = (key: Page_Key) => {
							if (key.actions == undefined) key.actions = { up: {}, down: {}, latch: {}, unlatch: {} };
							if (key.actions[createtype] == undefined) key.actions[createtype] = {};

							var newActionInstanceID = Undecked.generateRandom(8, (checkValid: string) => {
								return key.actions[createtype][checkValid] == undefined;
							});
							key.actions[createtype][newActionInstanceID] = {
								integrationID,
								actionID,
								actionInstanceID: newActionInstanceID,
								properties: {},
								logs: []
							};

							createCallback(key.actions[createtype][newActionInstanceID]);
						};

						if (Undecked.Integrations.exists(integrationID)) {
							var integration = Undecked.Integrations.get(integrationID);
							if (integration.api.hasAction(actionID)) {
								if (Undecked.Pages.exists(pageID)) {
									var page = Undecked.Pages.get(pageID);
									if (page.hasKey(x, y)) {
										var key = page.getKey(x, y);

										if (key.state != undefined && key.state.type == 'ghost') {
											if (key.state.masterID != undefined) {
												var masterLocation = Undecked.Pages.KeyManager.getLocation(
													key.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(key.id)
													)
														create(masterKey);
												}
											}
										} else create(key);
									}
								}
							}
						}

						break;

					case 'instance':
						var actionEditorID = args[0];
						var instanceQuery = args[1];

						if (Undecked.Integrations.editorExists(actionEditorID)) {
							var EditorWrapper = Undecked.Integrations.getEditor(actionEditorID);

							switch (instanceQuery) {
								case 'ready':
									EditorWrapper.ready();
									break;

								case 'fields':
									var fields: EditorAPI_Field[] = args[2];
									if (typeof EditorWrapper.editor._change == 'function')
										EditorWrapper.editor._change(fields);
									break;

								case 'close':
									EditorWrapper.destroy();
									break;
							}
						}

						break;
				}
			});

			socket.on('connections', (query: string, ...args: any[]) => {
				switch (query) {
					case 'request':
						var integrationID: string = args[0];
						var connectionType: string = args[1];
						var requestCallback: (fields: Integration_Connection_Field[]) => void = args[2];

						var fields = <any>Undecked.Connections.getConnectionRequestData(integrationID, connectionType);
						requestCallback(fields);

						break;

					case 'create':
						var integrationID: string = args[0];
						var connectionType: string = args[1];
						var properties = args[2];
						var createCallback: (succeed: boolean, errormessage?: string) => void = args[3];

						Undecked.Connections.create(integrationID, connectionType, properties, createCallback);

						break;

					default:
						break;
				}
			});
		});
	}

	broadcastTo(group: string, header: string, ...args: any[]) {
		if (this.io) this.io.to(group).emit(header, ...args);
	}

	broadcast(header: string, ...args: any[]) {
		if (this.io) this.io.emit(header, ...args);
	}
}

// export interface SocketServer {
// 	start: (server: Server, callback: () => void) => void;
// 	broadcastTo: (group: string, header: string, ...args: any[]) => void;
// 	broadcast: (header: string, ...args: any[]) => void;
// }
