# Represents a message sent from one user to one or more others.
# 
# == States
# 
# Messages can be in 1 of 3 states:
# * +unsent+ - The message has not yet been sent.  This is the *initial* state.
# * +queued+ - The message has been queued for future delivery.
# * +sent+ - The message has been sent.
# 
# == Interacting with the message
# 
# In order to perform actions on the message, such as queueing or delivering,
# you should always use the associated event action:
# * +queue!+ - Queues the message so that you can send it in a separate process
# * +deliver!+ - Sends the message to all of the recipients
# 
# == Hiding messages
# 
# Although you can delete a message, it will also delete it from the inbox of all
# the message's recipients.  Instead, you can hide messages from users with the
# following actions:
# * +hide!+ -Hides the message from the sender's inbox
# * +unhide!+ - Makes the message visible again
class Message < ActiveRecord::Base
  belongs_to  :sender, :polymorphic => true
  has_many    :recipients,
              :class_name => 'MessageRecipient',
              :order => 'kind DESC, position ASC',
              :dependent => :destroy
  
  validates_presence_of :state,
                        :sender_id,
                        :sender_type
  
  attr_accessible :subject,
                  :body,
                  :to, :cc, :bcc
  
  after_save :update_recipients
  
  named_scope :visible, :conditions => {:hidden_at => nil}
  
  # Define actions for the message
  state_machine :state, :initial => 'unsent' do
    # Queues the message so that it's sent in a separate process
    event :queue do
      transition :to => 'queued', :from => 'unsent', :if => :has_recipients?
    end
    
    # Sends the message to all of the recipients as long as at least one
    # recipient has been added
    event :deliver do
      transition :to => 'sent', :from => %w(unsent queued), :if => :has_recipients?
    end
  end
  
  # Directly adds the receivers on the message (i.e. they are visible to all recipients)
  def to(*receivers)
    receivers(receivers, 'to')
  end
  alias_method :to=, :to
  
  # Carbon copies the receivers on the message
  def cc(*receivers)
    receivers(receivers, 'cc')
  end
  alias_method :cc=, :cc
  
  # Blind carbon copies the receivers on the message
  def bcc(*receivers)
    receivers(receivers, 'bcc')
  end
  alias_method :bcc=, :bcc
  
  # Forwards this message, including the original subject and body in the new
  # message
  def forward
    message = self.class.new(:subject => subject, :body => body)
    message.sender = sender
    message
  end
  
  # Replies to this message, including the original subject and body in the new
  # message.  Only the original direct receivers are added to the reply.
  def reply
    message = self.class.new(:subject => subject, :body => body)
    message.sender = sender
    message.to(to)
    message
  end
  
  # Replies to all recipients on this message, including the original subject
  # and body in the new message.  All receivers (direct, cc, and bcc) are added
  # to the reply.
  def reply_to_all
    message = reply
    message.cc(cc)
    message.bcc(bcc)
    message
  end
  
  # Hides the message from the sender's inbox
  def hide!
    update_attribute(:hidden_at, Time.now)
  end
  
  # Makes the message visible in the sender's inbox
  def unhide!
    update_attribute(:hidden_at, nil)
  end
  
  # Is this message still hidden from the sender's inbox?
  def hidden?
    hidden_at?
  end
  
  # helper method that calls view! on the message recipient object for a given message
  def view!(recipient)
    valid_recipients = recipients.select{|r| r.receiver == recipient}
    raise Exception, "not a valid recipient of this email" if valid_recipients.empty?
    
    valid_recipients.each{|mr| mr.view!}
    recipient.reload # hate having to do this but I'm not sure how else to accomodate the updates due to caching(?)
  end
  
  private
    # Create/destroy any receivers that were added/removed
    def update_recipients
      if @receivers
        @receivers.each do |kind, receivers|
          kind_recipients = recipients.select {|recipient| recipient.kind == kind}
          new_receivers = receivers - kind_recipients.map(&:receiver)
          removed_recipients = kind_recipients.reject {|recipient| receivers.include?(recipient.receiver)}
          
          recipients.delete(*removed_recipients) if removed_recipients.any?
          new_receivers.each {|receiver| self.recipients.create!(:receiver => receiver, :kind => kind)}
        end
        
        @receivers = nil
      end
    end
    
    # Does this message have any recipients on it?
    def has_recipients?
      (to + cc + bcc).any?
    end
    
    # Creates new receivers or gets the current receivers for the given kind (to, cc, or bcc)
    def receivers(receivers, kind)
      if receivers.any?
        (@receivers ||= {})[kind] = receivers.flatten.compact
      else
        @receivers && @receivers[kind] || recipients.select {|recipient| recipient.kind == kind}.map(&:receiver)
      end
    end
end
