/*
jQuery.taggable

jQuery plugin for a clean, fun way to create tag lists.
Released by Greg Leuch <http://gleuch.com>, originally for Magma <http://hotlikemagma.com>.

*/


(function($) {
  $.taggable = function(s, opts) {$(s).taggable(opts);};

  $.fn.taggable = function(opts) {
    if (this.length > 0) {
      this.each(function() {
        var settings = $.extend(true, $.evalJSON($.toJSON($.taggable.settings)), opts);
        // Weird hack, I know, but toJSON/evalJSON convert regexp into objects (WTF, I know, right???)
        settings.characters = (opts && opts.characters ? opts.characters : $.taggable.settings.characters);
        settings.recommendedTags = (opts && opts.recommendedTags ? opts.recommendedTags : $.taggable.settings.recommendedTags);
        settings.container = $(this);
        $.taggable.init(settings);
      });
    }
    return self;
  };

  $.extend($.taggable, {
    settings : {
      debug : false,
      maxTags : 20, /* Integer, or set 0 or false no max */
      minLength : 3,
      maxLength : 20,
      separators : ',', 
      characters : /[A-Z0-9\s\?\.\!\"\-\']/i, 
      sensitive : false, /* Case-sensitive tags? */
      silent : false,
      languages : {
        label : false,
        start : 'Add some tags…',
        another : 'Add another tag…',
        none : 'There are no tags. Add some!',
        maxLength : 'Your tag can be no longer than %d character%s.',
        minLength : 'Your tag must be a minimum of %d character%s.',
        max : 'Sorry, but you can only have up to %d tag%s.',
        recommended : 'Recommended tags:'
      },
      allowInline : true,
      allowSorting : false, /* NOT IMPLEMENTED */
      recommendedTags : [],
      /* do not edit */
      tags : [], container : false, focusedTag : false, errorOpen : false, noticeOpen : false
    },


    init : function($s) {
      var src = $s.container.find('input[rel=taggable]'), t_id = $s.container.attr('id'), b = $s.separators.split('')[0], n = [];

      src.val($.trim(src.val()));
      if (src.val() != '') $s.tags = src.val().replace(new RegExp('['+$s.separators.replace(/\s/g, '\\s')+']', 'ig'), b).replace(new RegExp('(\\s'+b+'|'+b+'\\s)', 'ig'), b).split(b);

      /* Ugly, yes, but best way to manipulate (for now...) */
      $s.container.find('p').each(function() {n.push($(this).html());});

      if ($s.allowInline) {        $.taggable.adjustWidth($s, $s.container.find('.taggable_input'));
        $s.container.html('').addClass('taggable').addClass('taggable_inline').prepend('<input type="hidden" readonly="readonly" rel="taggable" id="'+src.attr('id')+'"" name="'+src.attr('name')+'" value="'+src.val()+'" />').append($s.languages.label && $s.languages.label != '' ? '<label>'+$s.languages.label+'</label>' : '').append('<div id="'+t_id+'_input_hidden_area" class="taggable_input_hidden_area"><span id="'+t_id+'_input_hidden" class="taggable_input_hidden"></span></div><div id="'+t_id+'_input_errors" class="taggable_input_errors"></div><div id="'+t_id+'_input_notices" class="taggable_input_notices"></div><div id="'+t_id+'_input_area" class="taggable_input_area"><div id="'+t_id+'_tags" class="taggable_tags"></div><input type="text" id="'+t_id+'_input" class="taggable_input" name="'+t_id+'_input" value="" /></div>');
        $s.container.find('.taggable_input_area').click(function() {$s.container.find('.taggable_input').get(0).focus();});
      } else {
        $s.container.html('').addClass('taggable').prepend('<input type="hidden" readonly="readonly" rel="taggable" id="'+src.attr('id')+'"" name="'+src.attr('name')+'" value="'+src.val()+'" />').append('<div id="'+t_id+'_input_errors" class="taggable_input_errors"></div><div id="'+t_id+'_input_area" class="taggable_input_area"><input type="text" id="'+t_id+'_input" class="taggable_input" name="'+t_id+'_input" value="" /></div><div id="'+t_id+'_tags" class="taggable_tags"></div>');
      }

      if (n.length > 0) {
        $s.container.find('.taggable_input_area').after('<div id="'+t_id+'_notes" class="taggable_notes"></div>');
        $.each(n, function() {$s.container.find('.taggable_notes').append('<p>'+this+'</p>');});
      }

      $s.container.find('.taggable_input').focus(function() {$.taggable.inputFocus($s, this);}).blur(function() {$.taggable.inputBlur($s, this);}).keypress(function(e) {return $.taggable.sniff($s, this, e);});

      if ($s.tags.length > 0) {
        for (var i=0; i<$s.tags.length; i++) $.taggable.makeTag($s, $s.tags[i]);
      } else {
        $.taggable.suggestAdd($s);
      }

      $.taggable.inputBlur($s);
      
      if (typeof($s.recommendedTags) == 'object' && $s.recommendedTags.length > 0) {
        $s.container.append('<div id="'+t_id+'_recommended_tags" class="taggable_recommended_tags"><strong>'+ $s.languages.recommended +'</strong></div>');
        for (var i=0; i<$s.recommendedTags.length; i++) $.taggable.makeRecommendedTag($s, $s.recommendedTags[i]);
      }

      /* Make sure to get last one.... */
      $s.container.parents('form').submit(function() {
        $s.silent = true;
        if ($s.maxTags > 0 && $s.tags.length >= $s.maxTags) return true;
        var p = $(this).find('.taggable_input'), v = $.trim(p.val());
        if (!p.hasClass('blur') && !p.hasClass('readonly') && v != '') $.taggable.updateTag($s, v);
        return true;
      });
    },

    sniff : function($s, t, e) {
      if ($s.allowInline && (e.which == 8 || e.which == 46 || $s.characters.test(String.fromCharCode(e.which)))) $.taggable.adjustWidth($s, t, e);

      if (e.which == 13 || $.taggable.keysWhich(e.which, $s.separators)) {
        var i = t.selectionStart, val = $(t).val();
        if ($.taggable.updateTag($s, val.substr(0, i))) $(t).val($.trim(val.substr(i)));
      } else if ($s.characters.test(String.fromCharCode(e.which)) 
        || ($.taggable.keysIgnore(e.which) && $s.characters.test(String.fromCharCode(e.which))) 
        || $.taggable.keysAllow(e.which)) {
          return true;
      }
      return false;
    },
    keysIgnore : function(k) {return (k == 0 || (k >= 8 && k <= 46 && k != 13) || (k >= 91 && k <= 93) || (k >= 112 && k <= 123) || (k >= 144 && k <= 145));},
    keysWhich : function(k, b) {var c = b.split(''); for (var i=0; i<c.length; i++) {if (k == c[i].charCodeAt(0) || (b == ',' && k == 188)) return true;} return false;},
    keysAllow : function(k) {return (k == 0 || k == 8 || (k >= 33 && k <= 40));},

    adjustWidth : function($s, t, e) {
      var t = $(t), h = $s.container.find('.taggable_input_hidden'), w=h.html(t.val()+(e && e.which && $s.characters.test(String.fromCharCode(e.which)) ? String.fromCharCode(e.which) : '')).width();
      t.width((w != null && w > 0 ? w : 100)+10);
    },


    makeRecommendedTag : function($s, tag) {
      var n = 'tag_'+tag.replace(/[\s\,\.\!\?\"\']/g, '_'), c = $s.container.find('.taggable_recommended_tags');
      c.append('<a class="taggable_recommended_tag '+ n +'">'+tag+'</a>').find('.'+n).click(function() {
        if ($.inArray(tag, $s.tags) >= 0) {
          $.taggable.destroyTag($s, n);
        } else {
          if ($s.maxTags > 0 && $s.tags.length >= $s.maxTags) {
            $.taggable.inputDisplay($s);
          } else {
            $.taggable.updateTag($s, tag, true);
          }
        }
        return false;
      });
      $.taggable.highlightRecommendedTag($s, tag);
    },
    highlightRecommendedTag : function($s, tag) {
      if ($s.recommendedTags && $s.recommendedTags.length > 0) {
        var n = 'tag_'+tag.replace(/[\s\,\.\!\?\"\']/g, '_'), c = $s.container.find('.taggable_recommended_tags');
        if ($.inArray(tag, $s.tags) >= 0) c.find('.'+n).addClass('added');
      }
    },
    eraseRecommendedTag : function($s, tag) {
      if ($s.recommendedTags && $s.recommendedTags.length > 0) {
        var n = 'tag_'+tag.replace(/[\s\,\.\!\?\"\']/g, '_'), c = $s.container.find('.taggable_recommended_tags');
        if ($.inArray(tag, $s.tags) < 0) c.find('.'+n).removeClass('added');
      }
    },

    makeTag : function($s, tag) {
      var n = 'tag_'+tag.replace(/[\s\,\.\!\?\"\']/g, '_'), c = $s.container.find('.taggable_tags');
      if (c.hasClass('suggest')) c.removeClass('suggest').html('');
      if (c.find('.'+n).size() < 1) {
        c.append('<div class="taggable_tag '+$s.container.attr('id')+'_tag '+n+'"></div>');
        c.find('.'+n).hide().html('<span>'+ tag+'</span> <a href="#" class="remove">X</a>').fadeIn(500).hover(function() {$(this).addClass('hover');}, function() {$(this).removeClass('hover');}).click(function() {return false;}).find('a.remove').click(function() {$.taggable.destroyTag($s, n); return false;});
      }
      $.taggable.highlightRecommendedTag($s, tag);
    },
    updateTag : function($s, tag, q) {
      if (tag.length >= $s.minLength && (!$s.maxLength || ($s.maxLength > $s.minLength && tag.length <= $s.maxLength) || $s.maxLength <= $s.minLength || $s.maxLength == 0)) {
        var r = $.trim($s.sensitive ? tag : tag.toLowerCase()), s = $s.tags;
        if (!$s.sensitive) s = $.map(s, function(a) {return a.toLowerCase();});
        if (r != '' && $.inArray(r, s) < 0) {
          $s.tags.push(tag);
          $s.container.find('input[rel=taggable]').val($s.tags.join($s.separators));
          $.taggable.makeTag($s, tag);
          $.taggable.inputDisplay($s, false, !q);
          $.taggable.errorHide($s);
          return !($s.maxTags > 0 && $s.tags.length >= $s.maxTags);
        }
      } else if ($s.maxLength && $s.maxLength > $s.minLength && tag.length > $s.maxLength) {
        $.taggable.error($s, $.taggable.evalString($s.languages.maxLength, $s.maxLength));
        return false;
      } else {
        $.taggable.error($s, $.taggable.evalString($s.languages.minLength, $s.minLength));
        return false;
      }
    },
    destroyTag : function($s, n) {
      var t = $s.container.find('.taggable_tags .'+n+' span').text(), p = $.inArray(t, $s.tags);
      $s.tags.splice(p,1);
      $s.container.find('input[rel=taggable]').val($s.tags.join($s.separators));
      $s.container.find('.taggable_tags .'+n).animate({'opacity':'0'}, 250, function() {
        $(this).remove();
        $.taggable.eraseRecommendedTag($s, t);
        if ($s.tags.length < 1) $.taggable.suggestAdd($s);
        $.taggable.inputDisplay($s);
      });
    },

    error : function($s, str) {
      var e = $s.container.find('.taggable_input_errors');
      if ($s.errorOpen) {
        e.fadeOut(250, function() {$(this).html(str).fadeIn(250);});
      } else {
        $s.errorOpen = true;
        e.hide().html(str).animate({'height':'show','opacity':'show'}, 250);
      }
    },
    errorHide : function($s) {$s.errorOpen = false; $s.container.find('.taggable_input_errors').animate({'height':'hide','opacity':'hide'}, 250);},

    notice : function($s, str) {
      var e = $s.container.find('.taggable_input_notices');
      if ($s.noticeOpen) {
        e.html(str).fadeOut(250, function() {e.fadeIn(250);});
      } else {
        $s.noticeOpen = true;
        e.hide().html(str).animate({'height':'show','opacity':'show'}, 250);
      }
    },
    noticeHide : function($s) {$s.noticeOpen = false; $s.container.find('.taggable_input_notices').animate({'height':'hide','opacity':'hide'}, 250);},


    evalString : function(str, i) {
      /* Allow singluar/plural on funny words (set at %s[singular|plural]) */
      if (/(\%s\[)([a-z]+)(\|)([a-z]+)(\])/i.test(str)) {
        var t = str.replace(/(.*)(\%s\[)([a-z]+)(\|)([a-z]+)(\])(.*)/i, '$3|$5').split('|');
        str = str.replace(/(\%s\[)([a-z]+)(\|)([a-z]+)(\])/i, (i != 1 ? t[1] : t[0]));
      }
      return str.replace(/\%d/, i).replace(/\%s/, (i > 1 ? 's' : ''));
    },

    inputDisplay : function($s, i, r) {
      if (!$s.silent) {
        i = i || $s.container.find('input.taggable_input');
        if ($s.maxTags > 0 && $s.tags.length >= $s.maxTags) {
          var str = $.taggable.evalString($s.languages.max, $s.maxTags);
          $(i).attr('readonly', 'readonly').addClass('readonly').addClass('blur').val($s.allowInline ? '' : str);
          if ($s.allowInline) {
            $.taggable.adjustWidth($s, i);
            $.taggable.notice($s, str);
          }
        } else if (!r) {
          $(i).removeAttr('readonly').removeClass('readonly').addClass('blur').val($s.tags.length > 0 ? ($s.allowInline ? '' : $s.languages.another) : $s.languages.start);
          if ($s.allowInline) {
            $.taggable.adjustWidth($s, i);
            $.taggable.noticeHide($s);
          }
        }
      }
    },
    inputFocus : function($s, i) {
      if (!$s.silent) {
        i = i || false;
        if (i && $(i).hasClass('blur') && !$(i).hasClass('readonly')) $(i).removeClass('blur').val('');
      }
    },
    inputBlur : function($s, i) {
      if (!$s.silent) {
        i = i || false;
        if ((i && $(i).val() == '') || !i) $.taggable.inputDisplay($s, i);
      }
    },
    suggestAdd : function($s) {
      if ($s.languages.none != '') $s.container.find('.taggable_tags').addClass('suggest').html($s.languages.none);
    }
  });
})(jQuery);