////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

var __CFMOUSE_BUTTON_HOLD_REPEAT_INTERVAL = 100;
var __CFMOUSE_BUTTON_HOLD_REPEAT_START = 500;

var __CFMOUSE_ERROR_DOCUMENT = "document mouse position is unavailable";
var __CFMOUSE_ERROR_SCREEN = "screen mouse position is unavailable";

////////////////////////////////////////////////////////////////////////////////
// Static variables
////////////////////////////////////////////////////////////////////////////////

var __cfMouseDocumentPositionX = undefined;
var __cfMouseDocumentPositionY = undefined;
var __cfMouseLeftButtonHoldTimer = undefined;
var __cfMouseLeftButtonsDown = new Array();
var __cfMouseOnMouseDownHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseDownLeftHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseDownRightHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseHoldHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseHoldLeftHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseHoldRightHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseMoveHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseUpHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseUpLeftHandlerSet = new CFEventHandlerSet();
var __cfMouseOnMouseUpRightHandlerSet = new CFEventHandlerSet();
var __cfMouseOverDocument = undefined;
var __cfMouseRightButtonDown = false;
var __cfMouseRightButtonHoldTimer = undefined;
var __cfMouseScreenPositionX = undefined;
var __cfMouseScreenPositionY = undefined;

////////////////////////////////////////////////////////////////////////////////
// Private
////////////////////////////////////////////////////////////////////////////////

function __cfMouseGetEventResult(results)
{
    var trueBias = 0;
    var value;
    for (var i = 0; i < results.length; i++) {
        value = results[i];
        if (typeof(value) != "undefined") {
            trueBias += value ? 1 : -1;
        }
    }
    return trueBias >= 0;
}

function __cfMouseHandleLeftButtonDown(button)
{
    var buttonDown = cfMouseIsLeftButtonDown();
    var results = new Array();
    __cfMouseLeftButtonsDown[button] = true;
    if (! buttonDown) {
        results = results.concat(__cfMouseOnMouseDownLeftHandlerSet.execute(),
                                 __cfMouseOnMouseDownHandlerSet.execute());
        if (__cfMouseOnMouseHoldLeftHandlerSet.getLength() ||
            __cfMouseOnMouseHoldHandlerSet.getLength()) {
            __cfMouseLeftButtonHoldTimer =
                window.setTimeout("__cfMouseHandleOnMouseHoldLeftEvent()",
                                  __CFMOUSE_BUTTON_HOLD_REPEAT_START);
        }
    }
    return __cfMouseGetEventResult(results);
}

function __cfMouseHandleLeftButtonUp(button)
{
    var results = new Array();
    __cfMouseLeftButtonsDown[button] = false;
    if (! cfMouseIsLeftButtonDown()) {
        if (__cfMouseLeftButtonHoldTimer) {
            window.clearTimeout(__cfMouseLeftButtonHoldTimer);
            __cfMouseLeftButtonHoldTimer = undefined;
        }
        results = results.concat(__cfMouseOnMouseUpLeftHandlerSet.execute(),
                                 __cfMouseOnMouseUpHandlerSet.execute());
    }
    return __cfMouseGetEventResult(results);
}

function __cfMouseHandleMove(docX, docY, screenX, screenY)
{
    __cfMouseDocumentPositionX = docX;
    __cfMouseDocumentPositionY = docY;
    __cfMouseScreenPositionX = screenX;
    __cfMouseScreenPositionY = screenY;
    return __cfMouseGetEventResult(__cfMouseOnMouseMoveHandlerSet.execute());
}

function __cfMouseHandleRightButtonDown()
{
    __cfMouseRightButtonDown = true;
    var results = new Array();
    results = results.concat(__cfMouseOnMouseDownRightHandlerSet.execute(),
                             __cfMouseOnMouseDownHandlerSet.execute());
    if (__cfMouseOnMouseHoldRightHandlerSet.getLength() ||
        __cfMouseOnMouseHoldHandlerSet.getLength()) {
        __cfMouseRightButtonHoldTimer =
            window.setTimeout("__cfMouseHandleOnMouseHoldRightEvent()",
                              __CFMOUSE_BUTTON_HOLD_REPEAT_START);
    }
    return __cfMouseGetEventResult(results);
}

