module MList
  class MailList < ActiveRecord::Base
    set_table_name 'mlist_mail_lists'
    
    # Provides the MailList for a given implementation of MList::List,
    # connecting it to the provided email server for delivering posts.
    #
    def self.find_or_create_by_list(list, outgoing_server)
      if list.is_a?(ActiveRecord::Base)
        mail_list = find_or_create_by_manager_list_identifier_and_manager_list_type_and_manager_list_id(
          list.list_id, list.class.base_class.name, list.id
        )
      else
        mail_list = find_or_create_by_manager_list_identifier(list.list_id)
        mail_list.manager_list = list
      end
      mail_list.outgoing_server = outgoing_server
      mail_list
    end
    
    belongs_to :manager_list, :polymorphic => true
    
    has_many :messages, :class_name => 'MList::Message', :dependent => :delete_all
    has_many :threads, :class_name => 'MList::Thread', :dependent => :delete_all
    
    delegate :address, :label, :post_url, :to => :list
    
    attr_accessor :outgoing_server
    
    # Creates a new MList::Message and delivers it to the subscribers of this
    # list.
    #
    def post(email_or_attributes)
      email = email_or_attributes
      email = MList::EmailPost.new(email_or_attributes) unless email.is_a?(MList::EmailPost)
      process_message messages.build(
        :parent => email.reply_to_message,
        :parent_identifier => email.parent_identifier,
        :mail_list => self,
        :subscriber => email.subscriber,
        :recipients => list.recipients(email.subscriber),
        :email => MList::Email.new(:tmail => email.to_tmail)
      ), :search_parent => false
    end
    
    # Processes the email received by the MList::Server.
    #
    def process_email(email)
      subscriber = list.subscriber(email.from_address)
      recipients = list.recipients(subscriber)
      process_message messages.build(
        :mail_list => self,
        :subscriber => subscriber,
        :recipients => recipients,
        :email => email
      )
    end
    
    def list
      @list ||= manager_list
    end
    
    def manager_list_with_dual_type=(list)
      if list.is_a?(ActiveRecord::Base)
        self.manager_list_without_dual_type = list
        @list = list
      else
        self.manager_list_without_dual_type = nil
        @list = list
      end
    end
    alias_method_chain :manager_list=, :dual_type
    
    def process?(message)
      !message.recipients.blank?
    end
    
    private
      # http://mail.python.org/pipermail/mailman-developers/2006-April/018718.html
      def bounce_headers
        {'sender'    => "mlist-#{address}",
         'errors-to' => "mlist-#{address}"}
      end
      
      # http://www.jamesshuggins.com/h/web1/list-email-headers.htm
      def list_headers
        headers = list.list_headers
        headers['x-beenthere'] = address
        headers.update(bounce_headers)
        headers.delete_if {|k,v| v.nil?}
      end
      
      def process_message(message, options = {})
        raise MList::DoubleDeliveryError.new(message) unless message.new_record?
        return message unless process?(message)
        
        options = {
          :search_parent => true,
          :delivery_time => Time.now
        }.merge(options)
        transaction do
          thread = find_thread(message, options)
          thread.messages << message
          prepare_delivery(message)
          destinations = message.delivery.destinations
          tmail = message.to_tmail
          self.updated_at = thread.updated_at = options[:delivery_time]
          thread.save! && save!
          outgoing_server.deliver(tmail, destinations)
        end
        message
      end
      
      def prepare_delivery(message)
        delivery = message.delivery
        prepare_list_headers(delivery)
        delivery.subject = list_subject(message)
        delivery.to = address
        delivery.bcc = message.recipients
        delivery.reply_to = "#{label} <#{post_url}>"
      end
      
      def prepare_list_headers(delivery)
        list_headers.each do |k,v|
          if TMail::Mail::ALLOW_MULTIPLE.include?(k.downcase)
            delivery.prepend_header(k,v)
          else
            delivery.write_header(k,v)
          end
        end
      end
      
      def list_subject(message)
        prefix = "[#{label}]"
        subject = message.subject.gsub(%r(#{Regexp.escape(prefix)}\s*), '')
        subject.gsub!(%r{(re:\s*){2,}}i, 'Re: ')
        "#{prefix} #{subject}"
      end
      
      def find_thread(message, options)
        if options[:search_parent]
          message.parent_identifier = message.email.parent_identifier(self)
          message.parent = messages.find_by_identifier(message.parent_identifier)
        end
        message.parent ? message.parent.thread : threads.build
      end
  end
end