MouseHandler = createClass({

init: function (el, options) {
    var $el = $(el);
    this.$el = $el;
    this.options = options;
    this.currentPageX = 0;
    this.currentPageY = 0;
    this.el = el;
    this.splist = [];
    this.tooltip = null;
    this.over = false;
    this.displayTooltips = !options.get('disableTooltips');
    this.highlightEnabled = !options.get('disableHighlight');
},

registerSparkline: function (sp) {
    this.splist.push(sp);
    if (this.over) {
        this.updateDisplay();
    }
},

registerCanvas: function (canvas) {
    var $canvas = $(canvas.canvas);
    this.canvas = canvas;
    this.$canvas = $canvas;
    $canvas.mouseenter($.proxy(this.mouseenter, this));
    $canvas.mouseleave($.proxy(this.mouseleave, this));
    $canvas.click($.proxy(this.mouseclick, this));
},

reset: function (removeTooltip) {
    this.splist = [];
    if (this.tooltip && removeTooltip) {
        this.tooltip.remove();
        this.tooltip = undefined;
    }
},

mouseclick: function (e) {
    var clickEvent = $.Event('sparklineClick');
    clickEvent.originalEvent = e;
    clickEvent.sparklines = this.splist;
    this.$el.trigger(clickEvent);
},

mouseenter: function (e) {
    $(document.body).unbind('mousemove.jqs');
    $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
    this.over = true;
    this.currentPageX = e.pageX;
    this.currentPageY = e.pageY;
    this.currentEl = e.target;
    if (!this.tooltip && this.displayTooltips) {
        this.tooltip = new Tooltip(this.options);
        this.tooltip.updatePosition(e.pageX, e.pageY);
    }
    this.updateDisplay();
},

mouseleave: function () {
    $(document.body).unbind('mousemove.jqs');
    var splist = this.splist,
         spcount = splist.length,
         needsRefresh = false,
         sp, i;
    this.over = false;
    this.currentEl = null;

    if (this.tooltip) {
        this.tooltip.remove();
        this.tooltip = null;
    }

    for (i = 0; i < spcount; i++) {
        sp = splist[i];
        if (sp.clearRegionHighlight()) {
            needsRefresh = true;
        }
    }

    if (needsRefresh) {
        this.canvas.render();
    }
},

mousemove: function (e) {
    this.currentPageX = e.pageX;
    this.currentPageY = e.pageY;
    this.currentEl = e.target;
    if (this.tooltip) {
        this.tooltip.updatePosition(e.pageX, e.pageY);
    }
    this.updateDisplay();
},

updateDisplay: function () {
    var splist = this.splist,
         spcount = splist.length,
         needsRefresh = false,
         offset = this.$canvas.offset(),
         localX = this.currentPageX - offset.left,
         localY = this.currentPageY - offset.top,
         tooltiphtml, sp, i, result, changeEvent;
    if (!this.over) {
        return;
    }
    for (i = 0; i < spcount; i++) {
        sp = splist[i];
        result = sp.setRegionHighlight(this.currentEl, localX, localY);
        if (result) {
            needsRefresh = true;
        }
    }
    if (needsRefresh) {
        changeEvent = $.Event('sparklineRegionChange');
        changeEvent.sparklines = this.splist;
        this.$el.trigger(changeEvent);
        if (this.tooltip) {
            tooltiphtml = '';
            for (i = 0; i < spcount; i++) {
                sp = splist[i];
                tooltiphtml += sp.getCurrentRegionTooltip();
            }
            this.tooltip.setContent(tooltiphtml);
        }
        if (!this.disableHighlight) {
            this.canvas.render();
        }
    }
    if (result === null) {
        this.mouseleave();
    }
}

});

Tooltip = createClass({

sizeStyle: 'position: static !important;' +
    'display: block !important;' +
    'visibility: hidden !important;' +
    'float: left !important;',

init: function (options) {
    var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
        sizetipStyle = this.sizeStyle,
        offset;
    this.container = options.get('tooltipContainer') || document.body;
    this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
    this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
    // remove any previous lingering tooltip
    $('#jqssizetip').remove();
    $('#jqstooltip').remove();
    this.sizetip = $('<div/>', {
        id: 'jqssizetip',
        style: sizetipStyle,
        'class': tooltipClassname
    });
    this.tooltip = $('<div/>', {
        id: 'jqstooltip',
        'class': tooltipClassname
    }).appendTo(this.container);
    // account for the container's location
    offset = this.tooltip.offset();
    this.offsetLeft = offset.left;
    this.offsetTop = offset.top;
    this.hidden = true;
    $(window).unbind('resize.jqs scroll.jqs');
    $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
    this.updateWindowDims();
},

updateWindowDims: function () {
    this.scrollTop = $(window).scrollTop();
    this.scrollLeft = $(window).scrollLeft();
    this.scrollRight = this.scrollLeft + $(window).width();
    this.updatePosition();
},

getSize: function (content) {
    this.sizetip.html(content).appendTo(this.container);
    this.width = this.sizetip.width() + 1;
    this.height = this.sizetip.height();
    this.sizetip.remove();
},

setContent: function (content) {
    if (!content) {
        this.tooltip.css('visibility', 'hidden');
        this.hidden = true;
        return;
    }
    this.getSize(content);
    this.tooltip.html(content)
        .css({
            'width': this.width,
            'height': this.height,
            'visibility': 'visible'
        });
    if (this.hidden) {
        this.hidden = false;
        this.updatePosition();
    }
},

updatePosition: function (x, y) {
    if (x === undefined) {
        if (this.mousex === undefined) {
            return;
        }
        x = this.mousex - this.offsetLeft;
        y = this.mousey - this.offsetTop;

    } else {
        this.mousex = x = x - this.offsetLeft;
        this.mousey = y = y - this.offsetTop;
    }
    if (!this.height || !this.width || this.hidden) {
        return;
    }

    y -= this.height + this.tooltipOffsetY;
    x += this.tooltipOffsetX;

    if (y < this.scrollTop) {
        y = this.scrollTop;
    }
    if (x < this.scrollLeft) {
        x = this.scrollLeft;
    } else if (x + this.width > this.scrollRight) {
        x = this.scrollRight - this.width;
    }

    this.tooltip.css({
        'left': x,
        'top': y
    });
},

remove: function () {
    this.tooltip.remove();
    this.sizetip.remove();
    this.sizetip = this.tooltip = undefined;
    $(window).unbind('resize.jqs scroll.jqs');
}

});