namespace ngIntroJs {
	export interface INgIntroService {
		intro: IntroJs.IntroJs,
		addListener(name: string, callback: Function): void
		removeListener(name: string): void
		setOptions: IntroJs.Options,
		start(stepId?: number): IntroJs.IntroJs,
		exit(): IntroJs.IntroJs,
		clear(callback: Function): IntroJs.IntroJs,
		goToStepNumber(stepId: number): IntroJs.IntroJs,

		addHints(): IntroJs.IntroJs,
		showHint(hintIdx: number): IntroJs.IntroJs,
		showHints(): IntroJs.IntroJs,
		hideHint(hintIdx: number): IntroJs.IntroJs,
		hideHints(): IntroJs.IntroJs
		removeHint(stepid: number): IntroJs.IntroJs
		removeHints(): IntroJs.IntroJs

		previous(): IntroJs.IntroJs,
		next(): IntroJs.IntroJs,

		refresh(): IntroJs.IntroJs,

		onComplete(callback: Function): void
		onExit(callback: Function): void
		onBeforeChange(callback: Function): void
		onAfterChange(callback: Function): void
		onChange(callback: Function): void
		onHintClick(callback: Function): void
		onHintClose(callback: Function): void
		onHintsAdded(callback: Function): void
	}
	export interface INgIntroDirectiveScope extends ng.IScope {
		ngIntroMethod(step?: number): void
		ngIntroExitMethod(cb?: Function): void
		ngIntroNextMethod(): void
		ngIntroPreviousMethod(): void
		ngIntroRefreshMethod(): void
		ngIntroOptions(): void
		ngIntroOncomplete(): void
		ngIntroOnexit(): void
		ngIntroOnchange(): void
		ngIntroOnbeforechange(): void
		ngIntroOnafterchange(): void
		ngIntroAutostart(): void
		ngIntroAutorefresh(): void

