package Mail::Audit::Corpus;
use strict;
use warnings;
use Mail::Audit;
use DBI;
use vars qw($VERSION);
$VERSION = 0.001;

=head2 new

Create a new corpus object by opening a corpus database.
If the database doesn't exist it is created.

=cut

sub new {
    my ($class, $corpus) = @_;

    my $self = { name => $corpus};
    bless $self, $class;

    my $dbh = DBI->connect("dbi:SQLite:dbname=$corpus.bayesdb","","", 
			   { AutoCommit => 0, RaiseError => 1 });
    $self->{dbh} = $dbh;
    $dbh->{RaiseError} = 0;
    my $msgs = $dbh->selectrow_array("select count(*) from messages");
    #my $tokens = $dbh->selectrow_array("select count(*) from message_tokens");
    unless (defined($msgs)) {
	$self->init;
    }
    $dbh->{RaiseError} = 1;
    return $self;
}

=head2 init

Initialize the corpus database.
This is done rather brutally by dropping and recreating the tables.

=cut

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

    my $dbh = $self->{dbh};
    my $raiseerror = $dbh->{RaiseError};
    $dbh->{RaiseError} = 0;
    $dbh->{PrintError} = 0;
    $dbh->do("drop table messages");
    $dbh->do("create table messages(
		id integer primary key,
		filename varchar,
		message_id varchar unique
	      )");
    $dbh->do("drop table tokens");
    $dbh->do("create table tokens(
		id integer primary key,
		token varchar unique,
		count int
	      )");
    $dbh->do("drop table message_tokens");
    $dbh->do("create table message_tokens(
		msg_id int,
		token_id int
	      )");
    $dbh->do("create index message_tokens_msg_id_idx on message_tokens(msg_id)");
    $dbh->{RaiseError} = $raiseerror;
}

sub message_ids {
    my ($self) = @_;
    return @{$self->{dbh}->selectcol_arrayref("select message_id from messages")};
}

=head2 add($message, $force)

Add a message to this corpus.

$message must be Mail::Audit(Bayesian) object.

The $force parameter determines what should happen if there is already a
message with the same message id in the corpus. If force is true, it is
removed and readded, otherwise the corpus is unchanged.
Normally, when reindexing a mailbox, $force should be false,
since the messages which are already in the corpus won't have changed.
However, you may want to force updating the messages when some messages
may have changed or when the tokenizer algorithm in
Mail::Audit::Bayesian changed.

=cut

sub add {
    my ($self, $message, $force) = @_;

    my $dbh = $self->{dbh};
    my $msgid = $message->get('Message-Id');
    my $mid = $dbh->selectrow_array("select id from messages where message_id=?",
    				    {},
				    $msgid
				   );
    if ($mid) {
	# message already in corpus
	unless ($force) {
	    return;
	}
	
	my @msgtids = @{$dbh->selectcol_arrayref(
			    "select token_id from message_tokens where msg_id=?", {}, $mid
			)};
	for my $tid (@msgtids) {
	    $dbh->do("update tokens set count=count-1 where id=?",
		     {},
		     $tid);
	}
	$dbh->do("delete from message_tokens where msg_id=?",
	         {},
		 $mid);
	$dbh->do("delete from messages where id=?",
	         {},
		 $mid);
    } else {
	my $fn    = $message->{_bayesian_filename};
	$dbh->do("insert into messages(message_id, filename) values(?, ?)",
	         {},
		 $msgid, $fn);
	$mid = $dbh->func('last_insert_rowid');

    }
    for my $token ($message->tokens) {
	my $tid = $dbh->selectrow_array("select id from tokens where token=?",
					{},
					$token
				       );
	unless ($tid) {
	    $dbh->do("insert into tokens(token, count) values(?, 0)",
		     {},
		     $token
		    );
	    $tid = $dbh->func('last_insert_rowid');
	}
	$dbh->do("insert into message_tokens(msg_id, token_id) values(?, ?)", {}, $mid, $tid);
	$dbh->do("update tokens set count=count+1 where id=?", {}, $tid);
    }
    $dbh->commit();
}

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

    return scalar($self->{dbh}->selectrow_array("select count(*) from messages"));
}

sub token_count {
    my ($self, $token) = @_;
    unless ($self->{sth_select_tokens_count}) {
	$self->{sth_select_tokens_count} = $self->{dbh}->prepare("select count from tokens where token=?");
    }
    my $tkcount = $self->{dbh}->selectrow_array($self->{sth_select_tokens_count}, {}, $token);
    return $tkcount || 0;
}

sub name {
    my ($self, $newname) = @_;

    my $oldname = $self->{name};
    $self->{name} = $newname if defined($newname);
    return $oldname;
}

sub DESTROY {
    my ($self) = @_;
    $self->{dbh}->disconnect;
}

sub delete {
    my ($self, %attrib) = @_;

    my $message_id = $attrib{message_id};
    return unless $message_id;
    my $dbh = $self->{dbh};
    my $mid = $dbh->selectrow_array("select id from messages where message_id=?",
    				    {},
				    $message_id
				   );
    return unless $mid;

    # Two variants of updating the token count.
    # I was hoping that the first would be quite a bit faster than the
    # second, but with sqlite there is almost no difference.
    # There may be larger differences with other engines so I leave both
    # in.
    if (1) {
        $dbh->do("update tokens set count=count-1
                  where id in (select token_id
                               from message_tokens
                               where msg_id=?)",
                 {},
                 $mid);
    } else {
        my @msgtids = @{$dbh->selectcol_arrayref(
                            "select token_id from message_tokens where msg_id=?", {}, $mid
                        )};
        for my $tid (@msgtids) {
            $dbh->do("update tokens set count=count-1 where id=?",
                     {},
                     $tid);
        }
    }
    $dbh->do("delete from message_tokens where msg_id=?",
	     {},
	     $mid);
    $dbh->do("delete from messages where id=?",
	     {},
	     $mid);
    $dbh->commit();
}

1;
