package Yaa::Autoresponse;

use strict;
use Net::SMTP;
use Mail::Address;
use MIME::QuotedPrint;
use Encode qw/encode decode/;
use POSIX qw(strftime);
use Sys::Hostname;
use vars qw($VERSION);

use constant DEFAULT_CHARSET => 'us-ascii';
use constant MESSAGE_MAX_NUM_RECIPS => 100;
use constant HEADER_DATE_FORMAT => '%a, %e %b %Y %T %z';
use constant CRLF => "\r\n";

# set debugging constant if YAA_DEBUG environment variable is set
BEGIN {
	use constant DEBUG => ((defined $ENV{YAA_DEBUG}) ? 1 : 0);
	require File::Basename if (DEBUG);
}


$VERSION = '0.1';

######################################################
#               OBJECT CONSTRUCTOR                   #
######################################################

sub new {
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $self = {};

	##################################################
	#               PUBLIC VARS                      #
	##################################################

	# last error accoured
	$self->{error} = undef;

	##################################################
	#              PRIVATE VARS                      #
	##################################################
	
	# Yaa::Message object
	$self->{msg} = undef;

	bless($self, $class);
	
	$self->clearParams();
	$self->setParams(@_);
	
	return $self;
}



####################################################
#               PUBLIC METHODS                     #
####################################################

sub clearParams {
	my ($self) = @_;
	
	$self->{error} = undef;

	# clear message parameters
	$self->clearMessageParams();

	# mail sending method
	$self->{mail_sending_method} = "smtp";

	# SMTP settings
	$self->{smtp_server} = "localhost";
	$self->{smtp_helo} = hostname();
	$self->{smtp_auth} = 0;
	$self->{smtp_username} = undef;
	$self->{smtp_password} = undef;
	
	# Sendmail binary settings
	$self->{sendmail_path} = "/usr/sbin/sendmail";
	
	return 1;
}

=head2 setParams (key => value, key2 => value2)

Sets object's parameters. Parameters are driver dependent.

=cut
sub setParams {
	my ($self) = shift;

	while ((my $name = shift) && (my $value = shift)) {
		$self->{$name} = $value if (substr($name, 0, 1) ne '_');
	}

	return 1;
}


sub clearMessageParams {
	my ($self) = @_;

	$self->{default_message} = undef;		# Autoresponse message settings
	$self->{default_charset} = DEFAULT_CHARSET;
	$self->{charset} = DEFAULT_CHARSET;
	$self->{msg} = undef;
}

# returns last error accoured
sub error {
	my ($self) = @_;
	return $self->{error};
}

sub setParsedMessage {
	my ($self, $msg) = @_;

	if (! defined $msg) {
		$self->{error} = "Message object was not given.";
		return 0;
	}
	elsif (ref($msg) ne 'Yaa::Message') {
		$self->{error} = "Message must be given as initialized Yaa::Message object.";
		return 0;
	}
	elsif (! $msg->isParsed()) {
		$self->{error} = "Yaa::Message object given, but no message was parsed yet.";
		return 0;
	}
	
	$self->{msg} = $msg;
	return 1;
}

