package Parp::Mail::Friends;

=head1 NAME

Parp::Mail::Friends - update friends database according to headers

=head1 SYNOPSIS

Internal use; see L<Parp::Mail>.

=head1 DESCRIPTION

This class provides methods for Parp::Mail objects so that the
friends database can have entries added to or removed from it,
depending on a mail's headers and whether it was classified as
spam or not.

=cut

use strict;
use warnings;

use Parp::Config qw(config);
use Parp::Friends qw(make_friend break_friend is_friend);
use Parp::Options qw(opt);
use Parp::Utils   qw(diagnose vprint);

use Mail::Address;

sub update_friends_db {
  my $m = shift;

  if ($m->{filter_category} eq 'IS_SPAM') {
    $m->break_friends;
  }
  elsif ($m->{filter_category} ne 'IS_SPAM') {
    $m->make_friends;
  }
}

sub make_friends {
  my $m = shift;
  my ($folder) = @_;

  my @new_friends = $m->extract_correspondents;
  return $m->_add_friends($folder, @new_friends);
}

sub break_friends {
  my $m = shift;
  my ($folder) = @_;

  my @new_ex_friends = $m->extract_correspondents;
  return $m->_remove_friends(@new_ex_friends);
}

sub extract_correspondents {
  my $m = shift;
  # Count which addresses in which headers are mine.
  my %addrs = $m->_classify_addresses;

  my @correspondents = ();

  if ($addrs{from}{me}  == 1 &&
      $addrs{to}{total} == 1 &&
      $addrs{cc}{total} == 0)
  {
    push @correspondents, { addr => $addrs{to}{addrs}[0], header => 'to' };
  }
  elsif ( # $addrs{to}{me} + $addrs{cc}{me} >= 1 && # could be on a list
         $addrs{from}{not_me} == 1)
  {
    push @correspondents, { addr => $addrs{from}{addrs}[0],
                            header => 'From' };
    push @correspondents, { addr => $addrs{reply_to}{addrs}[0],
                            header => 'Reply-To' }
      if $addrs{reply_to}{total} == 1;
  }

  return @correspondents;
}

sub _classify_addresses {
  my $m = shift;
  
  my %addrs = (
#   env_from_addr => { descr => 'envelope From' },
    from          => { descr => 'From'          },
    to            => { descr => 'to'            },
    cc            => { descr => 'cc'            },
    reply_to      => { descr => 'Reply-To'      },
#   return_path   => { descr => 'Return-Path'   },
  );

  foreach my $addr_type (keys %addrs) {
    $addrs{$addr_type}{$_} ||= 0 foreach qw/me not_me total/;
    next unless $m->$addr_type;
    my @addrs = Mail::Address->parse($m->$addr_type);
    foreach my $parsed (@addrs) {
      my $paddr = $parsed->address;
      if ($paddr =~ config->me || $paddr =~ config->old_me) {
        $addrs{$addr_type}{me}++;
      }
      else {
        $addrs{$addr_type}{not_me}++;
      }
      $addrs{$addr_type}{total}++;
      push @{$addrs{$addr_type}{addrs}}, $paddr;
    }
  }

#   for my $type (qw/from to cc reply_to/) {
#     vprint "type $type:\t";
#     for my $count (qw/me not_me total/) {
#       vprint "[$count $addrs{$type}{$count}] ";
#     }
#     vprint "\n";
#   }

  return %addrs;
}

sub _add_friends {
  my $m = shift;
  my ($folder_name, @new_friends) = @_;

  my $added = 0;
  foreach my $new_friend (@new_friends) {
    my $addr = $new_friend->{addr};
    my $hdr  = $new_friend->{header};
    if (my $reason = is_friend($addr)) {
#     vprint "$addr is already a friend ($reason)\n";
      next;
    }
    my $will_add = opt('extract_friends') || ! opt('test_run');
    vprint $will_add ? "Adding" : "Would add",
           " `$addr' to friends database from $hdr header.\n";
    return unless $will_add;
    my $source = "friend extracted from `$hdr' " .
                 "header of message";
    if ($m->parp_id) {
      $source .= " parp id " . $m->parp_id;
    }
    elsif ($m->id) {
      $source .= " id `" . $m->id . "'";
    }
    elsif ($m->date) {
      $source .= " dated " . $m->date;
    }
    $source .= " in `$folder_name' folder" if $folder_name;
    make_friend($addr, $source);
    $added++;
  }
  
  return $added ? q[EXTRACTED_FRIEND] : q[DIDN'T_EXTRACT_FRIEND];
}

sub _remove_friends {
  my $m = shift;
  my (@new_ex_friends) = @_;

  my $added = 0;
  foreach my $new_ex_friend (@new_ex_friends) {
    my $addr = $new_ex_friend->{addr};
    next unless is_friend($addr);
    if (opt('test_run')) {
      vprint "Would remove `$addr' from friends database.\n";
    }
    else {
      vprint "Removing `$addr' from friends database.\n";
      break_friend($addr);
    }
  }
}

1;
