class UIKit extends Framework {
	static registerViewControllerType(viewControllerClass) {
		if (!UIKit.viewControllerTypes) {
			UIKit.viewControllerTypes = { };
		}

		UIKit.viewControllerTypes[viewControllerClass.name] = viewControllerClass;
	}

	static registerViewType(viewClass, tagName = null) {
		if (!UIKit.viewTypes) {
			UIKit.viewTypes = { };
		}

		UIKit.viewTypes[viewClass.name] = viewClass;

		if (tagName) {
			if (!UIKit.viewTagNames) {
				UIKit.viewTagNames = { };
			}

			UIKit.viewTagNames[tagName.toLowerCase()] = viewClass;
		}
	}

	static registerPopoverType(popoverClass, tagName = null) {
		if (!UIKit.popoverTypes) {
			UIKit.popoverTypes = { };
		}

		UIKit.popoverTypes[popoverClass.name] = popoverClass;

		if (tagName) {
			if (!UIKit.popoverTagNames) {
				UIKit.popoverTagNames = { };
			}

			UIKit.popoverTagNames[tagName.toLowerCase()] = popoverClass;
		}
	}

	static registerTooltipType(tooltipClass, tagName = null) {
		if (!UIKit.tooltipTypes) {
			UIKit.tooltipTypes = { };
		}

		UIKit.tooltipTypes[tooltipClass.name] = tooltipClass;

		if (tagName) {
			if (!UIKit.tooltipTagNames) {
				UIKit.tooltipTagNames = { };
			}

			UIKit.tooltipTagNames[tagName.toLowerCase()] = tooltipClass;
		}
	}

	static getViewControllerType(typeName) {
		if (!UIKit.viewControllerTypes) {
			return null;
		}
		return UIKit.viewControllerTypes[typeName];
	}

	static getPopoverType(typeName) {
		if (!UIKit.popoverTypes) {
			return null;
		}
		return UIKit.popoverTypes[typeName];
	}

	static getTooltipType(typeName) {
		if (!UIKit.tooltipTypes) {
			return null;
		}
		return UIKit.tooltipTypes[typeName];
	}

	static main() {
		UIKit.scenes = { };
		UIKit.viewControllers = { };
		UIKit.navigationListeners = [ ];
	}

	static onApplicationDidFinishLaunching() {
		var viewControllers = document.body.querySelectorAll("view-controller");

		var initialHash = null;
		var initialPath = null;

		for (var i = 0; i < viewControllers.length; i++) {
			var viewController = viewControllers[i];

			var controllerClassName = viewController.getAttribute("controller-class");
			if (!controllerClassName) {
				controllerClassName = "ViewController";
			}
			var ControllerClass = UIKit.getViewControllerType(controllerClassName);
			if (!ControllerClass) {
				console.error("Could not find controller class \"" + controllerClassName + "\". Are you sure it's loaded?");
				continue;
			}

			var controllerInstance = new ControllerClass(viewController);

			var isInitialViewController = (viewController.getAttribute("initial-view-controller") === "true");

			//Check the hash.
			var viewControllerHash = viewController.getAttribute("hash");
			if (viewControllerHash) {
				controllerInstance.hash = viewControllerHash;

				if (isInitialViewController) {
					initialHash = viewControllerHash;
				}
			}

			//Check the request path.
			var viewControllerRequestPath = viewController.getAttribute("request-path");
			if (viewControllerRequestPath) {
				controllerInstance.requestPath = viewControllerRequestPath;

				if (isInitialViewController) {
					initialPath = viewControllerRequestPath;
				}
			}
			else {
				controllerInstance.requestPath = "/";
			}

			var sceneId = viewController.getAttribute("scene-id");
			if (sceneId !== null) {
				var scene = UIKit.getScene(sceneId);
				scene.addViewController(controllerInstance);

				if (isInitialViewController) {
					scene.initialViewController = controllerInstance;
				}
			}

			//Read the modal flag.
			var modal = viewController.getAttribute("modal");
			controllerInstance.showsModally = modal === "true";

			UIKit.viewControllers[viewController.getAttribute("id")] = controllerInstance;

			viewController.style.display = "";
			UIKit.processViewControllerElement(viewController, controllerInstance);
		}

		//Call the viewDidLoad() functions on all loaded view controllers.
		for (var id in UIKit.viewControllers) {
			UIKit.viewControllers[id].viewDidLoad();
		}

		//Load all the popovers.
		UIKit.popovers = { };
		var popovers = document.body.querySelectorAll("popover");
		for (var i = 0; i < popovers.length; i++) {
			var popover = popovers[i];

			if (!popover.hasAttribute("id")) {
				console.error("Skipping a popover with no ID attribute.");
			}

			var controllerClassName = popover.getAttribute("controller-class");
			if (!controllerClassName) {
				controllerClassName = "Popover";
			}
			var ControllerClass = UIKit.getPopoverType(controllerClassName);
			if (!ControllerClass) {
				console.error("Could not find popover class \"" + controllerClassName + "\". Are you sure it's loaded?");
				continue;
			}

			var popoverController = new ControllerClass(popover);

			UIKit.popovers[popover.getAttribute("id")] = popoverController;

			document.body.removeChild(popover);

			UIKit.processViewControllerElement(popover, popoverController);
		}

		//Load all the tooltips.
		UIKit.tooltips = { };
		var tooltips = document.body.querySelectorAll("tooltip");
		for (var i = 0; i < tooltips.length; i++) {
			var tooltip = tooltips[i];

			if (!tooltip.hasAttribute("id")) {
				console.error("Skipping a tooltip with no ID attribute.");
			}

			var controllerClassName = tooltip.getAttribute("controller-class");
			if (!controllerClassName) {
				controllerClassName = "Tooltip";
			}
			var ControllerClass = UIKit.getTooltipType(controllerClassName);
			if (!ControllerClass) {
				console.error("Could not find tooltip class \"" + controllerClassName + "\". Are you sure it's loaded?");
				continue;
			}

			var tooltipController = new ControllerClass(tooltip);

			UIKit.tooltips[tooltip.getAttribute("id")] = tooltipController;

			document.body.removeChild(tooltip);

			UIKit.processViewControllerElement(tooltip, tooltipController);
		}

		UIKit.onPopState();

		if (!window.location.hash && initialHash) {
			window.location.hash = initialHash;
		}
		else if (window.location.hash) {
			UIKit.onHashChanged();
		}

		//Add the mouse movement listener to track the cursor position.
		document.body.addEventListener("mousemove", UIKit.onBodyMouseMoved);
	}