		ngIntroHintsMethod(): void
		ngIntroOnhintsadded(): void
		ngIntroOnhintclick(): void
		ngIntroOnhintclose(): void
		ngIntroShowHint(id: number): void
		ngIntroShowHints(): void
		ngIntroHideHint(id: number): void
		ngIntroHideHints(): void
	}
	export interface NotifyItem {
		[name: string]: Function
	}
}
// UMD loader: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
declare const define: any;
declare const module: any;
declare const require: any;
(function (root, factory) {
	if (typeof define === "function" && define.amd) {
		define(["angular", "intro.js"], factory);
	} else if (typeof module === "object" && module.exports) {
		module.exports = factory(require("angular"), require("intro.js"));
	} else {
		root.angularIntroJs = factory(root.angular, root.introJs);
	}
}(typeof self !== "undefined" ? self : this, function (angular: ng.IAngularStatic, introJs: IntroJs.Factory) {
	let introStatus = { // i wanted to use enums, but for now it"ll work
		open: "open",
		closed: "closed"
	}
	let moduleName = "angular-intro";


	let notifyList: ngIntroJs.NotifyItem = {} // this is an objects that holds the current listeners.
	///when the intro opens or closes it"ll iterate through this list calling the callback;

	class NgIntroService implements ngIntroJs.INgIntroService {
		public intro: IntroJs.IntroJs
		// static $inject =  []

		constructor() {
			this.intro = introJs();
		}

		/// adds into notifyList, if there"s a valid callback.
		addListener(name: string, cb: Function) {
			if (angular.isFunction(cb))
				notifyList[name] = cb;
		}
		// remove from notifyList.
		removeListener(name: string) {
			delete notifyList[name];
		}

		///iterate through notifyList and call each callback.
		private notifyListeners(status: string) {
			for (let key in notifyList) {
				if (notifyList.hasOwnProperty(key)) {
					if (angular.isFunction(notifyList[key]))
						notifyList[key](status);
				}
			}
		}

		setOptions(options: IntroJs.Options) {
			return this.intro.setOptions(options);
		}

		start(step?: number) {
			if (typeof (step) === "number") {
				this.intro.goToStep(step).start();
			} else {
				this.intro.start();
			}
			this.notifyListeners(introStatus.open);

			return this.intro;
		}

		exit() {
			this.notifyListeners(introStatus.closed);
			return this.intro.exit();
		}

		clear(cb: Function) {
			if (typeof (this.intro) !== "undefined")
				this.intro.exit();

			this.intro = introJs();

			this.notifyListeners(introStatus.closed);

			if (angular.isFunction(cb)) cb();

			return this.intro;
		}
		goToStepNumber(stepId: number) {
			return this.intro.goToStepNumber(stepId);
		}

		addHints() {
			return this.intro.addHints();
		}
		showHint(hintIndex: number) {
			return this.intro.showHint(hintIndex);
		}
		showHints() {
			return this.intro.showHints();
		}

		hideHint(hintIndex: number) {
			return this.intro.hideHint(hintIndex);
		}

		hideHints() {
			return this.intro.hideHints();
		}
		removeHint(stepId: number) {
			return this.intro.removeHint(stepId)
		}

		removeHints() {
			return this.intro.removeHints()
		}

		previous() {
			this.notifyListeners(introStatus.open);
			return this.intro.previousStep();
		}
		next() {
			this.notifyListeners(introStatus.open);
			return this.intro.nextStep();

		}

		refresh() {
			return this.intro.refresh();
		}

		onComplete(cb: Function) {
			return this.intro.oncomplete(() => {
				if (angular.isFunction(cb)) cb();
				this.notifyListeners(introStatus.closed);
			});
		}
		onExit(cb: Function) {
			return this.intro.onexit(() => {
				this.notifyListeners(introStatus.closed);
				if (angular.isFunction(cb)) cb();
			});
		}
		onBeforeChange(cb: Function) {
			return this.intro.onbeforechange((targetElement) => {
				if (angular.isFunction(cb)) cb(targetElement);
			});
		}
		onChange(cb: Function) {
			return this.intro.onchange((targetElement) => {
				if (angular.isFunction(cb)) cb(targetElement);
			});
		}
		onAfterChange(cb: Function) {
			return this.intro.onafterchange((targetElement) => {
				if (angular.isFunction(cb)) cb(targetElement);
			});
		}

		onHintClick(cb: Function) {
			return this.intro.onhintclick(() => {
				if (angular.isFunction(cb)) cb();
			});
		}

		onHintClose(cb: Function) {
			return this.intro.onhintclose(() => {
				if (angular.isFunction(cb)) cb();
			});
		}
		onHintsAdded(cb: Function) {
			return this.intro.onhintclose(() => {
				if (angular.isFunction(cb)) cb();
			});
		}

	}

	class NgIntroDirective implements ng.IDirective {
		public restrict = "A";
		public link: (scope: ngIntroJs.INgIntroDirectiveScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => void;
		public scope = {
			ngIntroMethod: "=",
			ngIntroExitMethod: "=?",
			ngIntroNextMethod: "=?",
			ngIntroPreviousMethod: "=?",
			ngIntroRefreshMethod: "=?",
			ngIntroOptions: "=",
			ngIntroOncomplete: "=",
			ngIntroOnexit: "=",
			ngIntroOnchange: "=",
			ngIntroOnbeforechange: "=",
			ngIntroOnafterchange: "=",
			ngIntroAutostart: "=",
			ngIntroAutorefresh: "=",

			ngIntroHintsMethod: "=?",
			ngIntroOnhintsadded: "=",
			ngIntroOnhintclick: "=?",
			ngIntroOnhintclose: "=?",
			ngIntroShowHint: "=?",
			ngIntroShowHints: "=?",
			ngIntroHideHint: "=?",
			ngIntroHideHints: "=?"
		};
		destroy: any[] = [];

		static factory(): ng.IDirectiveFactory {
			const directive = (introService: NgIntroService, $timeout: ng.ITimeoutService) => new NgIntroDirective(introService, $timeout);
			directive.$inject = ["ngIntroService", "$timeout"];
			return directive;
		}
		constructor(introService: NgIntroService, $timeout: ng.ITimeoutService) {

			this.link = (scope: ngIntroJs.INgIntroDirectiveScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
				if (scope.ngIntroOncomplete) {
					introService.onComplete(scope.ngIntroOncomplete);
				}
				if (scope.ngIntroOnexit) {
					introService.onExit(scope.ngIntroOnexit);
				}

				if (scope.ngIntroOnbeforechange) {
					introService.onBeforeChange(scope.ngIntroOnbeforechange);
				}

				if (scope.ngIntroOnchange) {
					introService.onChange(scope.ngIntroOnchange);
				}

				if (scope.ngIntroOnafterchange) {
					introService.onAfterChange(scope.ngIntroOnafterchange);
				}

				scope.ngIntroMethod = (step) => {
					introService.setOptions(scope.ngIntroOptions);
					introService.start(step);
				};

				scope.ngIntroHintsMethod = (step?: number) => {
					introService.setOptions(scope.ngIntroOptions);
					introService.start(step);

					if (scope.ngIntroOnhintsadded) {
						introService.onHintsAdded(scope.ngIntroOnbeforechange);
					}

					if (scope.ngIntroOnhintclick) {
						introService.onHintClick(scope.ngIntroOnbeforechange);
					}

					if (scope.ngIntroOnhintclose) {
						introService.onHintClick(scope.ngIntroOnbeforechange);
					}

					introService.addHints();
				};

				scope.ngIntroShowHint = (id) => {
					introService.showHint(id);
				};

				scope.ngIntroShowHints = () => {
					introService.showHints();
				};

				scope.ngIntroHideHint = (id) => {
					introService.hideHint(id);
				};

				scope.ngIntroHideHints = () => {
					introService.hideHints();
				};

				scope.ngIntroNextMethod = () => {
					introService.next();
				};

				scope.ngIntroPreviousMethod = () => {
					introService.previous();
				};

				scope.ngIntroExitMethod = (callback) => {
					introService.exit();
					if (angular.isFunction(callback)) callback();
				};

				scope.ngIntroRefreshMethod = () => {
					introService.refresh();
				};

				let autoStartWatch = scope.$watch("ngIntroAutostart", () => {
					if (scope.ngIntroAutostart) {
						$timeout(() => {
							scope.ngIntroMethod();
						});
					}
					autoStartWatch();
				});

				this.destroy.push(scope.$on("$locationChangeStart", () => {
					introService.exit();
				}));

				this.destroy.push(scope.$on("$locationChangeSuccess", () => {
					introService.exit();
				}));

				if (scope.ngIntroAutorefresh) {
					this.destroy.push(scope.$watch(() => {
						introService.refresh();
					}));
				}

				this.destroy.push(scope.$on("$destroy", () => {
					introService.exit();
				}));

				scope.$on("$destroy", () => {
					clearWatches();
				});

				let clearWatches = () => {
					for (let d of this.destroy)
						d();
				}


			};
		}
	}

	angular.module(moduleName, [])
		.service("ngIntroService", NgIntroService)

		// 1st way to create the directive using typescript.
		.directive("ngIntroOptions", NgIntroDirective.factory())

		/// this is another
		.directive("ngIntroDisableButton", ["ngIntroService", function (ngIntroService) {
			let id = 0;
			return <ng.IDirective>{
				restrict: "A",
				priority: 1,
				link: function (scope, elm, attrs) {
					let uniqueId = "disabledBtn" + id++;
					ngIntroService.addListener(uniqueId, function (value: string) {
						if (value === introStatus.open) {
							attrs.$set("disabled", "disabled");
						} else {
							delete attrs.disabled;
							elm.removeAttr("disabled");
						}
					});

					scope.$on("$destroy", function () {
						ngIntroService.removeListener(uniqueId);
					});

				}
			};
		}]);
}))