define(function(require, exports, module) {
    var isWindows = require("ace/lib/useragent").isWindows;
    module.exports = function initInput(ace) {
        // use showkey --ascii to test
        var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
        var KEY_MODS = require("ace/lib/keys").KEY_MODS;
        var TERM_MODS = {
            "shift-": 2,
            "alt-": 3,
            "alt-shift-": 4,
            "ctrl-": 5,
            "ctrl-shift-": 6,
            "ctrl-alt-": 7,
            "ctrl-alt-shift-": 8,
        };
        var specialKeys = new HashHandler();
        // http://www.math.utah.edu/docs/info/features_7.html
        specialKeys.bindKeys({
            "Shift-PageUp": { command: "pageup" },
            "Shift-PageDown": { command: "pagedown" },
            "Ctrl-Up": { command: "scrollup" },
            "Ctrl-Down": { command: "scrolldown" },

            "Backspace": '\x7f',
            "Shift-Backspace": '\x08',
            "Alt-Backspace": '\x1b\x7f',
            "Tab": '\t',
            "Shift-Tab": '\x1b[Z',
            "Return": '\r',
            "Escape": '\x1b',
            "Left": '\x1b[D',
            "Right": '\x1b[C',
            "Up": '\x1b[A',
            "Down": '\x1b[B',
            "Delete": '\x1b[3~',
            "Insert": '\x1b[2~',
            "Home": '\x1b[1~',
            "End": '\x1b[4~',
            "PageUp": '\x1b[5~',
            "PageDown": '\x1b[6~',
            "F1": '\x1bOP',
            "F2": '\x1bOQ',
            "F3": '\x1bOR',
            "F4": '\x1bOS',
            "F5": '\x1b[15~',
            "F6": '\x1b[17~',
            "F7": '\x1b[18~',
            "F8": '\x1b[19~',
            "F9": '\x1b[20~',
            "F10": '\x1b[21~',
            "F11": '\x1b[23~',
            "F12": '\x1b[24~'
        });
        
        // make shell to behave more like regular editor
        var aliases = [{
            bindKey: { win: "Ctrl-left", mac: "Option-left" },
            name: "\u001bb" // "alt-b"
        }, {
            bindKey: { win: "Ctrl-right", mac: "Option-right" },
            name: "\u001bf" // "alt-b"
        }, {
            bindKey: { win: "Ctrl-Delete", mac: "Option-Delete" },
            name: "\u001bd" // "alt-d"
        }, {
            bindKey: { win: "Ctrl-Backspace", mac: "Option-Backspace" },
            name: "\x1b\x7f" // "alt-backspace"
        }, {
            bindKey: { win: "Ctrl-Delete", mac: "Option-Delete" },
            name: "\u001bd" // "alt-d"
        }, {
            bindKey: { win: "Alt-Backspace", mac: "Ctrl-Backspace" },
            name: "\u0015" // "ctrl-u"
        }, {
            bindKey: { win: "Alt-Delete", mac: "Ctrl-Delete" },
            name: "\u000b" // "ctrl-k"
        }, {
            bindKey: { win: "Ctrl-z", mac: "Cmd-z" },
            name: "\u0018\u0015" // "ctrl-x ctrl-u"
        }, {
            bindKey: { win: null, mac: "Cmd-Left" },
            name: "\x1b[1~" // "ctrl-x ctrl-u"
        }, {
            bindKey: { win: null, mac: "Cmd-Right" },
            name: "\x1b[4~" // "ctrl-x ctrl-u"
        }];
        
        specialKeys.addCommands(aliases);
        // SS3 as ^[O for 7-bit  '\x8fD';  SS3 as 0x8f for 8-bit
        var applicationKeys = {
            "left": '\x1bOD',
            "right": '\x1bOC',
            "up": '\x1bOA',
            "down": '\x1bOB',
            "home": '\x1bOH',
            "end": '\x1bOF'
        };
        
        function defaultHandler(ev) {
            var key = null;
            var keyCode = ev.keyCode;
            var isControl = ev.ctrlKey;
            var isMeta = specialKeys.platform == "mac" ? ev.metaKey : ev.altKey;
            // a-z and space
            if (isControl) {
                if (keyCode >= 65 && keyCode <= 90) {
                    key = String.fromCharCode(keyCode - 64);
                } else if (keyCode === 32 || keyCode == 192) {
                    // NUL
                    key = String.fromCharCode(0);
                } else if (keyCode >= 51 && keyCode <= 55) {
                    // escape, file sep, group sep, record sep, unit sep
                    key = String.fromCharCode(keyCode - 51 + 27);
                } else if (keyCode === 56) {
                    // delete
                    key = String.fromCharCode(127);
                } else if (keyCode === 219) {
                    // ^[ - escape
                    key = String.fromCharCode(27);
                } else if (keyCode === 221) {
                    // ^] - group sep
                    key = String.fromCharCode(29);
                } else if (keyCode === 189 || keyCode === 173) {
                    // _
                    key = String.fromCharCode(31);
                } else if (keyCode === 220) {
                    // SIGQUIT
                    key = String.fromCharCode(28);
                }
            } else if (isMeta) {
                if (keyCode >= 65 && keyCode <= 90) {
                    key = '\x1b' + String.fromCharCode(keyCode + 32);
                } else if (keyCode === 192) {
                    key = '\x1b`';
                } else if (keyCode >= 48 && keyCode <= 57) {
                    key = '\x1b' + (keyCode - 48);
                }
            }
            return key;
        } 
        
        this.handleKeyboard = function(data, hashId, keyString, keyCode, ev) {
            var term = data.editor.session.term;
            if (hashId == -1) {
                this.send(keyString);
                return { command: "null" };
            }
            if (term.applicationKeypad) {
                if (applicationKeys[keyString]) {
                    var mod = TERM_MODS[KEY_MODS[hashId]];
                    var str = applicationKeys[keyString];
                    if (mod)
                        str = "\u001b[1;" + mod + str.slice(-1);
                    this.send(str);
                    return { command: "null" };
                }
            }
            var key = specialKeys.findKeyCommand(hashId, keyString);
            if (typeof key == "string") {
                this.send(key);
                return { command: "null" };
            } else if (key && key.command) {
                return key;
            } else if (key && key.exec) {
                if (!key.isAvailable || key.isAvailable(ace))
                    return { command: key };
            } else if (key && key.name) {
                this.send(key.name);
                return { command: "null" };
            }
            
            key = defaultHandler(ev);
            if (key)
                this.send(key);

            return {
                command: "null",
                passEvent: !hashId || hashId === KEY_MODS.shift || (
                    // on mac key combos without ctrl or cmd trigger textinput
                    specialKeys.platform === "mac" && !(hashId & (KEY_MODS.ctrl | KEY_MODS.cmd))
                ) || (
                    // on windows 8+ calling preventDefault on win+space breaks textinput
                    specialKeys.platform === "win" && hashId == KEY_MODS.cmd && (keyCode == 32 || keyCode == -1)
                )
            };
        };

        var isCopyAvailable = function(ed) {
            if (ed && !ed.getCopyText)
                ed = ed.ace || ed.$editor;
            return ed && !!ed.getCopyText();
        };
        var noop = function() {};
        
        // Add special handling for editor keys
        specialKeys.addCommands([{
            name: "copy",
            bindKey: { mac: "Cmd-c", win: "Ctrl-c" },
            exec: noop,
            isAvailable: isCopyAvailable,
            passEvent: true
        }, {
            name: "cut",
            bindKey: { mac: "Cmd-x", win: "Ctrl-x" },
            exec: noop,
            isAvailable: isCopyAvailable,
            passEvent: true
        }, {
            name: "paste",
            bindKey: { mac: "Cmd-v", win: "Ctrl-v" },
            exec: noop,
            isAvailable: function(ed) {
                return true;
            },
            passEvent: true
        }, {
            name: "clear",
            bindKey: { mac: "Cmd-K", win: "Ctrl-K" },
            exec: function(ed) {
                var term = ed.ace.session.term;
                term.clear();
                term.send("\x0c"); //"ctrl-l"
            },
            isAvailable: function(editor) {
                return true; // todo disable in vim?
            }
        }]);
        
        ace.onPaste = function(text) {
            this.send(text.replace(/\r\n?|\n/g, this.session.term.convertEol ? "\n" : "\r"));
        };
        
        ace.setKeyboardHandler(this);
        ace.keyBinding.addKeyboardHandler(ace.commands);
        ace.commands.commandKeyBinding = {};
        
        this.send = ace.send = function(text) {
            ace.session.send && ace.session.send(text);
        };
        return specialKeys;
    };
});