sub sendAutoresponse {
	my ($self, $message, $subject, $sender, $recipient) = @_;

	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", sendAutoresponse(), line ", __LINE__, ": sender '$sender', recipient: '$recipient'\n" if (DEBUG);
	
	if (! (defined $self->{msg} && $self->{msg}->isParsed)) {
		$self->{error} = "Unable to send autoresponse, becouse Yaa::Message object was is not set or object contains unparsed message.";
		return 0;
	}
	if (ref($message) ne 'SCALAR') {
		$self->{error} = "Message must be given as scalar reference.";
		return 0;
	}
	
	# 2007-02-12 Geert Janssens
	#	Reply subject can come from either original mail or configuration settings and in
	#	both cases it can be encoded is some way.
	#	It should be decoded before using it for the autoreply
	my $mycharset;
	my $mysubject;
	if (! length $subject) {
		# Original subject can be MIME encoded.
		# Decode first to perl internal character format
		$mysubject = "Re: " . decode('MIME-Header',$self->{msg}->getHeader("subject", 1));
	} else {
		# Stored subject can be encoded in charset specified by the user in the configuration settings
		# Decode first to perl internal character format
		$mycharset = ($self->{charset}) ? $self->{charset} : $self->{default_charset};
		$mysubject = decode($mycharset,$subject);
	}
	$$message = $self->{default_message} if (! length $$message);
	
	my $tmp;
	my @headers;
	
	# Prepare headers
	# Message ID
	push(@headers, "Message-ID: <" . rand(4000000000) . '@' . hostname() . ">");
	
	# From:
	$tmp = $self->{msg}->getRecipientName($sender);
	# Changed by actra.development 2008/11/21
	# Check first if the sender is a fully qualified address with name prepended, e.g. "John Doe" <john@example.com>
	# If we can't find a name or the address parsing failed use the possibly returned name from $self->{msg}->getRecipientName($sender)
	my @tmpAddr = Mail::Address->parse($sender);
	if(defined $tmpAddr[0]) {
		if(defined $tmpAddr[0]->phrase && length($tmpAddr[0]->phrase)) {
			push(@headers, "From: " . $tmpAddr[0]->format());
		}
		else {
			push(@headers, "From: " . ((defined $tmp) ? '"' . $tmp . '" <' : undef) . $sender . ((defined $tmp) ? '>' : ''));
		}
	}
	else {
		push(@headers, "From: " . ((defined $tmp) ? '"' . $tmp . '" <' : undef) . $sender . ((defined $tmp) ? '>' : ''));
	}
	
	# To:
	$tmp = $self->{msg}->getSenderName();
	push(@headers, "To: " . (($tmp) ? '"' . $tmp . '"' : undef) . " <" . $recipient . ">");
	
	push(@headers, "Reply-To: <>");
	
	# Date:
	push(@headers, "Date: " . strftime(HEADER_DATE_FORMAT, localtime(time())));
	
	# Reply header
	$tmp = $self->{msg}->getHeader('Message-ID', 1);
	if (defined $tmp) {
		push(@headers, "In-Reply-To: " . $tmp);
		# hm, this could be MIME 1.0 non-compilant, becouse this could result in very long head line
		push(@headers, "References: " . $tmp . " " . $self->{msg}->getHeader('References'));
	}

	push(@headers, "Auto-Submitted: auto-replied");
	push(@headers, "Precedence: bulk");
	
	# Subject
	# 2007-02-12 Geert Janssens
	#	Headers should not be encoded with encode_qp from MIME:QuotedPrintable but with encode('MIME-...',$subject)
	#	See perldoc Encode::MIME::Header for more info
	push(@headers, "Subject: " . encode('MIME-Q', $mysubject));
	
	# Content Type
	push(@headers, "MIME-Version: 1.0");
	push(@headers, "Content-Type: text/plain;");
	push(@headers, '        charset="' . $self->{charset} . '"');
	push(@headers, "Content-Transfer-Encoding: Quoted-Printable");
	
	return $self->_sendMessage(\ @headers, $message, $sender, $recipient);
}

sub forwardMessage {
	my ($self, $sender, $recipient) = @_;
	
	unless (defined $recipient) {
		$self->{error} = "Unable to forward message to undefined address.";
		return 0;
	}
	
	return $self->_sendMessage(\ $self->{msg}->getHeadersAsString(), $self->{msg}->getMessageFh(), $sender, $recipient);
}

####################################################
#               PRIVATE METHODS                    #
####################################################

sub _sendMessage {
	my ($self, $headers, $message, $sender, $recipient) = @_;
	my $tmp;
	
	if (! $sender) {
		$self->{error} = "To few arguments. Minimum number of arguments is 5.";
		return 0;
	}
	elsif (($tmp = ref($headers)) ne 'ARRAY' && $tmp ne 'SCALAR') {
		$self->{error} = "Headers must be provided as array or scalar reference.";
		return 0;
	}
	elsif (($tmp = ref($message)) ne 'SCALAR' && substr($tmp, 0, 4) ne 'IO::') {
		$self->{error} = "Message body must be provided as scalar or filehandle reference.";
		return 0;
	}
	
	if ($self->{mail_sending_method} eq 'smtp') {
		return $self->_sendMessageSMTP($headers, $message, $sender, $recipient);
	}
	elsif ($self->{mail_sending_method} eq 'sendmail') {
		return $self->_sendMessageSendmail($headers, $message, $sender, $recipient);
	}
	else {
		$self->{error} = "Unknown mail sending method '" . $self->{mail_sending_method} . "'.";
		return 0;
	}
}

