/**
 * Copyright (c) 2009, Nathan Bubna
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Yet another input text hinting plugin, because the others don't have the
 * flexibility, features and ease i want.  Sigh.  This is the kitchen sink
 * version.  By default it does the "labelOver" thing, but you can set it to 
 * {keepLabel:true} if you want to do the more typical value-swapping.  It assumes
 * you have a "hint" css class to make your hints visually distinct.  If you know
 * exactly how you'll be using all your inputs and textareas and how you want to
 * do your hints, then use Remy Sharp's or EZPZ-Hint or something.
 * If you want easy options or custom stuff this one should do you well.
 * Here's the basics:
 *
 *  $.hint() turns all possible hints on, using title attributes as the text.
 *  $.hint('[enter value]') turns all possible hints on, using the specified text instead.
 *  $.hint({on:'blur'}) does the above, using 'blur' instead of 'hint' as the class indicating the hint is on.
 *  $.hint({attr:'title'}) forces the hint text to come from the "title" attribute on the element.
 *  $.hint('[enter value]', {on:'blur', keepLabel:true}) i think you can extrapolate what this does.
 *  $('.hintme').hint() accepts all the same params and works the same as any and all of
 *                      the above, only limited to the selected portion of the document.
 *
 * Pretty much everything involved in doing this is configurable per call,
 * per field and/or per document.  You can override any function, the css used
 * for doing label-over, the selector used to identify hint-able elements, and
 * so on.  You change things globally by setting them like:
 *
 *  $.hint.selector = 'input:text'; //or
 *  $.extend($.hint, { on: 'foo', attr: 'title' });
 *
 * Or locally by passing them in as options (see earlier above).  Yes, that goes
 * for the functions too.
 *
 * TODO? dimension detection to better automate position of labels over fields,
 *       but i don't really need it, so i probably won't get to it myself.
 *
 * @version 1.0
 * @name hint
 * @cat Plugins/Hint
 * @author Nathan Bubna
 */
;(function($) {

    var H = $.hint = function(txt, opts) {
        return $(document).hint(txt, opts);
    },
    toOpts = function(t, o) {
        if (!o && typeof t == "object") {
            o = $.extend({}, H, t);
        } else {
            o = !o ? $.extend({}, H) : $.extend({}, H, o);
            if (t) { o.text = ''+t; }
        }
        return o;
    };

    $.fn.hint = function(txt, opts) {
        H.init.call(this, toOpts(txt, opts));
        return this;
    };

    $.extend(H, {
        // properties
        version: "1.0",
        query: 'input:password,input:text:not(._hintPw),textarea',
        on: 'hint',
        attr: false,
        text: undefined,
        keepLabel: undefined,
        parentCss: { position: 'relative',  float: 'left',  clear: 'left' },
        labelCss:  { position: 'absolute',  top:   '4px',   left:  '5px'  },

        // functions
        hasValue: function(h) {
            var v = this.val();
            return (v && $.trim(v) != '');
        },
        hasHint: function(h) {
            if (h.keepLabel) {
                if (h.password) {
                    return h.password.is(':visible');
                }
                return (this.val() == h.getText.call(this, h))
                       && this.hasClass(h.on);
            }
            return h.label.css('textIndent').charAt(0) == '0';
        },
        start: function() {
            var i = $(this), h = i.data('hint');
            if (!h.hasValue.call(i, h)) {
                h.show.call(i, h);
            }
        },
        end: function() {
            var i = $(this), h = i.data('hint');
            if (h && h.hasHint.call(i, h)) {
                h.hide.call(i, h);
            }
        },
        show: function(h) {
            if (h.keepLabel) {
                if (h.password) {
                    this.hide();
                    h.password.show();
                } else {
                    this.addClass(h.on).val(h.getText.call(this, h));
                }
            } else {
                h.label.css('textIndent', 0);
            }
        },
        hide: function(h) {
            if (h.keepLabel) {
                if (h.password) {
                    h.password.hide();
                    this.show();
                } else {
                    this.val('').removeClass(h.on);
                }
            } else {
                h.label.css('textIndent', -10000);
            }
        },
        getText: function(h) {
            return h.text || this.attr(h.attr || 'title');
        },
        getElements: function(h) {
            var inputs = this.is(h.query) ? this : this.find(h.query);
            return (inputs.length == 0 ? this : inputs);
        },
        init: function(h) {
            h.getElements.call(this, h).each(function() {
                h.setup.call($(this), $.extend({}, h));
            });
        },
        setup: function(h) {
            // undo prior hints
            if (this.data('hint')) {
                this.data('hint').remove.call(this);
            }
            if (h.keepLabel) {
                h.valueSwap.call(this, h);
            } else {
                h.labelOver.call(this, h);
            }
            this.blur(h.start).focus(h.end).data('hint', h);
            h.start.call(this);
        },
        valueSwap: function(h) {
            var self = this, text = h.getText.call(self, h);
            h.kill = function() { h.remove.call(self); };
            if (self.is(':password')) {
                h.password = $('<input type="text" value="'+text+'" class="_hintPw">').click(function() {
                    self.show().focus();
                }).addClass(h.on).insertBefore(self);
            // fix IE value caching on refresh
            } else if ($.browser.msie && !self.attr('defaultValue') && self.val() == text) {
                self.val('');
            }
            $(window).unload(h.kill);
            $(this[0].form).submit(h.kill);
        },
        labelOver: function(h) {
            var self = this, n = this.attr('name'),
                l = $('label[for='+n+']'), p = self.parent();
            if (l.size() == 0) {
                h.newLabel = true;
                l = $('<label for="'+n+'">'+h.getText.call(this, h)+'</label>');
                self.before(l);
            } else if (h.text || h.attr) {
                h.labelText = l.text();
                l.text(h.getText.call(this, h));
            }
            if (!p.is('div') || p.find('input').size() > 1) {
                p = self.wrap('<div></div>').before(l.remove()).parent();
                h.wrap = true;
            } else {
                h.parentStyle = p.attr('style') || '';
            }
            p.css(h.parentCss);
            h.labelStyle = l.attr('style') || '';
            h.label = l.addClass(h.on).css(h.labelCss).click(function() {
                self.focus();
            });
        },
        remove: function() {
            var h = this.data('hint');
            if (h.keepLabel) {
                h.end.call(this);
                if (h.password) {
                    h.password.remove();
                }
                $(window).unbind('unload', h.kill);
                $(this[0].form).unbind('submit', h.kill);
            } else {
                h.label.removeClass(h.on).attr('style', h.labelStyle);
                if (h.newLabel) {
                    h.label.remove();
                    h.label = null;
                } else if (h.labelText) {
                    h.label.text(h.labelText);
                }
                if (h.wrap) {
                    var p = this.parent().after(this);
                    if (h.label) {
                        p.before(h.label);
                    }
                    p.remove();
                } else {
                    this.parent().attr('style', h.parentStyle);
                }
            }
            this.unbind('blur', h.start).unbind('focus', h.end).data('hint', null);
        }
    });

})(jQuery);

