import { Undecked } from '../Core';
import * as path from 'path'
import { Log } from '../Logger';
import { Page, Page_Config, Page_Key } from './Page';
import { KeyManager } from './KeyManager';

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

const PageClass = require('./Page');

declare var Undecked: Undecked;

export class PageManager {
	KeyManager: KeyManager;

	managerConfigPath: string;
	managerDataPath: string;

	managerConfig: PageManager_Config;

	pages: PageManager_Pages;
	order: string[];

	constructor() { }

	load(callback: () => void) {
		this.managerConfigPath = path.join(Undecked.dataPath, 'pagemanager.json');
		this.managerDataPath = path.join(Undecked.dataPath, 'pages');

		this.KeyManager = new KeyManager();

		this.pages = {};
		this.order = [];

		Log('info', 'Loading pages');

		ensureDir(this.managerDataPath, (err) => {
			if (err) throw err;
			this.loadConfig(() => {
				this.order = this.managerConfig.order || [];

				this.loadPages(callback);
			});
		});
	}

	loadConfig(callback: () => void) {
		pathExists(this.managerConfigPath, (err, exists) => {
			if (err) throw err;
			if (exists) {
				readJson(this.managerConfigPath, (err, json) => {
					if (err) throw err;
					this.managerConfig = json;
					callback();
				});
			} else {
				this.managerConfig = defaultPageConfig;
				this.saveConfig(callback);
			}
		});
	}

	saveConfig(callback?: () => void) {
		var toSave: PageManager_Config = {
			order: this.order
		};

		writeFile(this.managerConfigPath, JSON.stringify(toSave, null, 4), (err) => {
			if (err) Log('error', 'Error whilst saving manager config', err.message);
			if (callback) callback();
		});
	}

	loadPages(callback: () => void) {
		var instance = this;
		readdir(this.managerDataPath, (err, files) => {
			if (err) throw err;

			(function readPage(i = 0) {
				if (files[i]) {
					readJSON(path.join(instance.managerDataPath, files[i]), (err, data: Page_Config) => {
						if (err)
							Log('error', `Error whilst loading page ${files[i].replace('.json', '')}`, err.message);
						else instance.pages[data.pageID] = new PageClass(data);

						readPage(i + 1);
					});
				} else {
					Log('info', `Loaded ${Object.keys(instance.pages).length} page(s)`);
					if (Object.keys(instance.pages).length > 0) callback();
					else {
						instance.create('First page', callback);
					}
				}
			})();
		});
	}

	create(pageName: string, callback?: () => void) {
		var existingPageIDs = Object.keys(this.pages);
		var pageID = Undecked.generateRandom(
			4,
			(testrandom: string) => {
				return !existingPageIDs.includes(testrandom);
			},
			'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
		);
		this.pages[pageID] = new PageClass({
			pageID,
			name: `${pageName} (${pageID})`,
			keys: {
				'0': { '0': { state: { type: 'pageup' } } },
				'1': { '0': { state: { type: 'currentpage' } } },
				'2': { '0': { state: { type: 'pagedown' } } }
			}
		});

		if (!this.order.includes(pageID)) this.order.push(pageID);
		Undecked.SocketServer.broadcastTo('home', 'pagelist', this.getNames());

		this.pages[pageID].save(() => {
			Log('info', `Page ${pageID} has been created`);

			if (callback) callback();
		});
	}

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

	get(pageID: string): Page {
		if (this.exists(pageID)) return this.pages[pageID];
		return null;
	}

	getAll(): PageManager_Pages {
		return this.pages;
	}

	getIdByIndex(index: number): string {
		var ids = this.order;
		if (ids[index]) return ids[index];
		return null;
	}

	getIndexById(id: string): number {
		var ids = this.order;
		if (ids.includes(id)) return ids.indexOf(id);
		return null;
	}

	getNames(): { pageID: string; name: string }[] {
		var order = this.getOrder();
		var names = [];

		for (let i = 0; i < order.length; i++) {
			if (this.pages[order[i]] != undefined) {
				var page = this.pages[order[i]];
				names.push({ pageID: order[i], name: page.name });
			}
		}

		return names;
	}

	getOrder(): string[] {
		var hasChanges = false;
		for (var pageID in this.pages) {
			if (!this.order.includes(pageID)) {
				this.order.push(pageID);
				hasChanges = true;
			}
		}
		for (let i = 0; i < this.order.length; i++) {
			if (this.pages[this.order[i]] == undefined) {
				this.order.splice(i, 1);
				i--;
				hasChanges = true;
			}
		}
		if (hasChanges) this.saveConfig();
		return this.order;
	}

