import { Undecked } from '../Core';
import * as path from 'path'
import { Log } from '../Logger';
import { IntegrationAPI } from './IntegrationApi';
import { EditorAPI, EditorAPI_Field } from './EditorAPI';
import { Page_Key_Actions } from '../Pages/Page';
import { Deck } from '../Decks/Deck';

import * as fs from 'fs-extra';
var { ensureDir, pathExists, readdir, readJSON, readJson } = fs;

declare var Undecked: Undecked;

export class IntegrationsManager {
	integrations: IntegrationsManager_Integrations;

	openEditors: { [editorID: string]: IntegrationsManager_ActionEditor };

	constructor() {
		this.integrations = {};
		this.openEditors = {};
	}

	load(callback: () => void) {
		Log('info', 'Loading integrations');
		ensureDir(path.join(Undecked.dataPath, 'custom_integrations'), (err) => {
			if (err) throw err;

			this.loadIntegrations(callback);
		});
	}

	loadIntegrations(callback: () => void) {
		var instance = this;

		var integrations = { buildin: [], custom: [] };
		readdir(path.join(__filename, '..', 'buildin'), (err, buildinfiles) => {
			if (err) throw err;
			integrations.buildin = buildinfiles;
			readdir(path.join(Undecked.dataPath, 'custom_integrations'), (err, customfiles) => {
				if (err) throw err;
				integrations.custom = customfiles;
				var loaded = { buildin: 0, custom: 0 };
				function loadIntegrations(type: 'buildin' | 'custom', cb: Function, i = 0) {
					if (integrations[type][i]) {
						var integrationID = integrations[type][i];
						var integrationAddress =
							type == 'buildin'
								? path.join(__filename, '..', 'buildin', integrationID, 'integration')
								: path.join(Undecked.dataPath, 'custom_integrations', integrationID, 'integration');

						var waitInterval;
						try {
							instance.integrations[integrationID] = {
								type,
								api: null,
								connectionslist: [],
								connectionsmap: {},
								integration: require(integrationAddress)
							};

							var waitCounter = 0;
							waitInterval = setInterval(() => {
								if (
									instance.integrations[integrationID].integration != undefined &&
									instance.integrations[integrationID].integration.main != undefined
								) {
									clearInterval(waitInterval);
									loaded[type]++;
									loadIntegrations(
										type,
										() => {
											Log('info', `Loaded ${type} integration '${integrationID}'`);
											cb();
										},
										i + 1
									);
								} else {
									waitCounter++;

									if (waitCounter > 200) {
										clearInterval(waitInterval);
										delete instance.integrations[integrationID];

										Log(
											'error',
											`Unable to load ${type} integration '${integrationID}': Loading timeout`
										);
										loadIntegrations(type, cb, i + 1);
									}
								}
							}, 10);
						} catch (error) {
							clearInterval(waitInterval);
							Log('error', `Unable to load ${type} integration '${integrationID}': ${error.message}`);
							loadIntegrations(type, cb, i + 1);
						}
					} else cb();
				}

				loadIntegrations('buildin', () => {
					loadIntegrations('custom', () => {
						for (var integrationID in instance.integrations) {
							var integration = instance.integrations[integrationID];
							// for (let i = 0; i < integration.integration.actions.length; i++) {
							// 	if (integration.integration.actions[i] != undefined)
							// 		integration.actionslist.push(integration.integration.actions[i].id);
							// 	else
							// 		Log(
							// 			`warn`,
							// 			`Invalid action of ${integrationID} ${JSON.stringify(
							// 				integration.integration.actions[i],
							// 				null,
							// 				4
							// 			)}`
							// 		);
							// }
							if (integration.integration.connections != undefined)
								for (let i = 0; i < integration.integration.connections.length; i++) {
									integration.connectionslist.push(integration.integration.connections[i].type);
									integration.connectionsmap[integration.integration.connections[i].type] =
										integration.integration.connections[i];
								}
							integration.api = new IntegrationAPI({ integrationID, integration });
							try {
								integration.integration.main(integration.api);
							} catch (error) {
								Log('error', `Error in '${integrationID}'`, error.message, error.stack);
							}
						}
						Log('info', `Loaded ${loaded.buildin} buildin integrations and ${loaded.custom} integrations`);
						callback();
					});
				});
			});
		});
	}

