/* -------------------------------------------------------------------------- */
/** 
 *    @fileoverview
 *       keyboard equivalents system.
 *
 *    @version 2.0.20091129
 *    @requires jquery.js
 *    @requires bajl.js
 */
/* -------------------------------------------------------------------------- */
(function($) {



/* --------------- Class : BAJL.KeyEquiv --------------- */
/**
 * provide keyboard equivalents system; (use this as single object!)
 * @class keyboard equivalents
 * @extends BAJL.Observable
 * @constructor
 */
BAJL.KeyEquiv = function() {
	/** associative array of key asign. { keymark : { aliasName, keyCode, DOMName }, ... }
	    @type Object
		@private
		@constant */
	this.keyAlias = {
		'$' : { aliasName : 'Shift'  , keyCode : 16, DOMName : 'shiftKey' },
		'%' : { aliasName : 'Ctrl'   , keyCode : 17, DOMName : 'ctrlKey'  },
		'~' : { aliasName : 'Alt'    , keyCode : 18, DOMName : 'altKey'   },  /* alt (Win), option  (Mac) */
		'&' : { aliasName : 'Meta'   , keyCode : 91, DOMName : 'metaKey'  },  /* win (Win), command (Mac) */
		'|' : { aliasName : 'Tab'    , keyCode :  9, DOMName : ''         },
		'#' : { aliasName : 'Enter'  , keyCode : 13, DOMName : ''         },
		'!' : { aliasName : 'Esc'    , keyCode : 27, DOMName : ''         },
		'<' : { aliasName : '\u2190' , keyCode : 37, DOMName : ''         },  /* left  */
		'{' : { aliasName : '\u2191' , keyCode : 38, DOMName : ''         },  /* up    */
		'>' : { aliasName : '\u2192' , keyCode : 39, DOMName : ''         },  /* right */
		'}' : { aliasName : '\u2193' , keyCode : 40, DOMName : ''         }   /* down  */
	}

	this.init();
}

BAJL.KeyEquiv.prototype = new BAJL.Observable;

/**
 * initialize.
 * @private
 */
BAJL.KeyEquiv.prototype.init = function() {
	$(document).keydown(BAJL.Delegate(this.onKeyDown, this));
}

/**
 * register key equivalent for function call.
 * @param {String}                     keys             key combination defining string
 * @param {BAJL.KeyEquiv.callbackFunc} func             Function/method for key equivalent
 * @param {Object}                     [aThisObject]    Object that will be a global object ('this') in func
 * @return normalized key conbinaion string
 * @type String
 */
BAJL.KeyEquiv.prototype.addKey = function(keys, func, aThisObject) {
	if (typeof keys != 'string' || keys == '') {
		throw new TypeError('BAJL.KeyEquiv.addKey: first argument must be a key conbination defining string.');
	} else if (typeof func != 'function') {
		throw new TypeError('BAJL.KeyEquiv.addKey: second argument must be a Function object.');
	} else {
		this.addCallback(this.normalizeKey(keys), func, aThisObject);
		return keys;
	}
}

/**
 * get key name(s) in human readable format.
 * @param {String} keys    key combination defining string
 * @return an array of key conbinaion string
 * @type Array
 */
BAJL.KeyEquiv.prototype.getKeyName = function(keys) {
	if (typeof keys != 'string' || keys == '') {
		throw new TypeError('BAJL.KeyEquiv.getKeyName: first argument must be a key conbination defining string.');
	} else {
		return this.normalizeKey(keys).split('').map(function(key) {
			var keyAlias = this.keyAlias[key];
			return (!keyAlias) ? key : keyAlias.aliasName;
		}, this);
	}
}

/**
 * get key combination defining string.
 * @param {Number} keyCode    key code
 * @return key combination defining string; or '' when non-matched.
 * @type String
 */
BAJL.KeyEquiv.prototype.getKeyAlias = function(keyCode) {
	if (typeof keyCode != 'number') {
		throw new TypeError('BAJL.KeyEquiv.getKeyDefString: first argument must be a number.');
	} else {
		for (var key in this.keyAlias) {
			if (this.keyAlias[key].keyCode == keyCode) {
				return key;
			}
		}
		return '';
	}
}

/**
 * normalize key combination string.
 * @param {String} keys    key combination defining string
 * @return normalized key conbinaion string
 * @type String
 * @private
 */
BAJL.KeyEquiv.prototype.normalizeKey = function(keys) {
	if (typeof keys != 'string' || keys == '') {
		throw new TypeError('BAJL.KeyEquiv.normalizeKey: first argument must be a key conbination defining string.');
	} else {
//		return keys.toUpperCase().split('').sort().filter(function(c, i, a) { return (c != a[i + 1]) }).join('');

		// an error occurs above when prototype.js is used concurrently...
		var arr = keys.toUpperCase().split('').sort();
		return arr.filter(function(c, i) { return (c != arr[i + 1]) }).join('');
	}
}

/**
 * event hander for 'keydown' event.
 * @param {Event} e    event object
 * @private
 */
BAJL.KeyEquiv.prototype.onKeyDown = function(e) {
	var modKeys = [];
	for (var alias in this.keyAlias) {
		var domName = this.keyAlias[alias].DOMName;
		if (domName && e[domName]) {
			modKeys.push(alias);
		}
	}
	for (var keys in this.callbackChains) {
		var keyMatched = keys.split('').every(function(key) {
			var keyAlias = this.keyAlias[key];
			return (!keyAlias) ?
				Boolean(key == String.fromCharCode(e.keyCode).toUpperCase()) :
				Boolean(e[keyAlias.DOMName] || e.keyCode == keyAlias.keyCode);
		}, this);
		var modMatched = modKeys.every(function(mod) { return (keys.indexOf(mod) > -1) });
		if (keyMatched && modMatched) {
			this.doCallback(keys, e, keys, this.getKeyName(keys));
			break;
		}
	}
}

/**
 * fire by key combination.
 * @param {String} keys    key combination defining string
 */
BAJL.KeyEquiv.prototype.fireKey = function(keys) {
	if (typeof keys != 'string' || keys == '') {
		throw new TypeError('BAJL.KeyEquiv.fireKey: first argument must be a key conbination defining string.');
	} else {
//		// using DOM3 events...
//		var e = document.createEvent('keyboardEvent');
//		e.initKeyboardEvent(
//			/* type          */ 'keydown',
//			/* canBubble     */ true,
//			/* cancelable    */ true,
//			/* view          */ window,
//			/* keyIdentifier */ 'Undefined',
//			/* keyLocation   */ DOM_KEY_LOCATION_STANDARD,
//			/* modifiersList */ ''
//		);
//		document.dispatchEvent(e);

		// temporary...
		var keys = this.normalizeKey(keys);
		var e    = {};
		this.doCallback(keys, e, keys, this.getKeyName(keys));
	}
}

if (BAJL.settings.common.useBackCompat) {
	/** @deprecated use {@link #addKey} method instead of this method. */
	BAJL.KeyEquiv.prototype.addKeyEquiv = function() { return this.addKey.apply(this, arguments) }
}



/* -------------------- for JSDoc toolkit output -------------------- */
/**
 * a callback for keydown, managed by {@link BAJL.KeyEquiv}
 * @name BAJL.KeyEquiv.callback
 * @param {Event}    e           event object
 * @param {String}   key         key combination defining string
 * @param {String[]} keyNames    an array of human-readable key names
 * @function
 * @see BAJL.KeyEquiv
 */



/* -------------------- for backward compatibilities -------------------- */

if (BAJL.settings.common.useBackCompat) {
	BAJL.CreateBackCompat({
		  'BA_KEYEQUIV' : BAJL.Singleton(BAJL.KeyEquiv)
		, 'BAKeyEquiv'  : BAJL.KeyEquiv
	});
}



})(jQuery);
