/**
 * 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) {
    AnchoredPopup.base.call(this, node);
} 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
 * @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%"}, options);

    anchor.click(function(){
        var $this = $(this),
            title = $this.attr("title");

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

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

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

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

    anchor.mouseout(function(){
        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"});
};

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

    this.Node.animate({"opacity": "0"}, 750, function(){
        Tooltip.base.prototype.hide.call(popup);
    });
};

/**
 * 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;

});