	setOrder(order: string[]) {
		var newOrder: string[] = [];

		for (let i = 0; i < order.length; i++) if (this.exists(order[i])) newOrder.push(order[i]);

		for (var pageID in this.pages) if (!newOrder.includes(pageID)) newOrder.push(pageID);

		this.order = newOrder;

		for (var serialNumber in Undecked.Decks.decks) Undecked.Decks.decks[serialNumber].updateAll();

		Undecked.SocketServer.broadcastTo('home', 'pagelist', this.getNames());

		this.saveConfig();
	}

	handleOperation(
		operation: 'cut' | 'copy' | 'delete' | 'ghost',
		originPageID: string,
		originX: number,
		originY: number,
		destinationPageID?: string,
		destinationX?: number,
		destinationY?: number
	) {
		var findKey = (pageID: string, x: number, y: number): Page_Key => {
			if (this.exists(pageID)) {
				return this.get(pageID).getKey(x, y);
			}

			return null;
		};
		var setKey = (pageID: string, x: number, y: number, key: Page_Key, force: boolean = false) => {
			if (this.exists(pageID)) {
				return this.get(pageID).updateKey(x, y, key, '-1', null, force);
			}

			return null;
		};
		var newKey = (): Page_Key => {
			return {
				state: { type: 'empty', confirm: false, toggle: false },
				appearence: {},
				actions: { down: {}, up: {}, latch: {}, unlatch: {} },
				id: Undecked.Pages.KeyManager.generateNew()
			};
		};

		var originKey = findKey(originPageID, originX, originY);

		var destinationKey = findKey(destinationPageID, destinationX, destinationY);
		var originKeyClone: Page_Key = JSON.parse(JSON.stringify(originKey));

		if (originKey && originKey.state.type == 'custom') {
			switch (operation) {
				case 'copy':
					//FIXME: For some reason it doesnt copy the actions
					if (destinationKey) {
						var destinationKeyID = destinationKey.id;
						var actions = originKeyClone.actions;
						for (var actionsCategory in actions) {
							for (var actionID in actions[actionsCategory]) {
								actions[actionsCategory][
									actionID
								].id = Undecked.generateRandom(8, (checkValid: string) => {
									return actions[actionsCategory][actionID] == undefined;
								});
							}
						}

						setKey(
							destinationPageID,
							destinationX,
							destinationY,
							{
								...originKey,
								actions,
								id: destinationKeyID
							},
							true
						);
					}
					break;

				case 'cut':
					if (destinationKey) {
						setKey(originPageID, originX, originY, newKey(), true);
						setKey(destinationPageID, destinationX, destinationY, originKeyClone, true);
					}
					break;

				case 'ghost':
					if (destinationKey) {
						var originKeyID = originKey.id;
						var destinationKeyID = destinationKey.id;
						setKey(
							destinationPageID,
							destinationX,
							destinationY,
							{
								id: destinationKeyID,
								appearence: {},
								state: {
									type: 'ghost',
									toggle: false,
									confirm: false,
									masterID: originKeyID
								},
								actions: {
									up: {},
									down: {},
									latch: {},
									unlatch: {}
								}
							},
							true
						);

						if (originKey.state.ghostIDs == undefined) originKey.state.ghostIDs = [];
						originKey.state.ghostIDs.push(destinationKeyID);
					}
					break;

				case 'delete':
					setKey(originPageID, originX, originY, newKey(), true);
					break;
			}
		}
	}
}

var defaultPageConfig: PageManager_Config = {
	order: []
};

// export interface PageManager {
// 	KeyManager: KeyManager;

// 	load: (callback: () => void) => void;
// 	getNames: () => { pageID: string; name: string }[];
// 	getOrder: () => string[];

// 	create: (pageName: string, callback?: () => void) => void;
// 	exists: (pageID: string) => boolean;
// 	get: (pageID: string) => Page;
// 	getAll: () => PageManager_Pages;
// 	getIdByIndex: (index: number) => string;
// 	getIndexById: (id: string) => number;
// 	setOrder: (order: string[]) => void;
// 	handleOperation: (
// 		operation: 'cut' | 'copy' | 'delete',
// 		originPageID: string,
// 		originX: number,
// 		originY: number,
// 		destinationPageID: string,
// 		destinationX: number,
// 		destinationY: number
// 	) => void;
// }

export interface PageManager_Config {
	order: string[];
}

export interface PageManager_Pages {
	[id: string]: Page;
}