function __cfMouseHandleRightButtonUp()
{
    __cfMouseRightButtonDown = false;
    if (__cfMouseRightButtonHoldTimer) {
        window.clearTimeout(__cfMouseRightButtonHoldTimer);
        __cfMouseRightButtonHoldTimer = undefined;
    }
    var results = new Array();
    results = results.concat(__cfMouseOnMouseUpRightHandlerSet.execute(),
                             __cfMouseOnMouseUpHandlerSet.execute());
    return __cfMouseGetEventResult(results);
}

function __cfMousePatchMouseClickEvents(e, isDown)
{
    var mouseDownFunc;
    var mouseUpFunc;
    var result;
    if (e) {
        if (typeof(e.which) == "number") {
            mouseDownFunc = __cfMouseHandleOnMouseDownEventWhich;
            mouseUpFunc = __cfMouseHandleOnMouseUpEventWhich;
        } else if (typeof(e.button) == "number") {
            mouseDownFunc = __cfMouseHandleOnMouseDownEventButtonArg;
            mouseUpFunc = __cfMouseHandleOnMouseUpEventButtonArg;
        } else {
            mouseDownFunc = __cfMouseHandleOnMouseDownEventUndefinedArg;
            mouseUpFunc = __cfMouseHandleOnMouseUpEventUndefinedArg;
        }
        if (isDown) {
            result = mouseDownFunc(e);
        } else {
            result = mouseUpFunc(e);
        }
    } else {
        e = window.event;
        if (! e) {
            mouseDownFunc = __cfMouseHandleOnMouseDownEventUndefinedNoArg;
            mouseUpFunc = __cfMouseHandleOnMouseUpEventUndefinedNoArg;
        } else if (typeof(e.button) == "number") {
            mouseDownFunc = __cfMouseHandleOnMouseDownEventButtonNoArg;
            mouseUpFunc = __cfMouseHandleOnMouseUpEventButtonNoArg;
        } else {
            mouseDownFunc = __cfMouseHandleOnMouseDownEventUndefinedNoArg;
            mouseUpFunc = __cfMouseHandleOnMouseUpEventUndefinedNoArg;
        }
        if (isDown) {
            result = mouseDownFunc();
        } else {
            result = mouseUpFunc();
        }
    }
    document.onmousedown = __cfMouseHandleOnMouseDownEvent = mouseDownFunc;
    document.onmouseup = __cfMouseHandleOnMouseUpEvent = mouseUpFunc;
    return result;
}

////////////////////////////////////////////////////////////////////////////////
// Patches
////////////////////////////////////////////////////////////////////////////////

// __cfMouseHandleOnMouseDownEvent patches

function __cfMouseHandleOnMouseDownEventButtonArg(e)
{
    var button = e.button;
    if (button == 2) {
        return __cfMouseHandleRightButtonDown();
    }
    return __cfMouseHandleLeftButtonDown(button);
}

function __cfMouseHandleOnMouseDownEventButtonNoArg()
{
    return __cfMouseHandleOnMouseDownEventButtonArg(window.event);
}

function __cfMouseHandleOnMouseDownEventUndefinedArg(e)
{
    return __cfMouseHandleOnMouseDownEventUndefinedNoArg();
}

function __cfMouseHandleOnMouseDownEventUndefinedNoArg()
{
    return __cfMouseHandleLeftButtonDown(1);
}

function __cfMouseHandleOnMouseDownEventWhich(e)
{
    var button = e.which;
    if (button == 3) {
        return __cfMouseHandleRightButtonDown();
    }
    return __cfMouseHandleLeftButtonDown(button);
}

// __cfMouseHandleOnMouseMoveEvent patches

function __cfMouseHandleOnMouseMoveEventClient(e)
{
    return __cfMouseHandleMove(e.clientX, e.clientY, e.screenX, e.screenY);
}

function __cfMouseHandleOnMouseMoveEventClientScrollElement()
{
    var e = window.event;
    var offsets = cfViewportGetScrollOffsets();
    var x = e.clientX;
    var y = e.clientY;
    if (typeof(x) == "number") {
        x += offsets.x;
    }
    if (typeof(y) == "number") {
        y += offsets.y;
    }
    return __cfMouseHandleMove(x, y, e.screenX, e.screenY);
}

