module Taggable
  def self.included(base) #:nodoc:
    base.extend(ClassMethods)  
  end

  ###################################################################
  # Class-level methods
  ###################################################################

  module ClassMethods
    # Returns all instances (of the base class that included this file) that have the given tag.
    def with_tag(tag)
#      @objects_with_tag ||= {}
      return @objects_with_tag[tag] || []
    end

    # Call this method to add the given object instance to the list of instances for the given tag.
    def add_object_with_tag(object, tag)
#      @objects_with_tag ||= {}

      if @objects_with_tag[tag]
        @objects_with_tag[tag] << object unless @objects_with_tag[tag].include?(object)
      else
        @objects_with_tag[tag] = [object]
        # TODO: notify of added tag
        puts "tag added #{tag}"
      end
    end

    def remove_object_with_tag(object, tag)
#      @objects_with_tag ||= {}

      if @objects_with_tag[tag]
        @objects_with_tag[tag].delete(object)
        if @objects_with_tag[tag].empty?
          @objects_with_tag.delete(tag)
          # TODO: notify of deleted tag
          puts "tag deleted #{tag}"
        end
      end
    end

    def tags
#      @objects_with_tag ||= {}
      return @objects_with_tag.keys
    end
  end

  ###################################################################
  # Object-level methods
  ###################################################################  
  # Adding
  def add_tag(tag)
    unless has_tag?(tag)
      # add to object's list of tags
      @tags ||= {}
      @tags[tag] = true

      # add to class-level list of tags
      self.class.add_object_with_tag(self, tag)
    end
    return self
  end

  def tags=(tags)
    remove_tags
    tags.each { |tag| add_tag(tag) }
  end

  #
  # Querying
  #

  # Returns list of tags on this instance.
  def tags
    tag_keys
  end

  # Returns true if this instance is tagged with the given tag, otherwise false.
  def has_tag?(tag)
    return (@tags and @tags.has_key?(tag))
  end

  # Yields each tag on this instance, one at a time.
  def tag_each
    tag_keys.each { |tag| yield tag }
    return self
  end

  #
  # Removing
  #

  def remove_tags
    @tags.each { |tag| self.class.remove_object_with_tag(self, tag) } if @tags
    @tags = Hash.new
    return self
  end

  def remove_tag(tag)
    @tags.delete(tag) if @tags
    self.class.remove_object_with_tag(self, tag)
    return self
  end

private

  # Utilities
  def tag_keys
    return (@tags || {}).keys
  end
end