	static addNavigationListener(callback) {
		UIKit.navigationListeners.push(callback);
	}

	/**
	 * Outlets are attached to the view instance of the parent. Actions are attached to the current element's view instance.
	 * The view controller will get an instance reference to the view.
	 */
	static processViewControllerElement(element, viewController, viewInstance = null) {
		var outlet = element.getAttribute("outlet");
		if (outlet) {
			viewController[outlet] = element;
			if (viewInstance) {
				viewInstance[outlet] = element;
			}
		}

		var viewClassName = element.getAttribute("view-class");
		var ViewClass = (viewClassName in UIKit.viewTypes) ? UIKit.viewTypes[viewClassName] : null;
		if (!ViewClass) {
			ViewClass = (UIKit.viewTagNames && (element.tagName.toLowerCase() in UIKit.viewTagNames)) ? UIKit.viewTagNames[element.tagName.toLowerCase()] : null;
		}
		if (ViewClass) {
			viewInstance = new ViewClass(element);
			viewInstance.viewController = viewController;

			//If there are no children to this view, it shall be built by code.
			if (element.children.length == 0) {
				viewInstance.element = viewInstance.build(element);
			}
		}
		else if (viewClassName) {
			console.error("Can't find view class " + viewClassName + ". Is it loaded?");
		}

		var action = element.getAttribute("action");
		if (action) {
			var actionTypeString = element.getAttribute("action-type");
			if (!actionTypeString) {
				actionTypeString = "click";
			}

			actionTypeString = actionTypeString.replaceAll(", ", ",");
			var actionTypes = actionTypeString.split(",");

			for (var i = 0; i < actionTypes.length; i++) {
				var actionType = actionTypes[i];

				if (action.indexOf("#") !== 0 && action.indexOf("/") !== 0) {
					//It's a function call action.
					if (viewController[action]) {
						element.addEventListener(actionType, viewController[action].bind(viewController));
					}
					if (viewInstance && viewInstance[action]) {
						element.addEventListener(actionType, viewInstance[action].bind(viewInstance));
					}
				}
				else {
					//It's a badger badger badger – uhm... no, it's a SNAAAKE, uh, no, it's a request path action.
					element.addEventListener(actionType, UIKit.onElementAction);
				}
			}
		}

		if (outlet && viewInstance) {
			viewController[outlet + "Controller"] = viewInstance;
		}

		if (ViewClass) {
			viewController.allSubviews.push(viewInstance);
		}

		for (var i = 0; i < element.children.length; i++) {
			UIKit.processViewControllerElement(element.children[i], viewController, viewInstance);
		}

		if (ViewClass) {
			viewInstance.viewDidLoad();
		}
	}

