# <@LICENSE>
# Copyright 2004 Apache Software Foundation
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>

package Mail::SpamAssassin::Plugin::P595Body;

use Mail::SpamAssassin::Plugin;
use Mail::SpamAssassin::Logger;
use Mail::SpamAssassin::Plugin::BodyRuleBaseExtractor;
use Mail::SpamAssassin::Plugin::OneLineBodyRuleType;

use strict;
use warnings;
use bytes;

use vars qw(@ISA);
@ISA = qw(Mail::SpamAssassin::Plugin);

sub new {
  my $class = shift;
  my $mailsaobject = shift;
  $class = ref($class) || $class;
  my $self = $class->SUPER::new($mailsaobject);
  bless ($self, $class);

  $self->{one_line_body} = Mail::SpamAssassin::Plugin::OneLineBodyRuleType->new();

  if ($] < 5.009005) {
    die "this plugin requires perl 5.9.5 or later";
  }

  return $self;
}

###########################################################################

sub finish_parsing_end {
  my ($self, $params) = @_;
  my $conf = $params->{conf};

  my $main = $self->{main};

  $conf->{skip_body_rules}   ||= { };
  $self->setup_test_set ($conf, $conf->{body_tests}, 'body');
}

sub setup_test_set {
  my ($self, $conf, $test_set, $ruletype) = @_;
  foreach my $pri (keys %{$test_set}) {
    my $nicepri = $pri; $nicepri =~ s/-/neg/g;
    $self->setup_test_set_pri($conf, $test_set->{$pri},
                    $ruletype.'_'.$nicepri, $pri);
  }
}

sub setup_test_set_pri {
  my ($self, $conf, $rules, $ruletype, $pri) = @_;

  my $alternates = [];
  while (my ($rule, $pat) = each %{$conf->{body_tests}->{$pri}}) {
    $pat = Mail::SpamAssassin::Util::regexp_remove_delimiters($pat);

    # ignore rules marked for ReplaceTags work!
    next if ($conf->{rules_to_replace}->{$rule});

    # use the REGMARK feature:
    # see http://taint.org/2006/11/16/154546a.html#comment-1011
    #
    push @{$alternates}, "$pat(*:$rule)";

    # TODO: need a cleaner way to do this.  I expect when rule types
    # are implementable in plugins, I can do it that way
    $conf->{skip_body_rules}->{$rule} = 1;
  }

  my $sub = '
    sub {
        our $REGMARK;
        our @matched = ();
        $_[0] =~ m#('.join('|', @{$alternates}).')(?{
            push @matched, $REGMARK;
          })(*FAIL)#;
        return @matched;
      }
  ';
  # warn "JMD $sub";

  $conf->{$ruletype}->{trie_re_sub} = eval $sub;
  if ($@) { warn "REGMARK sub compilation failed: $@"; }
}

###########################################################################

# delegate these to the OneLineBodyRuleType object
sub check_start {
  my ($self, $params) = @_;
  $self->{one_line_body}->check_start($params);
}

sub check_rules_at_priority {
  my ($self, $params) = @_;
  $self->{one_line_body}->check_rules_at_priority($params);
}

###########################################################################

sub run_body_fast_scan {
  my ($self, $params) = @_;

  return unless ($params->{ruletype} eq 'body');

  my $pri = $params->{priority};
  my $nicepri = $params->{priority}; $nicepri =~ s/-/neg/g;
  my $ruletype = ($params->{ruletype}.'_'.$nicepri);
  my $scanner = $params->{permsgstatus};
  my $conf = $scanner->{conf};

  my $trie_re_sub = $conf->{$ruletype}->{trie_re_sub};
  if (!$trie_re_sub)
  {
    dbg("zoom: run_body_fast_scan for $ruletype skipped, no rules");
    return;
  }

  my $do_dbg = (would_log('dbg', 'zoom') > 1);
  my $scoresptr = $conf->{scores};

  dbg("zoom: run_body_fast_scan for $ruletype start");

  {
    no strict "refs";
    foreach my $line (@{$params->{lines}})
    {
      my @caught = $trie_re_sub->($line);
      next unless (scalar @caught > 0);

      my %alreadydone = ();
      foreach my $rulename (@caught) {
        {
          # only try each rule once per line
          next if exists $alreadydone{$rulename};
          $alreadydone{$rulename} = undef;

          # ignore 0-scored rules, of course
          next unless $scoresptr->{$rulename};

          $scanner->got_hit($rulename, "BODY: ", ruletype => "p595_body");
        }
      }
    }
    use strict "refs";
  }

  dbg("zoom: run_body_fast_scan for $ruletype done");
}

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

  my $do_dbg = (would_log('dbg', 'zoom') > 1);
  return unless $do_dbg;

  my $miss = $self->{rule2xs_misses};
  foreach my $r (sort { $miss->{$a} <=> $miss->{$b} } keys %{$miss}) {
    dbg "zoom: ".$miss->{$r}." misses for rule2xs rule $r\n";
  }
}

###########################################################################

1;