	exists(integrationID: string): boolean {
		return this.integrations[integrationID] != undefined;
	}

	get(integrationID: string): IntegrationsManager_Integration {
		if (this.exists(integrationID)) return this.integrations[integrationID];
		return null;
	}

	getActions(): IntegrationsManager_ActionsListItem[] {
		var actions: IntegrationsManager_ActionsListItem[] = [];
		for (var integrationID in this.integrations) {
			var integration = this.integrations[integrationID];
			actions = actions.concat(
				integration.api.getActionList().map((action) => {
					return {
						integrationID,
						integrationName: integration.integration.name,
						actionID: action.id,
						actionName: action.name
					};
				})
			);
		}

		return actions;
	}

	getConnections(): IntegrationsManager_ConnectionsListItem[] {
		var connections: IntegrationsManager_ConnectionsListItem[] = [];

		for (var integrationID in this.integrations) {
			var integration = this.integrations[integrationID];
			if (integration.integration.connections != undefined)
				for (let i = 0; i < integration.integration.connections.length; i++) {
					var con = integration.integration.connections[i];
					connections.push({
						integrationID,
						integrationName: integration.integration.name,
						connectionType: con.type,
						connectionName: con.name
					});
				}
		}

		connections.sort((a, b) => {
			if (a.integrationName == b.integrationName) return 0;
			return a.integrationName > b.integrationName ? 1 : -1
		})

		return connections;
	}

	startEditor(
		settings: Action_Settings
	): { error?: string; actionEditorID?: string; properties?: { [property: string]: any } } {
		if (this.exists(settings.integrationID)) {
			var integrationWrapper = this.get(settings.integrationID);
			var integration = integrationWrapper.integration;
			var integrationAPI = integrationWrapper.api;

			if (integrationAPI.hasAction(settings.actionID)) {
				var action = integrationAPI.getAction(settings.actionID);

				if (Undecked.Pages.exists(settings.pageID)) {
					var page = Undecked.Pages.get(settings.pageID);
					if (page.hasKey(settings.keyX, settings.keyY)) {
						var key = page.getKey(settings.keyX, settings.keyY);
						var actionInstance = page.getActionInstance(key, settings.actionInstanceID);

						if (actionInstance) {
							var editorID = Undecked.generateRandom(8, (generatedValid: string) => {
								return !this.exists(generatedValid);
							});

							var editorAPI = new EditorAPI({
								actionInstance,

								integrationID: settings.integrationID,
								pageID: settings.pageID,
								editorID,
								x: settings.keyX,
								y: settings.keyY,
								key
							});

							this.openEditors[editorID] = {
								editor: editorAPI,
								editorID,
								ready: () => {
									editorAPI.isOpen = true;

									action._openEditor(editorAPI, actionInstance.properties);
								},
								destroy: () => {
									if (this.openEditors[editorID] != undefined) {
										this.openEditors[editorID].editor.close();
										delete this.openEditors[editorID];
									}
								}
							};
							return { actionEditorID: editorID, properties: {} };
						} else return { error: `Key does not have this actioninstance` };
					} else return { error: `Key does not exist` };
				} else return { error: `Page '${settings.pageID}' does not exist` };
			} else return { error: `Action '${settings.actionID}' does not exist` };
		} else return { error: `Integration '${settings.integrationID}' does not exist` };
	}

	editorExists(editorID: string): boolean {
		return this.openEditors[editorID] != undefined;
	}

	getEditor(editorID: string): IntegrationsManager_ActionEditor {
		if (this.editorExists(editorID)) return this.openEditors[editorID];
		return null;
	}

