#
# Copyright (C) 2002-2004 Satoru Takabayashi <satoru@namazu.org> 
# Copyright (C) 2003-2005 Kouichirou Eto
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of 
# the GNU General Public License version 2.
#

$LOAD_PATH << "../../lib" unless $LOAD_PATH.include?("../../lib")
require "qwik/ml-gettext"
require "qwik/ml-exception"
require "qwik/ml-quickml"

module QuickML
  class Processor
    include GetText

    def initialize (config, mail)
      @config = config
      @mail = mail
      @logger = @config.logger
      @catalog = @config.catalog
      if mail.multipart?
	sub_mail = Mail.new
	sub_mail.read(mail.parts.first)
	@message_charset = sub_mail.charset
      else
	@message_charset = mail.charset
      end
    end

    def process
      mail_log
      if @mail.looping?
	@logger.log "Looping Mail: from #{@mail.from}"
	return
      end
      @mail.recipients.each {|recipient|
	process_recipient(recipient)
      }
    end

    private

    def mail_log
      @logger.vlog "MAIL FROM:<#{@mail.mail_from}>"
      @mail.recipients.each {|recipient|
	@logger.vlog "RCPT TO:<#{recipient}>"
      }
      @logger.vlog "From: " + @mail.from
      @logger.vlog "Cc: " + @mail.collect_cc.join(", ")
      @logger.vlog "bare From: " + @mail["From"]
      @logger.vlog "bare Cc: " + @mail["Cc"]
    end

    def process_recipient (recipient)
      mladdress = recipient
      if to_return_address?(mladdress)
	handler = ErrorMailHandler.new(@config, @message_charset)
	handler.handle(@mail)
      elsif @config.confirm_ml_creation and 
	  to_confirmation_address?(mladdress)
        validate_confirmation(mladdress)
      else
	begin
	  @config.ml_mutex(mladdress).synchronize {
	    ml = QuickML.new(@config, mladdress, @mail.from, @message_charset)
            @message_charset = (@message_charset or ml.charset)
	    (unsubscribe(ml); return) if unsubscribe_requested?
	    submit(ml)
	  }
	rescue InvalidMLName
	  report_invalid_mladdress(mladdress)
	end
      end
    end

    def to_return_address? (recipient)
      # "return=" for XVERP, "return@" for without XVERP.
      /^[^=]*=return[=@]/ =~ recipient
    end

    def to_confirmation_address? (address)
      /^confirm\+/.match(address)
    end

    def validate_confirmation (confirmation_address)
      m = /^confirm\+(\d+)\+(.*)/.match(confirmation_address)
      time = m[1]
      mladdress = m[2]
      ml = QuickML.new(@config, mladdress)
      if ml.confirmation_waiting? and ml.validate_confirmation(time)
        ml.accept_confirmation
      end
    end

    def unsubscribe_requested?
      @mail.empty_body? || 
        (@mail.body.length < 500 &&
         (/\A\s*(unsubscribe|bye|#\s*bye|quit|‘Þ‰ï|’E‘Þ)\s*$/s).match(@mail.body.tosjis))
    end

    def submit (ml)
      if ml.exclude?(@mail.from)
	@logger.log "Invalid From Address: #{@mail.from}"
      elsif ml.forward? 
	@logger.log "Forward Address: #{ml.address}"
	ml.submit(@mail)
      elsif confirmation_required?(ml)
        ml.prepare_confirmation(@mail)
      elsif acceptable_submission?(ml)
	submit_article(ml)
      else
	report_rejection(ml)
      end
    end

=begin
    def report_invalid_mladdress (mladdress)
      header = []
      subject = Mail.encode_field(_("[QuickML] Error: %s", @mail["Subject"]))
      header.push(["To",	@mail.from],
		  ["From",	@config.postmaster],
		  ["Subject",	subject],
                  ["Content-type", content_type])

      body =   _("Invalid mailing list name: <%s>\n", mladdress)
      body <<  _("You can only use 0-9, a-z, A-Z,  `.',  `-', and `_' for mailing list name\n")
      body << generate_footer
      Mail.send_mail(@config.smtp_host, @config.smtp_port, @logger,
		     :mail_from => "", 
		     :recipient => @mail.from,
		     :header => header,
		     :body => body)
      @logger.log "Invalid ML Address: #{mladdress}"
    end
=end

    #undef report_invalid_mladdress
    def report_invalid_mladdress (mladdress)
      header = []
      subject = Mail.encode_field(_("[QuickML] Error: %s", @mail["Subject"]))
      header.push(["To",	@mail.from],
		  ["From",	@config.postmaster],
		  ["Subject",	subject],
                  ["Content-type", content_type])

      body =   _("Invalid mailing list name: <%s>\n", mladdress)
      body <<  _("You can only use 0-9, a-z, A-Z,  `-' for mailing list name\n")

      body << generate_footer
      Mail.send_mail(@config.smtp_host, @config.smtp_port, @logger,
		     :mail_from => '', 
		     :recipient => @mail.from,
		     :header => header,
		     :body => body)
      @logger.log "Invalid ML Address: #{mladdress}"
    end

    #============================== old

    # FIXME: this is the same method of QuickML#content_type
    def content_type
      if @message_charset
        @config.content_type + "; charset=#{@message_charset}" 
      else
        @config.content_type
      end
    end

    def generate_footer
      "\n--\n" + _("Info: %s\n", @config.info_url)
    end

    def report_rejection (ml)
      header = []
      subject = Mail.encode_field(_("[QuickML] Error: %s", @mail["Subject"]))
      header.push(["To",	@mail.from],
		  ["From",	ml.address],
		  ["Subject",	subject])

      body =  _("You are not a member of the mailing list:\n<%s>\n",
		ml.address)
      body << "\n"
      body <<  _("Did you send a mail with a different address from the address registered in the mailing list?\n")
      body <<  _("Please check your 'From:' address.\n")
      body << generate_footer
      body << "\n"

      body << _("----- Original Message -----\n")
      orig_subject = codeconv(Mail.decode_subject(@mail["Subject"]))
      body << "Subject: #{orig_subject}\n"
      body << "To: #{@mail['To']}\n"
      body << "From: #{@mail['From']}\n"
      body << "Date: #{@mail['Date']}\n"
      body << "\n"
      if @mail.multipart?
        ["Content-Type", "Mime-Version", 
          "Content-Transfer-Encoding"].each {|key|
          header.push([key, @mail[key]]) unless @mail[key].empty?
        }
        sub_mail = Mail.new
        parts = @mail.parts
        sub_mail.read(parts.first)
        body << sub_mail.body
        sub_mail.body = body
        parts[0] = sub_mail.to_s
        body = Mail.join_parts(parts, @mail.boundary)
      else
        unless @mail["Content-type"].empty?
          header.push(["Content-Type", @mail["Content-type"]]) 
        end
        body << @mail.body
      end

      Mail.send_mail(@config.smtp_host, @config.smtp_port, @logger,
		     :mail_from => "", 
		     :recipient => @mail.from,
		     :header => header,
		     :body => body)
      @logger.log "[#{ml.name}]: Reject: #{@mail.from}"
    end

    def report_unsubscription (ml, member, requested_by = nil)
      header = []
      subject = Mail.encode_field(_("[%s] Unsubscribe: %s",
				    ml.short_name, ml.address))
      header.push(["To",	member],
		  ["From",	ml.address],
		  ["Subject",	subject],
                  ["Content-type", content_type])

      if requested_by
	body =  _("You are removed from the mailing list:\n<%s>\n",
		  ml.address)
	body << _("by the request of <%s>.\n", requested_by)
      else
	body = _("You have unsubscribed from the mailing list:\n<%s>.\n", 
		 ml.address)
      end
      body << generate_footer
      Mail.send_mail(@config.smtp_host, @config.smtp_port, @logger,
		     :mail_from => "", 
		     :recipients => member,
		     :header => header,
		     :body => body)
      @logger.log "[#{ml.name}]: Unsubscribe: #{member}"
    end

    def report_too_many_members (ml, unadded_addresses)
      header = []
      subject = Mail.encode_field(_("[QuickML] Error: %s", @mail["Subject"]))
      header.push(["To",	@mail.from],
		  ["From",	ml.address],
		  ["Subject",	subject],
                  ["Content-type", content_type])

      body =  _("The following addresses cannot be added because <%s> mailing list reaches the max number of members (%d persons)\n\n",
		ml.address,
                ml.max_members)
      unadded_addresses.each {|address|
        body << sprintf("<%s>\n", address)
      }

      body << generate_footer
      Mail.send_mail(@config.smtp_host, @config.smtp_port, @logger,
		     :mail_from => "", 
		     :recipient => @mail.from,
		     :header => header,
		     :body => body)
      @logger.log "[#{ml.name}]: Too Many Members: #{address}"
    end

    def sender_knows_an_active_member? (ml)
      @mail.collect_cc.find {|address|
	ml.active_members.include?(address)
      }
    end

    def add_member (ml, address)
      begin
	ml.add_member(address)
      rescue TooManyMembers
        @unadded_addresses.push(address)
      end
    end

    def ml_address_in_to? (ml)
      @mail.collect_to.find {|address|
        address == ml.address
      }
    end

    def submit_article (ml)
      @unadded_addresses = []
      if ml_address_in_to?(ml)
        add_member(ml, @mail.from)
        @mail.collect_cc.each {|address| 
          add_member(ml, address)
        }
      end
      unless @unadded_addresses.empty?
        report_too_many_members(ml, @unadded_addresses)
      end
      ml.submit(@mail)
    end

    def unsubscribe_self (ml)
      if ml.active_members.include?(@mail.from)
	ml.remove_member(@mail.from)
	report_unsubscription(ml, @mail.from)
      else
	report_rejection(ml)
      end
    end

    def unsubscribe_other (ml, cc)
      if ml.active_members.include?(@mail.from)
	cc.each {|other|
	  if ml.active_members.include?(other)
	    ml.remove_member(other) 
	    report_unsubscription(ml, other, @mail.from)
	  end
	}
      else
	@logger.vlog "rejected"
      end
    end

    def unsubscribe (ml)
      cc = @mail.collect_cc
      if cc.empty?
	unsubscribe_self(ml)
      else
	unsubscribe_other(ml, cc)
      end
    end

    def acceptable_submission? (ml)
      ml.newly_created? or
        ml.active_members.include?(@mail.from) or
        ml.former_members.include?(@mail.from) or
        sender_knows_an_active_member?(ml)
    end

    def confirmation_required? (ml)
      @config.confirm_ml_creation and ml.newly_created?
    end

  end
end

if $0 == __FILE__
  require "test/unit"
  require "qwik/test-module-quickml"
  require "qwik/ml-config"
  require "qwik/ml-mail"
  require "qwik/util-kconv"
  $debug = true
end

if $debug
  class TestMLProcessor < Test::Unit::TestCase
    include TestQuickMLModule

    def ml_mail(mail_from, add_recipient, body)
      mail = QuickML::Mail.new
      mail.mail_from = mail_from
      mail.add_recipient(add_recipient)
      mail.read(body.sjistojis)
      return mail
    end

    def assert_file(e, file)
      str = ("./test/"+file).path.open {|f| f.read }
      assert_equal(e, str)
    end

    def assert_config(e)
      file = ",config"
      str = ("./test/"+file).path.open {|f| f.read }
      hash = eval(str)
      assert_equal(e, hash)
    end

    def test_all
      str = <<"EOT"
From: "Test User" <user@e.com>
To: "Test Mailing List" <test@example.com>
Subject: Test Mail
Date: Mon, 3 Feb 2001 12:34:56 +0900

This is a test.
EOT
      mail = ml_mail("user@e.com", "test@example.com", str)
      pro = QuickML::Processor.new(@ml_config, mail)
      pro.process

      assert_file("", ",members")
      assert_file("user@e.com\n", ",waiting-members")
      assert_file(str, ",waiting-message")
      h = {
	:max_members => 100,
	:max_mail_length => 2097152,
	:ml_life_time => 2678400,
	:ml_alert_time => 2073600,
	:auto_unsubscribe_count => 5,
      }
      assert_config(h)

    end
  end
end
