module MongoidExt
  module Voteable
    extend ActiveSupport::Concern

    included do
      field :votes_count, :type => Integer, :default => 0
      field :votes_average, :type => Integer, :default => 0
      field :votes_up, :type => Integer, :default => 0
      field :votes_down, :type => Integer, :default => 0

      field :votes, :type => Hash, :default => {}
    end

    def voted?(voter_id)
      if self[:votes] && !self[:votes].empty?
        self[:votes].include?(voter_id)
      else
        self.class.exists?(:conditions => {:_id => self.id, :"votes.#{voter_id}".exists => true})
      end
    end

    def vote!(value, voter_id, &block)
      old_vote = self.votes[voter_id]
      if !old_vote
        self.votes[voter_id] = value
        self.save(:validate => false)

        add_vote!(value, voter_id, &block)
        return :created
      else
        if(old_vote != value)
          self.votes[voter_id] = value
          self.save
          self.remove_vote!(old_vote, voter_id, &block)
          self.add_vote!(value, voter_id, &block)

          return :updated
        else
          self.votes.delete(voter_id)
          self.save(:validate => false)
          remove_vote!(value, voter_id, &block)
          return :destroyed
        end
      end
    end

    def add_vote!(value, voter_id, &block)
      if embedded?
        updates = {self.atomic_position+".votes_count" => 1,
                   self.atomic_position+".votes_average" => value.to_i}
        if value == 1
          updates[self.atomic_position+".votes_up"] = 1
        elsif value == -1
          updates[self.atomic_position+".votes_down"] = 1
        end

        self._parent.increment(updates)
      else
        updates = {:votes_count => 1, :votes_average => value.to_i}
        if value == 1
          updates[:votes_up] = 1
        elsif value == -1
          updates[:votes_down] = 1
        end

        self.increment(updates)
      end

      block.call(value, :add) if block

      self.on_add_vote(value, voter_id) if self.respond_to?(:on_add_vote)
    end

    def remove_vote!(value, voter_id, &block)
      if embedded?
        updates = {self.atomic_position+".votes_count" => -1,
                   self.atomic_position+".votes_average" => -value.to_i}
        if value == 1
          updates[self.atomic_position+".votes_up"] = -1
        elsif value == -1
          updates[self.atomic_position+".votes_down"] = -1
        end

        self._parent.increment(updates)
      else
        updates = {:votes_count => -1, :votes_average => -value}
        if value == 1
          updates[:votes_up] = -1
        elsif value == -1
          updates[:votes_down] = -1
        end

        self.increment(updates)
      end

      block.call(value, :remove) if block

      self.on_remove_vote(value, voter_id) if self.respond_to?(:on_remove_vote)
    end

    module ClassMethods
    end
  end
end