module OrbitTag
  module Taggable

    def self.included(base)
      base.extend ClassMethods
      base.class_eval 'taggable'
    end

    module ClassMethods

      def taggable
        class_eval do
          field :tags_to_destroy, type: Array, default: []
          field :tagged_ids, type: Array, default: []

          has_many :taggings, as: :taggable, autosave: true, dependent: :destroy
          accepts_nested_attributes_for :taggings, allow_destroy: true
          after_save :remove_taggings, unless: Proc.new{self.tags_to_destroy.blank?}
        end
        send :include, InstanceMethods
      end
    end

    module InstanceMethods

      def remove_id(id)
        self.class.without_callback(:save, :after, :remove_taggings) do
          self.update_attribute(:tagged_ids, self.tagged_ids - [id.to_s])
        end
      end

      def sorted_tags
        if tags.blank?
          []
        else
          tag_array = tags.inject([]){ |result, value|
            result << [value.name, value]
          }
          tag_array.sort.map{|x| x[1] }
        end
      end

      def tags
        self.taggings.blank? ? [] : self.taggings.map{|t| t.tag}.compact
      end

      def tags=(tag_ids)
        tag_ids = [tag_ids].flatten
        tag_ids.delete_if{|e| e.match(/^[0-1]$|^$/)}
        ids = self.taggings.blank? ? [] : self.taggings.map{|t| t.tag.id}
        tag_ids.each do |tag_id|
          unless ids.include? tag_id
            self.taggings.build(tag_id: tag_id)
          end
        end
        self.tags_to_destroy = self.taggings.where(:tag_id.in => (ids - tag_ids)).map{|t| t.id}
        self.tagged_ids = tag_ids
      end

      def tag_ids
        self.tags.map{|t| t.id}
      end

      def tag_ids=(ids)
        self.tags = ids
      end

      private

      def remove_taggings
        self.taggings.where(:_id.in => self.tags_to_destroy).destroy
        self.class.without_callback(:save, :after, :remove_taggings) do
          self.update_attribute(:tags_to_destroy, [])
        end
      end

    end

  end

end
