/**
 * Generic Javascript helpers for CWIS.
 *
 * Part of the Collection Workflow Integration System (CWIS)
 * Copyright 2011 Internet Scout Project
 * http://scout.wisc.edu
 */

cw.provide("CW-Helpers", function(){

if (typeof jQuery == "undefined") {
    cw.error("jQuery is required for the Helpers module");
}

/**
 * Construct a popup.
 * @param node:reference jQuery object reference
 */
function Popup(node) {
    this.Node = node;
}

/**
 * Show the popup.
 */
Popup.prototype.show = function() {
    var offset;

    if (!this.isVisible()) {
        offset = this.Node.offset();

        this.Node.offset({"top": offset.top, "left": this.Left});
    }
};

/**
 * Hide the popup.
 */
Popup.prototype.hide = function() {
    var offset, left;

    if (this.isVisible()) {
        offset = this.Node.offset();
        left = -(this.Node.outerWidth(true) + 500);

        this.Left = offset.left;
        this.Node.offset({"top": offset.top, "left": left});
    }
};

/**
 * @protected
 * Determine whether the popup is visible or not.
 * @return true if the popup is visible, false otherwise
 */
Popup.prototype.isVisible = function() {
    var offset = this.Node.offset(),
        width = this.Node.outerWidth(true);

    return (offset.left + width > 0);
};

/**
 * @protected Node:reference jQuery object
 * @protected Left:number cached left offset
 */
Popup.prototype.Node = null;
Popup.prototype.Left = 0;

/**
 * Construct a popup that is anchored to an element.
 * @param node:reference jQuery object reference
 */
function AnchoredPopup(node) {
    AnchoredPopup.base.call(this, node);
} cw.extend(AnchoredPopup, Popup);

/**
 * Anchor the popup to the given element. Options are:
 * anchorSide: which side of the anchor the popup should be displayed
 * anchorHandle: where, in pixels or percent, should the anchor handle be
 * popupHandle: where, in pixels or perecent, should the popup handle be
 * distance: how many pixels away the popup should be from the anchor
 * @param anchor:reference jQuery object reference
 * @param options:object options to specify the position and length of the tether
 */
AnchoredPopup.prototype.anchor = function(anchor, options) {
    var popupTop, popupLeft;
    var anchorOffset = anchor.offset(),
        anchorWidth = anchor.outerWidth(),
        anchorHeight = anchor.outerHeight(),
        popupWidth = this.Node.outerWidth(),
        popupHeight = this.Node.outerHeight();

    // defaults
    var anchorSide = this.getValue(options, "anchorSide", "top"),
        anchorHandle = this.getValue(options, "anchorHandle", "50%"),
        popupHandle = this.getValue(options, "popupHandle", "50%"),
        distance = this.getValue(options, "distance", 10);

    if (anchorSide == "left" || anchorSide == "right") {
        // transform percent values to pixels if percent given
        anchorHandle = this.transformPercent(anchorHandle, anchorHeight);
        popupHandle = this.transformPercent(popupHandle, popupHeight);

        // get the anchor handle into the correct range if it isn't
        anchorHandle = Math.max(anchorHandle, 0);
        anchorHandle = Math.min(anchorHandle, anchorHeight);

        // get the popup handle into the correct range if it isn't
        popupHandle = Math.max(popupHandle, 0);
        popupHandle = Math.min(popupHandle, popupHeight);

        // compute the new offset for the popup
        popupTop = anchorOffset.top + anchorHandle - popupHandle;
        popupLeft = anchorOffset.left;

        // offset the left coordinate based on position relative to anchor
        if (anchorSide == "right") {
            popupLeft += anchorWidth + distance;
        } else {
            popupLeft -= popupWidth + distance;
        }
    } else {
        // transform percent values to pixels if percent given
        anchorHandle = this.transformPercent(anchorHandle, anchorWidth);
        popupHandle = this.transformPercent(popupHandle, popupWidth);

        // get the anchor handle into the correct range if it isn't
        anchorHandle = Math.max(anchorHandle, 0);
        anchorHandle = Math.min(anchorHandle, anchorWidth);

        // get the popup handle into the correct range if it isn't
        popupHandle = Math.max(popupHandle, 0);
        popupHandle = Math.min(popupHandle, popupWidth);

        // compute the new offset for the popup
        popupTop = anchorOffset.top;
        popupLeft = anchorOffset.left + anchorHandle - popupHandle;

        // offset the top coordinate based on position relative to anchor
        if (anchorSide == "bottom") {
            popupTop += anchorHeight + distance;
        } else {
            popupTop -= popupHeight + distance;
        }
    }

    // finally position the popup
    this.Node.offset({"top": popupTop, "left": popupLeft});
};

/**
 * @protected
 * Get the value from the object at key or defaultValue if it doesn't exist.
 * @param object:object object
 * @param key:mixed object key
 * @param defaultValue:mixed default value if the value isn't set
 * @return the value if it's set or defaultValue if it's not
 */
AnchoredPopup.prototype.getValue = function(object, key, defaultValue) {
    return object && typeof object[key] != "undefined" ? object[key] : defaultValue;
};

/**
 * @protected
 * Get the percent of the given value if the percent parameter is a percentage.
 * Otherwise return it unmodified.
 * @param percent:mixed a percentage or other value
 * @param value:number number to calculate percentage of
 * @return percentage of value if a percent, otherwise unmodified percent param
 */
AnchoredPopup.prototype.transformPercent = function(percent, value) {
    if ("string" == typeof percent && percent.match(/%$/)) {
        return Math.round(parseInt(percent, 10) * value / 100);
    }

    return percent;
};

/**
 * Construct a popup for tooltips.
 * @param node:reference jQuery object reference
 */
function Tooltip(node) {
    var tooltip = this;

    AnchoredPopup.base.call(this, node);

    // hide the tooltip the instant the mouse button is touched
    this.Node.mousedown(function(){
        tooltip.hide(true);
    });
} cw.extend(Tooltip, AnchoredPopup);

/**
 * Anchor the tooltip to the given element, but not until shown. Options are:
 * anchorSide: which side of the anchor the popup should be displayed
 * anchorHandle: where, in pixels or percent, should the anchor handle be
 * popupHandle: where, in pixels or perecent, should the popup handle be
 * distance: how many pixels away the popup should be from the anchor
 * showOnClick: true to show the tooltip when the anchor is clicked
 * showOnHover: true to show the tooltip when the anchor is hovered over
 * @param anchor:reference jQuery object reference
 * @param options:object options to specify the position and length of the tether
 */
Tooltip.prototype.anchor = function(anchor, options) {
    var tooltip = this;

    options = $.extend({
        "anchorSide": "right",
        "anchorHandle": "100%",
        "popupHandle": "0%",
        "showOnClick": true,
        "showOnHover": true}, options);

    if (options.showOnClick) {
        anchor.click(function(){
            clearTimeout(tooltip.Timeout);
            tooltip.anchorAux($(this), options);
        });
    } else {
        anchor.click(function(){
            clearTimeout(tooltip.Timeout);
            tooltip.hide(true);
        });
    }

    if (options.showOnHover) {
        anchor.mouseover(function(){
            var $this = $(this),
                delay = tooltip.isVisible() ? 375 : 1500;

            clearTimeout(tooltip.Timeout);

            // remove the title attribute to disable browser-based tooltips
            tooltip.removeTitle($this);

            tooltip.Timeout = setTimeout(function(){
                tooltip.anchorAux($this, options);
            }, delay);
        });

        anchor.mouseout(function(){
            clearTimeout(tooltip.Timeout);

            // restore the title attribute to the element
            tooltip.restoreTitle($(this));

            tooltip.Timeout = setTimeout(function(){
                tooltip.hide();
            }, 200);
        });
    }
};

/**
 * Show the tooltip.
 */
Tooltip.prototype.show = function() {
    // stop any animations that might interfere
    this.Node.stop();

    // show the tooltip
    this.Node.css({"opacity": "1"});
    Tooltip.base.prototype.show.call(this);
};

/**
 * Hide the tooltip.
 */
Tooltip.prototype.hide = function(immediate) {
    var tooltip = this;

    // stop any animations that might interfere
    this.Node.stop();

    if (immediate) {
        // hide immediately
        this.Node.css({"opacity": "0"});
        Tooltip.base.prototype.hide.call(tooltip);
    } else {
        // hide with an animation
        this.Node.animate({"opacity": "0"}, 650, function(){
            Tooltip.base.prototype.hide.call(tooltip);
        });
    }
};

/**
 * @protected
 * Since the anchor function sets two event handlers that need to accomplish
 * essentially the same thing, use this auxiliary function to perform that "same
 * thing". Options are:
 * anchorSide: which side of the anchor the popup should be displayed
 * anchorHandle: where, in pixels or percent, should the anchor handle be
 * popupHandle: where, in pixels or perecent, should the popup handle be
 * distance: how many pixels away the popup should be from the anchor
 * @param anchor:reference jQuery object reference
 * @param options:object options to specify the position and length of the tether
 */
Tooltip.prototype.anchorAux = function(anchor, options) {
    var text = anchor.attr("tooltip");

    // convert new lines to breaks, per the HTML5 spec
    text = text.replace(/\u000a/g, "<br />");

    // update the tooltip text
    this.Node.html(text);

    // anchor the tooltip to the anchor
    Tooltip.base.prototype.anchor.call(this, anchor, options);

    // show the tooltip
    this.show();
};

/**
 * Remove the title attribute from the anchor and cache it.
 * @param anchor:reference jQuery object reference
 */
Tooltip.prototype.removeTitle = function(anchor) {
    this.TitleCache = anchor.attr("title");
    anchor.removeAttr("title");
};

/**
 * Restore the title attribute of the anchor and clear the cache.
 * @param anchor:reference jQuery object reference
 */
Tooltip.prototype.restoreTitle = function(anchor) {
    anchor.attr("title", this.TitleCache);
    this.TitleCache = null;
};

/**
 * @protected Timeout:reference holds object returned by setTimeout
 * @protected TitleCache:string caches the title attribute of the anchor
 */
Tooltip.prototype.Timeout = null;
Tooltip.prototype.TitleCache = null;

/**
 * Construct a remote data store.
 */
function RemoteDataStore() {
    this.Cache = {};
}

/**
 * Get data from the data store.
 * @param id:mixed data id
 * @param callback:function function called when the data is available
 */
RemoteDataStore.prototype.get = function(id, callback) {
    if (this.Cache[id]) {
        callback(this.Cache[id]);
    } else {
        this.fetch(id, callback);
    }
};

/**
 * Fetch data remotely.
 * @param id:mixed data id
 * @param callback:function function called when the data is available
 */
RemoteDataStore.prototype.fetch = function(id, callback) {
    return undefined;
};

/**
 * @protected Cache:object data cache
 */
RemoteDataStore.prototype.Cache = null;

// exports
this.Popup = Popup;
this.AnchoredPopup = AnchoredPopup;
this.Tooltip = Tooltip;
this.RemoteDataStore = RemoteDataStore;

});
