/*
 * Copyright (C) 2006, hatena ( http://www.hatena.ne.jp/ ).
 *
 * This program is dual-licensed free software
 * you can redistribute it and/or modify it under the terms of the MIT License or the Academic Free License v2.1.
 */

try {
    if (typeof(MochiKit.Base) == 'undefined') {
        throw '';
    }
} catch (e) {
    throw 'KeyTypeListener depends on MochiKit.Base!';
}

try {
    if (typeof(MochiKit.Signal) == 'undefined') {
        throw '';
    }
} catch (e) {
    throw 'KeyTypeListener depends on MochiKit.Signal!';
}

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

update(MochiKit.Signal.Event.prototype, {
    detailedKeyString: function() {
        if (!this.key().string.match(/^KEY_(.*)/))
            return;

        var m = this.modifier();
        var key = RegExp.$1;
        var upcase = m.shift && key.length == 1;
            key = key['to' + (upcase ? 'Upper' : 'Lower') + 'Case']();
        var modifiers = ifilter(partial(operator.ne, key), upcase ? ['ctrl', 'alt', 'meta'] : ['ctrl','alt', 'shift', 'meta']);

        return extend(list(ifilter(KeyTypeListener.Util.propertyOf(m), modifiers)), [key]).join('-');
    }
});

KeyTypeListener.VERSION = "1.1";
KeyTypeListener.NAME = "KeyTypeListener";

if (typeof(KeyTypeListener.Util) == 'undefined') {
    KeyTypeListener.Util = {};
}

MochiKit.Base.update(KeyTypeListener.Util, {
    appliedChain: function() {
        try {
            (partial(reduce, function(value, func) {
                return isUndefinedOrNull(value) ? value : func(value);
            })).apply(this, arguments);
            return true;
        } catch (e) {
            if (e != KeyTypeListener.StopKeyChain) {
                throw e;
            }
            return false; // for WinIE...
        }
    },

    propertyOf: function(obj) {
        return function(p) { return obj[p] };
    },

    fcall: function(func) {
        return func.call(extend(null, arguments, 1));
    },

    thruWith: function(func) {
        return function(value) { 
            func.apply(this, arguments);
            return value;
       };
    },

    deleteValue: function(value, arr) {
        var ary = [];
        map(function(a) { if(compare(value, a) != 0) ary.push(a) }, arr);
        return ary;
    },

    lastPartial: function(func) {
        var args = m.extend(null, arguments, 1);
        return function() { 
            func.apply(null, flattenArguments(arguments, args));
        };
    },

    EXPORT_TAGS: {
        ':all': ['appliedChain', 'propertyOf', 'fcall', 'thruWith', 'lastPartial', 'deleteValue']
    }
});

// MochiKit.Base._exportSymbols(this, KeyTypeListener.Util);

update(KeyTypeListener, {
    KEYTYPE: window.opera ? 'onkeypress' : 'onkeydown',
    StopKeyChain: NamedError('stopKeyChain'),

    keyListener: function(e) {
        var self = this;
        var u = KeyTypeListener.Util;
        var el = e.src();
        try {
            do {
                var keybinds = this.keybinds(el);
                if (keybinds) {
                    var res = u.appliedChain(
                      [u.propertyOf(keybinds), u.thruWith(method(e, 'stop')), partial(this.keyChain, e), u.fcall], 
                      this.lastkey(e)
                    );
                    if (res === false) {
                        throw self.StopKeyChain;
                    }
                }
                el = el.parentNode;
            } while (el);
        } catch (e) {
            if (e != self.StopKeyChain) {
                throw e;
            }
        }
    },

    keyChain: function(e, callbacks) {
        var self = this;
        return function() {
          for (var i = 0, len = callbacks.length; i < len; i++) {
              if( false === callbacks[i](e))
                  throw this.StopKeyChain;
          }
        };
    },
    
    lastkey: function(e) {
        return window.opera? e.key().string : e.detailedKeyString();
    },
    
    _keybinds_elements: [],
    _keybinds: {},
    keybinds: function(el) {
        var keybind_index = false;
        for (var i = 0, len = this._keybinds_elements.length; i < len; i++) {
            if(this._keybinds_elements[i] == el) {
                keybind_index = i;
                break;
            }
        }
        if (keybind_index === false) {
            keybind_index = this._keybinds_elements.length;
            this._keybinds_elements.push(el);
            this._keybinds['_' + keybind_index] = {};
        }
        return this._keybinds['_' + keybind_index];
    },

    elementKeybinds: {
    },

    getBoundKeybindFunctions: function(key, element/*= document */) {
        if (arguments.length <= 1)
            element = document;

        if( this.keybinds(element) && this.keybinds(element)[key] )
            return this.keybinds(element)[key];
    },

    addKeybind: function(key, func, element/*= document */) {
        if (arguments.length <= 2)
            element = document;

        var keybind = this.keybinds(element);
        if (keys(keybind).length == 0) {
            connect(element, this.KEYTYPE, this, this.keyListener);
        }
        if ( !isArrayLike(keybind[key]) ) {
            keybind[key] = [];
        }

        keybind[key].push(func);

        return func;
    },

    removeKeybind: function(key, func, element/*= document */) {
        if (arguments.length <= 2)
            element = document;
        this.keybinds(element)[key] = KeyTypeListener.Util.deleteValue(func, this.keybinds(element)[key]);
    },

    removeKeybindAll: function(key, element/*= document */) {
        if (arguments.length <= 1)
            element = document;
        this.keybinds(element)[key] = [];
    },

    cancelKeybind: function(element) {
        connect(element, this.KEYTYPE, methodcaller('stopPropagation'));
    },

    EXPORT_TAGS: {
        ':all': ['addKeybind', 'removeKeybind', 'cancelKeybind', 'removeKeybindAll', 'getBoundKeybindFunctions']
    }
});

bindMethods(KeyTypeListener);
nameFunctions(KeyTypeListener);

MochiKit.Base._exportSymbols(this, KeyTypeListener);

/* exemple

addKeybind('a', function(ev) {
    p(ev);
    return false;
});

addKeybind('a', partial(p,'_a') );
var c = addKeybind('c', partial(p, ('_c')) );
var d = addKeybind('d', partial(p, ('_d')) );
removeKeybind('d', d);
addKeybind('c', partial(p,'_c+text') , $('text'));
removeKeybind('c', c);

*/