function __cfMouseHandleOnMouseMoveEventPage(e)
{
    return __cfMouseHandleMove(e.pageX, e.pageY, e.screenX, e.screenY);
}

function __cfMouseHandleOnMouseMoveEventScreenOnly()
{
    e = window.event;
    return __cfMouseHandleMove(undefined, undefined, e.screenX, e.screenY);
}

function __cfMouseHandleOnMouseMoveEventUndefinedArg(e)
{
    return __cfMouseHandleOnMouseMoveEventUndefinedNoArg();
}

function __cfMouseHandleOnMouseMoveEventUndefinedNoArg()
{
    return __cfMouseHandleMove(undefined, undefined, undefined, undefined);
}

// __cfMouseHandleOnMouseUpEvent patches

function __cfMouseHandleOnMouseUpEventButtonArg(e)
{
    var button = e.button;
    if (button == 2) {
        return __cfMouseHandleRightButtonUp();
    }
    return __cfMouseHandleLeftButtonUp(button);
}

function __cfMouseHandleOnMouseUpEventButtonNoArg()
{
    return __cfMouseHandleOnMouseUpEventButtonArg(window.event);
}

function __cfMouseHandleOnMouseUpEventUndefinedArg(e)
{
    return __cfMouseHandleOnMouseUpEventUndefinedNoArg();
}

function __cfMouseHandleOnMouseUpEventUndefinedNoArg()
{
    return __cfMouseHandleLeftButtonUp(1);
}

function __cfMouseHandleOnMouseUpEventWhich(e)
{
    var button = e.which;
    if (button == 3) {
        return __cfMouseHandleRightButtonUp();
    }
    return __cfMouseHandleLeftButtonUp(button);
}

////////////////////////////////////////////////////////////////////////////////
// Events
////////////////////////////////////////////////////////////////////////////////

function __cfMouseHandleOnLoadEvent()
{
    document.onmousedown = __cfMouseHandleOnMouseDownEvent;
    document.onmousemove = __cfMouseHandleOnMouseMoveEvent;
    document.onmouseout = __cfMouseHandleOnMouseOutEvent;
    document.onmouseover = __cfMouseHandleOnMouseOverEvent;
    document.onmouseup = __cfMouseHandleOnMouseUpEvent;
    if (document.captureEvents) {
        document.captureEvents(Event.MOUSEDOWN);
        document.captureEvents(Event.MOUSEMOVE);
        document.captureEvents(Event.MOUSEOUT);
        document.captureEvents(Event.MOUSEOVER);
        document.captureEvents(Event.MOUSEUP);
    }
}

function __cfMouseHandleOnMouseDownEvent(e)
{
    return __cfMousePatchMouseClickEvents(e, true);
}

function __cfMouseHandleOnMouseHoldLeftEvent()
{
    var results = new Array();
    if (cfMouseIsLeftButtonDown()) {
        __cfMouseOnMouseHoldLeftHandlerSet.execute();
        __cfMouseOnMouseHoldHandlerSet.execute();
        __cfMouseLeftButtonHoldTimer =
            window.setTimeout("__cfMouseHandleOnMouseHoldLeftEvent()",
                              __CFMOUSE_BUTTON_HOLD_REPEAT_INTERVAL);
    }
}

function __cfMouseHandleOnMouseHoldRightEvent()
{
    if (cfMouseIsRightButtonDown()) {
        __cfMouseOnMouseHoldRightHandlerSet.execute();
        __cfMouseOnMouseHoldHandlerSet.execute();
        __cfMouseRightButtonHoldTimer =
            window.setTimeout("__cfMouseHandleOnMouseHoldRightEvent()",
                              __CFMOUSE_BUTTON_HOLD_REPEAT_INTERVAL);
    }
}

