/*
 * An URI datatype.  Based upon examples in RFC3986.
 *
 * TODO %-escaping
 * TODO split apart authority
 * TODO split apart query_string (on demand, anyway)
 *
 * @(#) $Id$
 */
 
// Constructor for the URI object.  Parse a string into its components.
function URI(str) {
    if (!str) str = "";
    // Based on the regex in RFC2396 Appendix B.
    var parser = /^(?:([^:\/?\#]+):)?(?:\/\/([^\/?\#]*))?([^?\#]*)(?:\?([^\#]*))?(?:\#(.*))?/;
    var result = str.match(parser);
    this.scheme    = result[1] || null;
    this.authority = result[2] || null;
    this.path      = result[3] || null;
    this.query     = result[4] || null;
    this.fragment  = result[5] || null;
}

// Restore the URI to it's stringy glory.
URI.prototype.toString = function () {
    var str = "";
    if (this.scheme) {
        str += this.scheme + ":";
    }
    if (this.authority) {
        str += "//" + this.authority;
    }
    if (this.path) {
        str += this.path;
    }
    if (this.query) {
        str += "?" + this.query;
    }
    if (this.fragment) {
        str += "#" + this.fragment;
    }
    return str;
};

// Introduce a new scope to define some private helper functions.
(function () {
    // RFC3986 §5.2.3 (Merge Paths)
    function merge(base, rel_path) {
        var dirname = /^(.*)\//;
        if (base.authority && !base.path) {
            return "/" + rel_path;
        }
        else {
            return base.path.match(dirname)[0] + rel_path;
        }
    }
    // Match two path segments, where the second is ".." and the first must
    // not be "..".
    var DoubleDot = /\/((?!\.\.\/)[^\/]*)\/\.\.\//;
    function remove_dot_segments(path) {
        if (!path) return "";
        // Remove any single dots
        var newpath = path.replace(/\/\.\//g, '/');
        // Remove any trailing single dots.
        newpath = newpath.replace(/\/\.$/, '/');
        // Remove any double dots and the path previous.  NB: We can't use
        // the "g", modifier because we are changing the string that we're
        // matching over.
        while (newpath.match(DoubleDot)) {
            newpath = newpath.replace(DoubleDot, '/');
        }
        // Remove any trailing double dots.
        newpath = newpath.replace(/\/([^\/]*)\/\.\.$/, '/');
        // If there are any remaining double dot bits, then they're wrong
        // and must be nuked.  Again, we can't use the g modifier.
        while (newpath.match(/\/\.\.\//)) {
            newpath = newpath.replace(/\/\.\.\//, '/');
        }
        return newpath;
    }
    // RFC3986 §5.2.2. Transform References;
    URI.prototype.resolve = function (base) {
        var target = new URI();
        if (this.scheme) {
            target.scheme    = this.scheme;
            target.authority = this.authority;
            target.path      = remove_dot_segments(this.path);
            target.query     = this.query;
        }else {
            if (this.authority) {
                target.authority = this.authority;
                target.path      = remove_dot_segments(this.path);
                target.query     = this.query;
            } else {
                // XXX Original spec says "if defined and empty"…;
                if (!this.path) {
                    target.path = base.path;
                    if (this.query) {
                        target.query = this.query;
                    }
                    else {
                        target.query = base.query;
                    }
                } else {
                    if (this.path.charAt(0) === '/') {
                        target.path = remove_dot_segments(this.path);
                    } else {
                        target.path = merge(base, this.path);
                        target.path = remove_dot_segments(target.path);
                    }
                    target.query = this.query;
                }
                target.authority = base.authority;
            }
            target.scheme = base.scheme;
        }
        target.fragment = this.fragment;
        return target;
    };
})();

/*
 * Originally taken from http://tutorialzine.com/2010/07/colortips-jquery-tooltip-plugin/
 * Implemented for QR codes by Konstantin Kovshenin http://kovshenin.com
 */

(function($){
    $.fn.colorTip = function(settings){
        var defaultSettings = {
            color		: 'white',
            timeout		: 200,
            size		: 100
        }
        var supportedColors = ['red','green','blue','white','yellow','black'];
        settings = $.extend(defaultSettings,settings);
        /*
	 *	Looping through all the elements and returning them afterwards.
	 *	This will add chainability to the plugin.
	 */
        return this.each(function(){
            var elem = $(this);
            // If the title attribute is empty, continue with the next element
            // if(!elem.attr('title')) return true;
            // Creating new eventScheduler and Tip objects for this element.
            // (See the class definition at the bottom).
            var scheduleEvent = new eventScheduler();
            url = elem.attr("href");
            this_uri = new URI(window.location.href);
            uri = new URI(url);
            url = uri.resolve(this_uri);
            content = "<img src='http://chart.apis.google.com/chart?cht=qr&chs=" + settings.size + "x" + settings.size + "&choe=UTF-8&chld=L%7C0&chl=" + url + "' />";
            var tip = new Tip(content);
            // Adding the tooltip markup to the element and
            // applying a special class:
            elem.append(tip.generate()).addClass('colorTipContainer');
            // Checking to see whether a supported color has been
            // set as a classname on the element.
            var hasClass = false;
            for(var i=0;i<supportedColors.length;i++){
                if(elem.hasClass(supportedColors[i])){
                    hasClass = true;
                    break;
                }
            }
            // If it has been set, it will override the default color
            if(!hasClass){
                elem.addClass(settings.color);
            }
            // On mouseenter, show the tip, on mouseleave set the
            // tip to be hidden in half a second.
            elem.hover(function(){
                tip.show(settings.size);
                // If the user moves away and hovers over the tip again,
                // clear the previously set event:
                scheduleEvent.clear();
            },function(){
                // Schedule event actualy sets a timeout (as you can
                // see from the class definition below).
                scheduleEvent.set(function(){
                    tip.hide();
                },settings.timeout);
            });
            // Removing the title attribute, so the regular OS titles are
            // not shown along with the tooltips.
            elem.removeAttr('title');
        });
    }


    /*
     *	Event Scheduler Class Definition
     */
    function eventScheduler(){}
        eventScheduler.prototype = {
            set	: function (func,timeout){
            // The set method takes a function and a time period (ms) as
            // parameters, and sets a timeout
            this.timer = setTimeout(func,timeout);
        },
        clear: function(){
            // The clear method clears the timeout
            clearTimeout(this.timer);
        }
    }

    /*
     *	Tip Class Definition
     */
    function Tip(txt){
        this.content = txt;
        this.shown = false;
    }	
    Tip.prototype = {
        generate: function(){
            // The generate method returns either a previously generated element
            // stored in the tip variable, or generates it and saves it in tip for
            // later use, after which returns it.
            return this.tip || (this.tip = $('<span class="colorTip">'+this.content+
                '<span class="pointyTipShadow"></span><span class="pointyTip"></span></span>'));
        },
        show: function(size){
            if(this.shown) return;
            // Center the tip and start a fadeIn animation
            this.tip.css('margin-top', -size);
            this.tip.css('margin-left',-this.tip.outerWidth()/2).fadeIn('fast');
            this.tip.css('z-index',10000);
            this.shown = true;
        },
        hide: function(){
            this.tip.fadeOut();
            this.shown = false;
        }
    }
    $.fn.qr = function(settings) {
        var defaultSettings = {
            size: 100
        }
        settings = $.extend(defaultSettings,settings);
        jQuery(this).colorTip(settings);
    };
})(jQuery);