	executeActions(actions: Page_Key_Actions, deck: Deck) {
		for (var actionInstanceID in actions) {
			var actionInstance = actions[actionInstanceID];

			if (Undecked.Integrations.exists(actionInstance.integrationID)) {
				var integration = Undecked.Integrations.get(actionInstance.integrationID);
				if (integration.api.hasAction(actionInstance.actionID)) {
					var action = integration.api.getAction(actionInstance.actionID);
					if (typeof action._hdl == 'function')
						action._hdl(
							actionInstance.properties,
							(text: string, type: 'info' | 'error' | 'warning' = 'info') => {
								if (actionInstance.logs == undefined) actionInstance.logs = [];

								actionInstance.logs = [
									{ timestamp: Date.now(), type, text },
									...actionInstance.logs
								];
								if (actionInstance.logs.length > 20)
									actionInstance.logs.splice(20, actionInstance.logs.length - 20);

								// Log(
								// 	<any>type.replace('warning', 'warn'),
								// 	`[Deck:${deck != null ? deck.getName() : 'Internal'}][Integration:${integration
								// 		.integration.name}][Action:${actionInstance.actionID}] ${text}`
								// );
							},
							deck
						);
					else
						Log(
							'warn',
							`Tried calling non-exisintg handler for action '${actionInstance.actionID}' of integration '${actionInstance.integrationID}'`
						);
				}
			}
		}
	}
}

// export interface IntegrationsManager {
// 	load: (callback: () => void) => void;
// 	exists: (integrationID: string) => boolean;
// 	get: (integrationID: string) => IntegrationsManager_Integration;
// 	getActions: () => IntegrationsManager_ActionsListItem[];
// 	getConnections: () => IntegrationsManager_ConnectionsListItem[];
// 	startEditor: (
// 		settings: Action_Settings
// 	) => { error?: string; actionEditorID?: string; properties?: { [property: string]: any } };
// 	editorExists: (editorID: string) => boolean;
// 	getEditor: (editorID: string) => IntegrationsManager_ActionEditor;
// 	executeActions: (actions: Page_Key_Actions, deck: Deck) => void;
// }

export interface IntegrationsManager_Integrations {
	[integrationID: string]: IntegrationsManager_Integration;
}

export interface IntegrationsManager_Integration {
	type: 'buildin' | 'custom';
	api: IntegrationAPI;
	connectionslist: string[];
	connectionsmap: { [connectionType: string]: Integration_Connection };
	integration: Integration;
}

export interface IntegrationsManager_ActionsListItem {
	integrationID: string;
	integrationName: string;
	actionID: string;
	actionName: string;
}

export interface IntegrationsManager_ConnectionsListItem {
	integrationID: string;
	integrationName: string;
	connectionType: string;
	connectionName: string;
}

export interface Integration {
	name: string;
	description: string;
	main: (api: IntegrationAPI) => {};

	/**
	 * @deprecated Has been replaced by title argument in integrationAPI.registerAction function
	 */
	actions?: Integration_Action[];
	connections?: Integration_Connection[];
}

export interface Integration_Action {
	id: string;
	name: string;
}

export interface Integration_Connection {
	type: string;
	name: string;
	message?: string;
	link?: {
		address: string;
		title: string;
	};
	fields: Integration_Connection_Field[];
}

export interface Integration_Connection_Field {
	id: string;
	name: string;
	type: 'text' | 'number' | 'select' | 'connection' | 'color' | 'checkbox';
	value?: any;
	values?: { id: string; text: string }[];
	connectionType?: string;
}

export interface Action_Settings {
	integrationID: string;
	actionID: string;
	actionInstanceID: string;
	pageID: string;
	actionType: 'up' | 'down';
	keyX: number;
	keyY: number;
}

export interface IntegrationsManager_ActionEditor {
	editor: EditorAPI;
	editorID: string;
	ready: () => void;
	destroy: () => void;
}