function __cfMouseHandleOnMouseMoveEvent(e)
{
    var func;
    var result;
    if (e) {
        if ((typeof(e.pageX) == "number") || (typeof(e.pageY) == "number")) {
            func = __cfMouseHandleOnMouseMoveEventPage;
        } else if ((typeof(e.clientX) == "number") ||
                   (typeof(e.clientY) == "number")) {
            func = __cfMouseHandleOnMouseMoveEventClient;
        } else {
            func = __cfMouseHandleOnMouseMoveEventUndefinedArg;
        }
        result = func(e);
    } else {
        e = window.event;
        if (! e) {
            func = __cfMouseHandleOnMouseMoveEventUndefinedNoArg;
        } else if ((typeof(e.clientX) == "number") ||
                   (typeof(e.clientY) == "number")) {
            func = __cfMouseHandleOnMouseMoveEventClientScrollElement;
        } else {
            func = __cfMouseHandleOnMouseMoveEventScreenOnly;
        }
        result = func();
    }
    document.onmousemove = __cfMouseHandleOnMouseMoveEvent = func;
    return result;
}

function __cfMouseHandleOnMouseOutEvent(e)
{
    __cfMouseOverDocument = false;
    __cfMouseDocumentPositionX = undefined;
    __cfMouseDocumentPositionY = undefined;
    return true;
}

function __cfMouseHandleOnMouseOverEvent(e)
{
    __cfMouseOverDocument = true;
    return __cfMouseHandleOnMouseMoveEvent(e);
}

function __cfMouseHandleOnMouseUpEvent(e)
{
    return __cfMousePatchMouseClickEvents(e, false);
}

////////////////////////////////////////////////////////////////////////////////
// Public API
////////////////////////////////////////////////////////////////////////////////

function cfMouseAddOnMouseDownCallback()
{
    var handlerSet = __cfMouseOnMouseDownHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseDownLeftCallback()
{
    var handlerSet = __cfMouseOnMouseDownLeftHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseDownRightCallback()
{
    var handlerSet = __cfMouseOnMouseDownRightHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseHoldCallback()
{
    var handlerSet = __cfMouseOnMouseHoldHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseHoldLeftCallback()
{
    var handlerSet = __cfMouseOnMouseHoldLeftHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseHoldRightCallback()
{
    var handlerSet = __cfMouseOnMouseHoldRightHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseMoveCallback()
{
    var handlerSet = __cfMouseOnMouseMoveHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseUpCallback()
{
    var handlerSet = __cfMouseOnMouseUpHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseUpLeftCallback()
{
    var handlerSet = __cfMouseOnMouseUpLeftHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseAddOnMouseUpRightCallback()
{
    var handlerSet = __cfMouseOnMouseUpRightHandlerSet;
    handlerSet.add.apply(handlerSet, arguments);
}

function cfMouseGetDocumentPosition(ignoreError)
{
    var x = __cfMouseDocumentPositionX;
    var y = __cfMouseDocumentPositionY;
    if ((typeof(x) != "number") || (typeof(y) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("cfMouseGetDocumentPosition: " +
                              __CFMOUSE_ERROR_DOCUMENT);
    }
    return {x: x, y: y};
}

function cfMouseGetElementPosition(element, ignoreError)
{
    var documentPosition = cfMouseGetDocumentPosition(ignoreError);
    if (documentPosition) {
        var elementPosition = cfElementGetDocumentPosition(element,
                                                           ignoreError);
        if (elementPosition) {
            return {
                x: documentPosition.x - elementPosition.x,
                y: documentPosition.y - elementPosition.y
            };
        }
    }
    return undefined;
}

function cfMouseGetScreenPosition(ignoreError)
{
    var x = __cfMouseScreenPositionX;
    var y = __cfMouseScreenPositionY;
    if ((typeof(x) != "number") || (typeof(y) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("cfMouseGetScreenPosition: " +
                              __CFMOUSE_ERROR_SCREEN);
    }
    return {x: x, y: y};
}

function cfMouseGetViewportPosition(ignoreError)
{
    var offsets = cfViewportGetScrollOffsets(ignoreError);
    if (offsets) {
        var position = cfMouseGetDocumentPosition(ignoreError);
        if (position) {
            return {x: position.x - offsets.x, y: position.y - offsets.y};
        }
    }
    return undefined;
}

function cfMouseIsLeftButtonDown()
{
    for (var i = 0; i < __cfMouseLeftButtonsDown.length; i++) {
        if (__cfMouseLeftButtonsDown[i]) {
            return true;
        }
    }
    return false;
}

function cfMouseIsRightButtonDown()
{
    return __cfMouseRightButtonDown;
}

////////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////////

cfDocumentAddOnLoadCallback(__cfMouseHandleOnLoadEvent);
