/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2010, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

define(function (require, exports, module) {
    "use strict";

    var keys = require("./keys");
    var useragent = require("./useragent");

    var pressedKeys = null;
    var ts = 0;

    exports.addListener = function (elem, type, callback) {
        if (elem.addEventListener) {
            return elem.addEventListener(type, callback, false);
        }
        if (elem.attachEvent) {
            var wrapper = function () {
                callback.call(elem, window.event);
            };
            callback._wrapper = wrapper;
            elem.attachEvent("on" + type, wrapper);
        }
    };

    exports.removeListener = function (elem, type, callback) {
        if (elem.removeEventListener) {
            return elem.removeEventListener(type, callback, false);
        }
        if (elem.detachEvent) {
            elem.detachEvent("on" + type, callback._wrapper || callback);
        }
    };

    /*
     * Prevents propagation and clobbers the default action of the passed event
     */
    exports.stopEvent = function (e) {
        exports.stopPropagation(e);
        exports.preventDefault(e);
        return false;
    };

    exports.stopPropagation = function (e) {
        if (e.stopPropagation)
            e.stopPropagation();
        else
            e.cancelBubble = true;
    };

    exports.preventDefault = function (e) {
        if (e.preventDefault)
            e.preventDefault();
        else
            e.returnValue = false;
    };

    /*
     * @return {Number} 0 for left button, 1 for middle button, 2 for right button
     */
    exports.getButton = function (e) {
        if (e.type == "dblclick")
            return 0;
        if (e.type == "contextmenu" || (useragent.isMac && (e.ctrlKey && !e.altKey && !e.shiftKey)))
            return 2;

        // DOM Event
        if (e.preventDefault) {
            return e.button;
        }
        // old IE
        else {
            return {1: 0, 2: 2, 4: 1}[e.button];
        }
    };

    exports.capture = function (el, eventHandler, releaseCaptureHandler) {
        function onMouseUp(e) {
            eventHandler && eventHandler(e);
            releaseCaptureHandler && releaseCaptureHandler(e);

            exports.removeListener(document, "mousemove", eventHandler, true);
            exports.removeListener(document, "mouseup", onMouseUp, true);
            exports.removeListener(document, "dragstart", onMouseUp, true);
        }

        exports.addListener(document, "mousemove", eventHandler, true);
        exports.addListener(document, "mouseup", onMouseUp, true);
        exports.addListener(document, "dragstart", onMouseUp, true);

        return onMouseUp;
    };

    exports.addTouchMoveListener = function (el, callback) {
        if ("ontouchmove" in el) {
            var startx, starty;
            exports.addListener(el, "touchstart", function (e) {
                var touchObj = e.changedTouches[0];
                startx = touchObj.clientX;
                starty = touchObj.clientY;
            });
            exports.addListener(el, "touchmove", function (e) {
                var factor = 1,
                    touchObj = e.changedTouches[0];

                e.wheelX = -(touchObj.clientX - startx) / factor;
                e.wheelY = -(touchObj.clientY - starty) / factor;

                startx = touchObj.clientX;
                starty = touchObj.clientY;

                callback(e);
            });
        }
    };

    exports.addMouseWheelListener = function (el, callback) {
        if ("onmousewheel" in el) {
            exports.addListener(el, "mousewheel", function (e) {
                var factor = 8;
                if (e.wheelDeltaX !== undefined) {
                    e.wheelX = -e.wheelDeltaX / factor;
                    e.wheelY = -e.wheelDeltaY / factor;
                } else {
                    e.wheelX = 0;
                    e.wheelY = -e.wheelDelta / factor;
                }
                callback(e);
            });
        } else if ("onwheel" in el) {
            exports.addListener(el, "wheel", function (e) {
                var factor = 0.35;
                switch (e.deltaMode) {
                    case e.DOM_DELTA_PIXEL:
                        e.wheelX = e.deltaX * factor || 0;
                        e.wheelY = e.deltaY * factor || 0;
                        break;
                    case e.DOM_DELTA_LINE:
                    case e.DOM_DELTA_PAGE:
                        e.wheelX = (e.deltaX || 0) * 5;
                        e.wheelY = (e.deltaY || 0) * 5;
                        break;
                }

                callback(e);
            });
        } else {
            exports.addListener(el, "DOMMouseScroll", function (e) {
                if (e.axis && e.axis == e.HORIZONTAL_AXIS) {
                    e.wheelX = (e.detail || 0) * 5;
                    e.wheelY = 0;
                } else {
                    e.wheelX = 0;
                    e.wheelY = (e.detail || 0) * 5;
                }
                callback(e);
            });
        }
    };

    exports.addMultiMouseDownListener = function (elements, timeouts, eventHandler, callbackName) {
        var clicks = 0;
        var startX, startY, timer;
        var eventNames = {
            2: "dblclick",
            3: "tripleclick",
            4: "quadclick"
        };

        function onMousedown(e) {
            if (exports.getButton(e) !== 0) {
                clicks = 0;
            } else if (e.detail > 1) {
                clicks++;
                if (clicks > 4)
                    clicks = 1;
            } else {
                clicks = 1;
            }
            if (useragent.isIE) {
                var isNewClick = Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5;
                if (!timer || isNewClick)
                    clicks = 1;
                if (timer)
                    clearTimeout(timer);
                timer = setTimeout(function () {
                    timer = null
                }, timeouts[clicks - 1] || 600);

                if (clicks == 1) {
                    startX = e.clientX;
                    startY = e.clientY;
                }
            }

            e._clicks = clicks;

            eventHandler[callbackName]("mousedown", e);

            if (clicks > 4)
                clicks = 0;
            else if (clicks > 1)
                return eventHandler[callbackName](eventNames[clicks], e);
        }

        function onDblclick(e) {
            clicks = 2;
            if (timer)
                clearTimeout(timer);
            timer = setTimeout(function () {
                timer = null
            }, timeouts[clicks - 1] || 600);
            eventHandler[callbackName]("mousedown", e);
            eventHandler[callbackName](eventNames[clicks], e);
        }

        if (!Array.isArray(elements))
            elements = [elements];
        elements.forEach(function (el) {
            exports.addListener(el, "mousedown", onMousedown);
            if (useragent.isOldIE)
                exports.addListener(el, "dblclick", onDblclick);
        });
    };

    var getModifierHash = useragent.isMac && useragent.isOpera && !("KeyboardEvent" in window)
        ? function (e) {
            return 0 | (e.metaKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.ctrlKey ? 8 : 0);
        }
        : function (e) {
            return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
        };

    exports.getModifierString = function (e) {
        return keys.KEY_MODS[getModifierHash(e)];
    };

    function normalizeCommandKeys(callback, e, keyCode) {
        var hashId = getModifierHash(e);

        if (!useragent.isMac && pressedKeys) {
            if (e.getModifierState && (e.getModifierState("OS") || e.getModifierState("Win")))
                hashId |= 8;
            if (pressedKeys.altGr) {
                if ((3 & hashId) != 3)
                    pressedKeys.altGr = 0;
                else
                    return;
            }
            if (keyCode === 18 || keyCode === 17) {
                var location = "location" in e ? e.location : e.keyLocation;
                if (keyCode === 17 && location === 1) {
                    if (pressedKeys[keyCode] == 1)
                        ts = e.timeStamp;
                } else if (keyCode === 18 && hashId === 3 && location === 2) {
                    var dt = e.timeStamp - ts;
                    if (dt < 50)
                        pressedKeys.altGr = true;
                }
            }
        }

        if (keyCode in keys.MODIFIER_KEYS) {
            keyCode = -1;
        }

        // keyCode of right command is 93 on mac and 92 on windows.
        // keyCode of left command key is 91
        if (hashId & 8 && (keyCode >= 91 && keyCode <= 93)) {
            keyCode = -1;
        }

        if (!hashId && keyCode === 13) {
            var location = "location" in e ? e.location : e.keyLocation;
            if (location === 3) {
                callback(e, hashId, -keyCode);
                if (e.defaultPrevented)
                    return;
            }
        }

        if (useragent.isChromeOS && hashId & 8) {
            callback(e, hashId, keyCode);
            if (e.defaultPrevented)
                return;
            else
                hashId &= ~8;
        }

        // If there is no hashId and the keyCode is not a function key, then
        // we don't call the callback as we don't handle a command key here
        // (it's a normal key/character input).
        if (!hashId && !(keyCode in keys.FUNCTION_KEYS) && !(keyCode in keys.PRINTABLE_KEYS)) {
            return false;
        }

        return callback(e, hashId, keyCode);
    }


    exports.addCommandKeyListener = function (el, callback) {
        var addListener = exports.addListener;
        if (useragent.isOldGecko || (useragent.isOpera && !("KeyboardEvent" in window))) {
            // Old versions of Gecko aka. Firefox < 4.0 didn't repeat the keydown
            // event if the user pressed the key for a longer time. Instead, the
            // keydown event was fired once and later on only the keypress event.
            // To emulate the 'right' keydown behavior, the keyCode of the initial
            // keyDown event is stored and in the following keypress events the
            // stores keyCode is used to emulate a keyDown event.
            var lastKeyDownKeyCode = null;
            addListener(el, "keydown", function (e) {
                lastKeyDownKeyCode = e.keyCode;
            });
            addListener(el, "keypress", function (e) {
                return normalizeCommandKeys(callback, e, lastKeyDownKeyCode);
            });
        } else {
            var lastDefaultPrevented = null;

            addListener(el, "keydown", function (e) {
                pressedKeys[e.keyCode] = (pressedKeys[e.keyCode] || 0) + 1;
                var result = normalizeCommandKeys(callback, e, e.keyCode);
                lastDefaultPrevented = e.defaultPrevented;
                return result;
            });

            addListener(el, "keypress", function (e) {
                if (lastDefaultPrevented && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey)) {
                    exports.stopEvent(e);
                    lastDefaultPrevented = null;
                }
            });

            addListener(el, "keyup", function (e) {
                pressedKeys[e.keyCode] = null;
            });

            if (!pressedKeys) {
                resetPressedKeys();
                addListener(window, "focus", resetPressedKeys);
            }
        }
    };
    function resetPressedKeys() {
        // console.log("resetting")
        pressedKeys = Object.create(null);
    }

    if (typeof window == "object" && window.postMessage && !useragent.isOldIE) {
        var postMessageId = 1;
        exports.nextTick = function (callback, win) {
            win = win || window;
            var messageName = "zero-timeout-message-" + postMessageId;
            exports.addListener(win, "message", function listener(e) {
                if (e.data == messageName) {
                    exports.stopPropagation(e);
                    exports.removeListener(win, "message", listener);
                    callback();
                }
            });
            win.postMessage(messageName, "*");
        };
    }


    exports.nextFrame = typeof window == "object" && (window.requestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.msRequestAnimationFrame
        || window.oRequestAnimationFrame);

    if (exports.nextFrame)
        exports.nextFrame = exports.nextFrame.bind(window);
    else
        exports.nextFrame = function (callback) {
            setTimeout(callback, 17);
        };
});
