/*
 * The MIT License
 *
 * Copyright (c) 2007 Jiro Iwamoto
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

if (typeof jxDialog == 'undefined') jxDialog = {};

(function() {
 	// constant
	var ELEMENT_NODE = 1; /* Element Node Type */
	var TAB_KEY = 9; /* Key code of Tab key */
	var ESC_KEY = 27; /* Escape key */

 	// dialogs
 	jxDialog.dialogs = [];
	jxDialog.close = function() {
		jxDialog.dialogs.pop().close();
	};

 	// Dialog Base
	var Base = function() { this.initialize.apply(this, arguments);};
	Base.prototype.initialize = function(options) {
		// default options
		this.options = {
			format: 'text',
			message: '',
			okButton: 'OK',
//			cancelButton: 'Cancel',
			cancelButton: 'キャンセル',
			closeButton: null,
			width: 300,
			backgroundColor: 'white',
			color: 'black',
			overlayBackgroundColor: 'black',
			duration: 400, //1000,
			opacity: 0.8,
			onFinish: function() {},
			transition: function(pos) { return (-Math.cos(pos*Math.PI)/2) + 0.5; },
			promptInputType: 'text',
			value: ''
			// Remember the last element must NOT have a comma
		};
		if (typeof options == 'string') options = {message: options};
		// merge options
		this.extend(this.options, options);

		// options.message html tag escape
		if (this.options.format != "html")
		{
			this.options.message = this.options.message
				.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "#039;")
				.replace(/</g, "&lt;").replace(/>/g, "&gt;");
		}

		// observers
		this.observers = [];

		// overlay
		this.overlay = document.createElement('div');
		// dialog
		this.dialog = this.createDialog();

		// focus control
		this.focusControl(this.dialog);

		// set initial style
		this.setStyle();

		document.body.appendChild(this.overlay);
		document.body.appendChild(this.dialog);
		var _this = this;

		// if resize and scroll event is occured, recaluculate position.
		this.observe(window, 'resize', function() {_this.resize();});
		this.observe(window, 'scroll', function() {_this.resize();});
		// prevent moving focus with tab key
		this.observe(window, this.getKeyEventName(), function(event) {_this.disableTab(event);});
		
		this.observe(document, this.getKeyEventName(), function(event) {_this.handleEsc(event, _this);});

		// animation start
		this.startAnimation();

		// register dialog
		jxDialog.dialogs.push(this);
	};

	// set initial style
	Base.prototype.setStyle = function() {
		var overlay = this.overlay;
		var dialog = this.dialog;
		var viewport = this.getViewPort();
		var scrollingOffset = this.getScrollingOffset();
		overlay.style.width = '100%';
		overlay.style.position = 'absolute';
		overlay.style.backgroundColor = this.options.overlayBackgroundColor;
		overlay.style.height = viewport.height + 'px';
		overlay.style.top = scrollingOffset.y + 'px';
		overlay.style.left = 0;
		overlay.style.zIndex = 9999;
		this.setOpacity(overlay, 0);

		dialog.style.position = 'absolute';
		dialog.style.width = this.options.width + 'px';
		dialog.style.left = '50%';
		dialog.style.marginLeft = -(this.options.width / 2) + 'px';
		dialog.style.top = '-1000px';
		dialog.style.zIndex = 10000;
	};

	// relocate element position
	Base.prototype.resize = function() {
		var viewport = this.getViewPort();
		var scrollingOffset = this.getScrollingOffset();
		this.overlay.style.height = viewport.height + 'px';
		this.dialog.style.top = this.overlay.style.top = scrollingOffset.y + 'px';

		this.dialog.style.left = '50%';
		this.dialog.style.marginLeft = -(this.options.width / 2) + 'px';
	};

	// disable moving tab focus
	Base.prototype.disableTab = function(event) {
		event = event || window.event;
		if (event.keyCode == TAB_KEY) {
			// if focused element is window, focus first focusElements
			if (this.focusElements && this.focusElements[0]) this.focusElements[0].focus();
			this.eventStop(event);
		}
	};
	
	Base.prototype.handleEsc = function(event, _this) {
		event = event || window.event;
		if (event.keyCode == ESC_KEY) {
			_this.cancelDialog();
		}
	};

	Base.prototype.createCloseButton = function() {
		var _this = this;
		var closeButton = document.createElement('a');
		closeButton.href = 'javascript: void(0);';
		if (this.options.closeButton) closeButton.innerHTML = this.options.closeButton;
		this.observe(closeButton, 'click', function() {_this.close()});
		return closeButton;
	};

	// create rounded corner
	Base.prototype.createConer = function() {
		var container = document.createElement('div');
		var margins = [1, 1, 1, 2, 2, 3, 4, 5, 6, 9];
		for (var i = 0, len = margins.length; i < len; i++) {
			var cornerDiv = document.createElement('div');
			cornerDiv.style.height = '1px';
			cornerDiv.style.overflow = 'hidden';
			cornerDiv.style.backgroundColor = this.options.backgroundColor;
			cornerDiv.style.margin = '0px ' + margins[i] + 'px';
			container.appendChild(cornerDiv);
		}
		return container;
	};

	// start animation
	Base.prototype.startAnimation = function() {
	
		var _this = this;
		var start = new Date().getTime();
		var overlay = this.overlay;
		var dialog = this.dialog;
		var toOpacity = this.options.opacity;
		var duration = this.options.duration;
		var transition = this.options.transition;

		// fix dialog position
		var dialogHeight = dialog.offsetHeight;
		dialog.style.top = -dialogHeight + 'px';

		// start animation
		iid = setInterval(doAnimation, 10);

		function doAnimation() {
			try
			{
				if (_this.isClose) return;
				
				var opacity, dialogTop;
				var now = new Date().getTime();
				var pos = (now - start) / duration;
				var scrollOffset = _this.getScrollingOffset();
				if (pos >= 1) {
					opacity = toOpacity;
					dialogTop = scrollOffset.y;
					clearInterval(iid);
					// focus first focuseElements
					if (_this.focusElements && _this.focusElements[0]) _this.focusElements[0].focus();
				}
				else {
					// if you specify transition option, you can use other transition
					pos = transition(pos);
					opacity = pos * toOpacity;
					dialogTop = scrollOffset.y - dialogHeight * (1 - pos);
				}
				_this.setOpacity(overlay, opacity);
				dialog.style.top = dialogTop + 'px';
			}
			catch(e)
			{
				clearInterval(iid);
				throw e;
			}
		}
	};

	// end animation
	// if you specify onFinish option, you can use callback
	Base.prototype.endAnimation = function(onFinish) {
		var _this = this;
		var start = new Date().getTime();
		var overlay = this.overlay;
		var dialog = this.dialog;
		var toOpacity = this.options.opacity;
		var duration = this.options.duration;
		var transition = this.options.transition;

		var dialogHeight = dialog.offsetHeight;

		// start animation
		iid = setInterval(doAnimation, 10);

		function doAnimation() {
			try
			{
				var opacity, dialogTop;
				var now = new Date().getTime();
				var pos = (now - start) / duration;
				var scrollOffset = _this.getScrollingOffset();
				if (pos >= 1) {
					opacity = toOpacity;
					dialogTop = scrollOffset.y - dialogHeight;
					clearInterval(iid);
					_this.destroy();
					if (onFinish) onFinish();
				} else {
					pos = transition(pos);
					opacity = toOpacity - pos * toOpacity;
					dialogTop = scrollOffset.y - dialogHeight * pos;
				}
				_this.setOpacity(overlay, opacity);
				dialog.style.top = dialogTop + 'px';
			}
			catch(e)
			{
				clearInterval(iid);
				throw e;
			}
		}
	};

	// close dialog
	Base.prototype.close = function(func) {
		// prevent double click
		if (!this.isClose) {
			var _this = this;
			var _arguments = arguments;
			this.endAnimation(func);
			this.isClose = true;
		}
	};

	// focus control
	Base.prototype.focusControl = function() {
		var _this = this;
		this.focusElements = this.getFocusElements(this.dialog);
		for (var i = 0, len = this.focusElements.length; i < len; i++) {
			var currentElement = this.focusElements[i];
			var nextIndex = (i == len - 1 ? 0 : i + 1);
			var prevIndex = (i == 0 ? len - 1 : i - 1);
			var nextElement = this.focusElements[nextIndex];
			var prevElement = this.focusElements[prevIndex];
			var moveFocus = (function(n, p) {
				return function(event) {
					event = event || window.event;
					if (event.keyCode == TAB_KEY || (event.charCode == 25 && event.shiftKey)/* for safari */) {
						try {
							var elem = event.shiftKey ? p : n;
							var target = event.target || event.srcElement;
							if ((target.getAttribute('oncomplete') || target.oncomplete) != 'on') {
								elem.focus();
								_this.eventStop(event);
							} else {
								// need to move focus after auto-completion
								setTimeout(function(){
									// ignore Permission denied to get property XULElement.popupOpen
									try {
										elem.focus();
									} catch (e) {}
								}, 1);
							}
						} catch(e) {
							if (console) console.debug(e);
						}
					}
				};
			})(nextElement, prevElement);

			// stop key event
			this.observe(currentElement, this.getKeyEventName(), moveFocus);
		}
	};

	// get focus elements
	Base.prototype.getFocusElements = function(parentNode) {
		var list = [];
		var children = parentNode.childNodes;
		for (var i = 0, len = children.length; i < len; i++) {
			var node = children[i];
			if (node.nodeType != ELEMENT_NODE) continue;
			var tagName = node.tagName.toLowerCase();
			switch(tagName) {
			case 'input':
			case 'select':
			case 'a':
				list.push(node);
				break;
			default:
				Array.prototype.push.apply(list, this.getFocusElements(node));
			}
		}
		return list;
	};

	// destroy
	Base.prototype.destroy = function() {
		// repair event
		for (var i = 0, len = this.observers.length; i < len; i++) {
			this.stopObserving.apply(this, this.observers[i]);
		}
		document.body.removeChild(this.overlay);
		document.body.removeChild(this.dialog);
	};

	// get ViewPort
	Base.prototype.getViewPort = function() {
		var width, height;
		if (self.innerHeight) {
			// all except Explorer
			width = self.innerWidth;
			height = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) {
			// Explorer 6 Strict Mode
			width = document.documentElement.clientWidth;
			height = document.documentElement.clientHeight;
		} else if (document.body) {
			// other Explorers
			width = document.body.clientWidth;
			height = document.body.clientHeight;
		}
		return {width: width, height: height};
	};

	// get scrolling offset
	Base.prototype.getScrollingOffset = function() {
		var x,y;
		if (self.pageYOffset) {
			// all except Explorer
			x = self.pageXOffset;
			y = self.pageYOffset;
		} else if (document.documentElement && document.documentElement.scrollTop) {
			// Explorer 6 Strict
			x = document.documentElement.scrollLeft;
			y = document.documentElement.scrollTop;
		} else if (document.body) {
			// all other Explorers
			x = document.body.scrollLeft;
			y = document.body.scrollTop;
		}
		return {x: x, y: y};
	};
			
	// crossbrowser addEventListener
	Base.prototype.observe = function(element, event, observer) {
		if (element.addEventListener) {
			this.observers.push([element, event, observer]);
			element.addEventListener(event, observer, false);
		} else if (element.attachEvent) {
			this.observers.push([element, event, observer]);
			element.attachEvent('on' + event, observer);
		}
	};

	// crossbrowser removeEventListener
	Base.prototype.stopObserving = function(element, name, observer) {
		//if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown';
		
		if (element.removeEventListener) {
			element.removeEventListener(name, observer, false);
		} else if (element.detachEvent) {
			try {
				element.detachEvent('on' + name, observer);
			} catch (e) {}
		}
	};

	// crossbrowser event stop
	Base.prototype.eventStop = function(event) {
		if (event.preventDefault) {
			event.preventDefault();
			event.stopPropagation();
		} else {
			event.returnValue = false;
			event.cancelBubble = true;
		}	
	};

	// copy property
	Base.prototype.extend = function(destination, source) {
		for (var property in source) {
			destination[property] = source[property];
		}
		return destination;
	};

	// crossbrowser setOpacity
	Base.prototype.setOpacity = function(element, value) {
		element.style.filter = 'alpha(opacity=' + (value * 100) + ')';
		element.style.MozOpacity = value;
		element.style.opacity = value;
	};

	// judge Windows
	Base.prototype.isWin = function() {
		return /Windows/.test(navigator.userAgent);
	};

	// judge mac
	Base.prototype.isMac = function() {
		return /Macintosh/.test(navigator.userAgent);
	};
	
	Base.prototype.createOKCancelButtons = function(controlDiv, ok, cancel) {
		var buttons = this.isFirstButtonCancel() ? [cancel, ok] : [ok, cancel];
		controlDiv.appendChild(buttons[0]);
		controlDiv.appendChild(buttons[1]);
	};
	Base.prototype.isFirstButtonCancel = function() {
		return this.isMac();
	};

	Base.prototype.getKeyEventName = function() {
		return this.isWin() ? 'keydown'
			: this.isMac() ? 'keypress'
			: 'keypress';
	};

	// alert
	var Alert = function() { this.initialize.apply(this, arguments);};
	Base.prototype.extend(Alert.prototype, Base.prototype);
	Alert.prototype.createDialog = function() {
		var _this = this;
		var messageDiv = document.createElement('div');
		messageDiv.innerHTML = this.options.message;
		messageDiv.style.margin = '0 2em';
		var ok = document.createElement('input');
		ok.setAttribute('type', 'button');
		ok.value = this.options.okButton;
		this.observe(ok, 'click', function() {
			_this.close(function() {_this.options.onFinish(); });
		});

		var controlDiv = document.createElement('div');
		controlDiv.style.textAlign = 'right';
		controlDiv.style.marginTop = '0.2em';
		controlDiv.appendChild(ok);

		if (this.options.closeButton) {
			controlDiv.appendChild(this.createCloseButton());
		}

		var view = document.createElement('div');
		view.style.padding = '10px 10px 0px 10px';
		view.style.backgroundColor = this.options.backgroundColor;
		view.appendChild(messageDiv);
		view.appendChild(controlDiv);
		var dialog = document.createElement('div');
		dialog.appendChild(view);
		dialog.appendChild(this.createConer());

		// use this later 
		this.ok = ok;

		return dialog;
	};
	Alert.prototype.cancelDialog = function() {
		var _this = this;
		this.close(function() {_this.options.onFinish(); });
	};

	// confirm
	var Confirm = function() { this.initialize.apply(this, arguments);};
	Base.prototype.extend(Confirm.prototype, Base.prototype);
	Confirm.prototype.createDialog = function() {
		var _this = this;
		var messageDiv = document.createElement('div');
		messageDiv.innerHTML = this.options.message;
		messageDiv.style.margin = '0 2em';

		var cancel = document.createElement('input');
		cancel.setAttribute('type', 'button');
		cancel.value = this.options.cancelButton;
		// prevent double click
		this.observe(cancel, 'click', function() {
			_this.close(function() {_this.options.onFinish(false);});
		});

		var ok = document.createElement('input');
		ok.setAttribute('type', 'button');
		ok.value = this.options.okButton;
		this.observe(ok, 'click', function() {
			_this.close(function() {_this.options.onFinish(true);});
		});

		var controlDiv = document.createElement('div');
		controlDiv.style.textAlign = 'right';
		controlDiv.style.marginTop = '0.2em';
		this.createOKCancelButtons(controlDiv, ok, cancel);

		if (this.options.closeButton) {
			controlDiv.appendChild(this.createCloseButton());
		}

		var view = document.createElement('div');
		view.style.padding = '10px 10px 0px 10px';
		view.style.backgroundColor = this.options.backgroundColor;
		view.appendChild(messageDiv);
		view.appendChild(controlDiv);
		var dialog = document.createElement('div');
		dialog.appendChild(view);
		dialog.appendChild(this.createConer());

		// use this later 
		this.cancel = cancel;

		return dialog;
	};
	Confirm.prototype.cancelDialog = function() {
		var _this = this;
		this.close(function() {_this.options.onFinish(false);});
	};

	// prompt
	var Prompt = function() { this.initialize.apply(this, arguments);};
	Base.prototype.extend(Prompt.prototype, Base.prototype);
	Prompt.prototype.createDialog = function() {
		var _this = this;
		var messageDiv = document.createElement('div');
		messageDiv.innerHTML = this.options.message;
		messageDiv.style.margin = '0 2em';

		// create form includes 1input text and 2buttons.
		var form = document.createElement('form');
		form.style.margin = '0 2em';
		var input = document.createElement('input');
		input.setAttribute('type', this.options.promptInputType);
		input.style.width = '80%';
		input.value = this.options.value;
		form.appendChild(input);
		// prevent double click
		var doOnce = false;
		this.observe(form, 'submit', function(event) {
			event = event || window.event;
			_this.close(function() {_this.options.onFinish(input.value);});
			// prevent form submitting
			_this.eventStop(event);
		});

		var cancel = document.createElement('input');
		cancel.setAttribute('type', 'button');
		cancel.value = this.options.cancelButton;
		this.observe(cancel, 'click', function() {
			_this.close(function() {_this.options.onFinish(null);});
		});

		var ok = document.createElement('input');
		ok.setAttribute('type', 'button');
		ok.value = this.options.okButton;
		this.observe(ok, 'click', function() {
			if (!doOnce) {
				_this.endAnimation(function() {_this.options.onFinish(input.value);});
				doOnce = true;
			}
		});

		var controlDiv = document.createElement('div');
		controlDiv.style.textAlign = 'right';
		controlDiv.style.marginTop = '0.2em';
		this.createOKCancelButtons(controlDiv, ok , cancel);

		if (this.options.closeButton) {
			controlDiv.appendChild(this.createCloseButton());
		}

		var view = document.createElement('div');
		view.style.padding = '10px 10px 0px 10px';
		view.style.backgroundColor = this.options.backgroundColor;
		view.appendChild(messageDiv);
		view.appendChild(form);
		view.appendChild(controlDiv);
		var dialog = document.createElement('div');
		dialog.appendChild(view);
		dialog.appendChild(this.createConer());

		// use this later 
		this.input = input;

		return dialog;
	};
	Prompt.prototype.cancelDialog = function() {
		var _this = this;
		this.close(function() {_this.options.onFinish(null);});
	};

	// General purpose dialog
	var Simple = function() { this.initialize.apply(this, arguments);};
	Base.prototype.extend(Simple.prototype, Base.prototype);
	Simple.prototype.createDialog = function() {
		var _this = this;
		var messageDiv = document.createElement('div');
		messageDiv.style.margin = '0';
		messageDiv.innerHTML = this.options.message;

		var view = document.createElement('div');
		view.style.padding = '10px 10px 0px 10px';
		view.style.backgroundColor = this.options.backgroundColor;
		view.appendChild(messageDiv);

		if (this.options.closeButton) {
			var controlDiv = document.createElement('div');
			controlDiv.style.textAlign = 'right';
			controlDiv.style.marginTop = '0.2em';
			controlDiv.appendChild(this.createCloseButton());
			view.appendChild(controlDiv);
		}

		var dialog = document.createElement('div');
		dialog.appendChild(view);

		dialog.appendChild(this.createConer());

		return dialog;
	};
	Simple.prototype.cancelDialog = function() {
		var _this = this;
		this.close(function() {_this.close();});
	};

	// dialog
	var Attach = function() { this.initialize.apply(this, arguments); };
	Base.prototype.extend(Attach.prototype, Base.prototype);
	Attach.prototype.initialize = function(options) {
		options.dialog.style.display = '';
		Base.prototype.initialize.apply(this, arguments);
	};
	Attach.prototype.createDialog = function() {
		var _this = this;
		
		if (this.options.closeButton)
			this.observe(this.options.closeButton, 'click', function() {_this.close()});
		
		return this.options.dialog;
	};
	Attach.prototype.cancelDialog = function() {
		var _this = this;
		this.close();
	};
	Attach.prototype.destroy = function() {
		// repair event
		for (var i = 0, len = this.observers.length; i < len; i++) {
			this.stopObserving.apply(this, this.observers[i]);
		}
		document.body.removeChild(this.overlay);
		this.dialog.style.display = 'none';
		// document.body.removeChild(this.dialog);
	};
	
	// export
	jxDialog.Base = Base;
	jxDialog.Alert = Alert;
	jxDialog.Confirm = Confirm;
	jxDialog.Prompt = Prompt;
	jxDialog.Simple = Simple;
	jxDialog.Attach = Attach;
	jxDialog.confirm = function(options) { return new Confirm(options); };
	jxDialog.alert = function(options) { return new Alert(options); };
	jxDialog.prompt = function(options) { return new Prompt(options); };
	jxDialog.simple = function(options) { return new Simple(options); };
	jxDialog.attach = function(options) { return new Attach(options); };
 })();