	static onElementAction(event) {
		var action = event.currentTarget.getAttribute("action");

		if (action.indexOf("#") === 0) {
			window.location.hash = action.substring(1);
			return;
		}

		if (action.indexOf("/") === 0) {
			window.history.pushState(null, "", action);
			UIKit.onPopState();
		}
	}

	static getScene(sceneId) {
		if (!(sceneId in UIKit.scenes)) {
			UIKit.scenes[sceneId] = new Scene(sceneId);
		}
		return UIKit.scenes[sceneId];
	}

	/**
	 * Returns the scene that contains the given view controller, null if none does so.
	 */
	static getSceneWithViewController(viewController) {
		for (var sceneId in UIKit.scenes) {
			var scene = UIKit.scenes[sceneId];

			if (scene.containsViewController(viewController)) {
				return scene;
			}
		}

		return null;
	}

	/**
	 * Looks for the view controller with the given hash inside all scenes and returns it if found.
	 */
	static getViewControllerWithHash(hash) {
		for (var sceneId in UIKit.scenes) {
			var scene = UIKit.scenes[sceneId];

			var viewController = scene.getViewControllerWithHash(hash);

			if (viewController) {
				return viewController;
			}
		}

		return null;
	}

	/**
	 * Looks for the view controller with the given request path inside all scenes and returns it if found.
	 */
	static getViewControllerWithRequestPath(requestPath) {
		for (var sceneId in UIKit.scenes) {
			var scene = UIKit.scenes[sceneId];

			var viewController = scene.getViewControllerWithRequestPath(requestPath);

			if (viewController) {
				return viewController;
			}
		}

		return null;
	}

	static getViewControllerById(id) {
		return UIKit.viewControllers[id];
	}

	static getActiveViewController() {
		return UIKit.activeScene.activeViewController;
	}

	static getPopoverById(id) {
		return UIKit.popovers[id];
	}

	static getTooltipById(id) {
		return UIKit.tooltips[id];
	}

	static showScene(sceneId) {
		//Hide the others.
		for (var sceneId in UIKit.scenes) {
			UIKit.scenes[sceneId].hide();
		}

		var scene = UIKit.getScene(sceneId);
		scene.show();
	}

	/**
	 * Called when the URL hash has changed.
	 * Checks if there is any view controller that matches the hash and shows it's scene and itself.
	 */
	static onHashChanged(event) {
		var hash = window.location.hash;

		var viewControllerToShow = null;

		if (hash) {
			hash = hash.substring(1);

			var viewController = UIKit.getViewControllerWithHash(hash);

			if (viewController) {
				viewControllerToShow = viewController;
			}
		}
		
		//Use the initial view controller as a fallback.
		if (!viewControllerToShow) {
			viewControllerToShow = UIKit.getInitialViewController();
		}

		if (viewControllerToShow) {
			if (viewControllerToShow.showsModally) {
				UIKit.getActiveViewController().presentModalViewController(viewControllerToShow);
			}
			else {
				var scene = UIKit.getSceneWithViewController(viewControllerToShow);

				if (scene) {
					scene.showWithViewController(viewControllerToShow);
				}
				else {
					console.error("Can't find any scene with the view controller " + viewControllerToShow.constructor.name + "!");
				}
			}
		}
		else {
			console.error("No view controller to show! Not even a fallback! What is this?!");
		}
	}

	/**
	 * Called when the request path has changed.
	 * Checks if there is any view controller that matches the request path and shows it's scene and itself.
	 */
	static onPopState(event) {
		var requestPath = location.pathname;

		var viewControllerToShow = UIKit.getViewControllerWithRequestPath(requestPath);

		if (!viewControllerToShow) {
			viewControllerToShow = UIKit.getInitialViewController();
		}

		if (viewControllerToShow) {
			var scene = UIKit.getSceneWithViewController(viewControllerToShow);

			if (scene) {
				scene.showWithViewController(viewControllerToShow);
				viewControllerToShow.view.scrollTo(0, 0);
			}
			else {
				console.error("Can't find any scene with the view controller " + viewControllerToShow.constructor.name + "!");
			}
		}
		else {
			console.error("No view controller to show! Not even a fallback! What is this?!");
		}

		for (var i = 0; i < UIKit.navigationListeners.length; i++) {
			UIKit.navigationListeners[i](requestPath);
		}
	}

	/**
	 * Returns the initial view controller of the main scene.
	 */
	static getInitialViewController() {
		var scene = UIKit.getScene("main");
		if (scene) {
			return scene.initialViewController;
		}

		return null;
	}

	static onBodyMouseMoved(event) {
		UIKit.mousePosition = {
			x: event.clientX,
			y: event.clientY
		};
	}
}

window.addEventListener("hashchange", UIKit.onHashChanged);
window.addEventListener("popstate", UIKit.onPopState);