sub _sendMessageSMTP {
	my ($self, $headers, $message, $sender, $recipient) = @_;

	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": connecting to smtp server.\n" if (DEBUG);
	my $smtp = Net::SMTP->new(
		$self->{smtp_server},
		timeout => $self->{smtp_timeout},
		Hello => $self->{smtp_helo},
		Debug => DEBUG
	);

	if (! defined $smtp) {
		$self->{error} = "Unable to connect to SMTP server '" . $self->{smtp_server} . "': " . $!;
		print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": ", $self->{error}, "\n" if (DEBUG);
		return 0;
	}
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": successfully connected.\n" if (DEBUG);

	if (! defined $smtp->domain) {
		$self->{error} = "Invalid greeting received from SMTP server: " . $smtp->message;
		return 0;
	}
		
	# authenticate?
	if ($self->{smtp_auth}) {
		print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": proceeding with smtp authentication.\n" if (DEBUG);
		if (! (defined $self->{smtp_user} && defined $self->{smtp_pass})) {
			$self->{error} = "You must provide username and password in order to use SMTP Auth";
			return 0;
		}
		elsif (! $smtp->auth($self->{smtp_user}, $self->{smtp_pass})) {
			$self->{error} = "SMTP authentication failed";
			return 0;
		}
	}
			
	# we are connected.
	# sender email address (MAIL FROM:)
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": sending MAIL FROM: command.\n" if (DEBUG);
	$smtp->mail($sender);
	if ($smtp->code != 250) {
		$self->{error} = "SMTP server said after MAIL FROM: " . $smtp->message;
		$smtp->quit;
		$smtp = undef;
		return 0;
	}
			
	# send recipients (RCPT TO:)
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": sending RCPT TO: commands.\n" if (DEBUG);
	$smtp->recipient($recipient);
			
	if ($smtp->code != 250) {
		$self->{error} = "SMTP server said after RCPT TO: " . $smtp->message;
		$smtp->quit;
		$smtp = undef;
		return 0;
	}
			
	# send DATA
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": sending DATA command.\n" if (DEBUG);
	$smtp->data();
	if ($smtp->code != 354) {
		$self->{error}="SMTP server said after DATA: " . $smtp->message;
		$smtp->quit;
		$smtp = undef;
		return 0;
	}
			
	# send headers
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": sending message headers.\n" if (DEBUG);
	if (ref($headers) eq 'ARRAY') {
		$smtp->datasend($_ . CRLF) foreach (@{$headers});
	} else {
		$smtp->datasend($$headers);
		$smtp->datasend(CRLF);
	}
	
	# send CRLF
	$smtp->datasend(CRLF);
	
	# send message
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": sending message body.\n" if (DEBUG);
	if (substr(ref($message), 0, 4) eq 'IO::') {
		$smtp->datasend($_) while (<$message>);
	} else {
		$smtp->datasend($$message);
	}
  
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": finishing smtp transaction.\n" if (DEBUG);
	$smtp->dataend();
			
	# end of message, check results
	if ($smtp->code != 250) {
		$self->{error} = "SMTP server said: " . $smtp->message;
		$smtp->quit;
		$smtp = undef;
		return 0;
	}
	
	# disconnect from server
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSMTP(), line ", __LINE__, ": disconnecting from smtp server.\n" if (DEBUG);
	if (! $smtp->quit()) {
		$self->{error} = "Error disconnecting from STMP server: " . $smtp->message;
		return 0;
	}
	
	# uf. it's over.
	return 1;
}


# sends message using sendmail binary
sub _sendMessageSendmail {
	my ($self, $headers, $message, $sender, $recipient) = @_;

	unless (defined $self->{sendmail_path}) {
		$self->{error} = "Error sending message: unable to run sendmail binary: path to executable is not defined.";
		return 0;
	}

	my $args = "| " . $self->{sendmail_path} . " -f '" . $sender . "' '" . $recipient . "'";
	
	my $flush_status = $|;
	$| = 1;

	local *YAA_SENDMAIL_PROC;
	local $SIG{PIPE} = sub {
		print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSendMail(), line ", __LINE__, ": got SIGPIPE.\n" if (DEBUG);
		$self->{error} = "Error sending message: sendmail binary broke message pipe.";
		$self->_logger("Error: " . $self->{error});
		return 0;
	};

	# run process
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSendMail(), line ", __LINE__, ": Running sendmail binary: $args\n" if (DEBUG);
	if (! open(YAA_SENDMAIL_PROC, $args)) {
		$self->{error} = "Error sending message: unable to run sendmail binary " . $self->{sendmail_path} . ": " .$!;
		return 0;
	}

	# print headers
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSendMail(), line ", __LINE__, ": pipeing message headers to sendmail binary.\n" if (DEBUG);
	if (ref($headers) eq 'ARRAY') {
		if (DEBUG) {
			print STDERR $_, CRLF foreach (@{$headers});
		}
		print YAA_SENDMAIL_PROC $_, CRLF foreach (@{$headers});
	} else {
		print STDERR $$headers, "\n" if (DEBUG);
		print YAA_SENDMAIL_PROC $$headers;
	}

	print YAA_SENDMAIL_PROC $_, CRLF;
			
	# print message
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSendmail(), line ", __LINE__, ": pipeing message body to sendmail binary.\n" if (DEBUG);
	if (substr(ref($message), 0, 4) eq 'IO::') {
		while (<$message>) {
			print STDERR $_ if (DEBUG);
			print YAA_SENDMAIL_PROC $_;
		}
		print STDERR "\n" if (DEBUG);
	} else {
		print STDERR $$message, "\n" if (DEBUG);
		print YAA_SENDMAIL_PROC $$message;
	}

	$| = $flush_status;

	# close process
	print STDERR "DEBUG: ",  File::Basename::basename(__FILE__), ", _sendMessageSendMail(), line ", __LINE__, ": closing pipe to sendmail binary.\n" if (DEBUG);
	if (! close(YAA_SENDMAIL_PROC)) {
		$self->{error} = "Error sending message: sendmail command died with exit status: " . $?;
		return 0;
	}

	return 1;
}

1;

=head1 SEE ALSO

perldoc Yaa.

=cut

=head1 AUTHOR

Branko F. Gracnar <bfg@frost.ath.cx>
http://frost.ath.cx/~bfg

=cut

=head1 LICENSE

This software is released under BSD-style license. If you like this software, send me a nice postcard from somewhere to the snail mail address published on my website.

=cut

