The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -w


use strict;
use Bot::Infobot::Config qw( parse_config save_config );
use Text::CSV;
use Data::Dumper;
use Module::Pluggable require => 1, search_path => "Bot::Infobot::Importer", sub_name => 'importers';


die "You must pass at least a DSN\n" unless @ARGV;

my %config = parse_config ( 'infobot.conf' );


my $name         = $config{store}->{type} || 'Storable';

    
my $store_class  = "Bot::BasicBot::Pluggable::Store::${name}";

eval "require $store_class";
die "Couldn't load $store_class - $@\n" if $@;

$store_class->import;

my $store = $store_class->new( %{$config{store}} ); 



$|++;

# sort out the importer class
my $importer;
for my $class (importers()) {
	my $sub = $class->can('handle');
    if ($sub && $sub->(@ARGV)) {
        $importer = $class->new(@ARGV);
        last;
    }
}

die "Couldn't find a importer to handle - ".join(" ",@ARGV)."\n"
    unless $importer;

=head1 NAME

infobot_import - import an old infobot brain into new infrastructure 

=head1 SYNOPSIS

    infobot-import dsn [user [password]]

=head1 USAGE

If you're using a stock infobot then changing to the directory where the 
bot stores its config. There should be files lying around like 
<botname>-is.(dir|pag). Then do :

    infobot-import <botname>

If it's a modified infobot using the DBI backend then doing something 
like

    infobot-import dbi:mysql:<dbname> <username> <password>

should work.

You can also import factoids from factpacks such as the ones at

    http://www.infobot.org/snapshots/factpacks/

by doing 

    infobot-import <filename>

where I<filename> ends in I<.fact>.

It should be noted that if you have a lot of items then the Storable 
backend, whilst quick, will bloat up to a lot of memory. the DBI backend 
is slow but efficient. The Deep backend is a good compromise.


=head1 DESCRIPTION

This program will import the values for an existing, old style infobot
into a new style.

=over 4

=item infobot factoids

=item seen lists

=item karma

=item ignore lists

=back

it will look in the current directory for an infobot.config
for which C<Bot::BasicBot::Pluggable::Store> backend to use.

See the C<infobot> documentation for details on the 
C<Store> config keys.

=head1 AUTHOR

Simon Wistow <simon@thegestalt.org>

=head1 COPYRIGHT

Copyright 2005, Simon Wistow

Distributed under the same terms as Perl itself

=cut

# various vars
my $width = 72;
my $debug_limit = -100;

############################################################
# Banner
############################################################
print "\n\n".("*" x $width)."\n\n";
print "   Infobot brain slurper  \n";
print "   - convert an Infobot brain to Bot::BasicBot::Pluggable";
print "\n\n".("*" x $width)."\n\n";



############################################################
# Injoke
############################################################
status_init("reticulating splines");
status_finish("injoke successfully promulgated");


FACTOIDS:
############################################################
# Factoids first
############################################################
{
    status_init("extracting factoids");
    my $atoms = 0;
    my $facts = 0;

    foreach my $table (qw(is are)) {
        $importer->fetch(${table});
        my $rows  = $importer->rows;

        while (my $result = $importer->next) {
            my $what = $result->{key};
            my $fact = $result->{value};
            my $time = $result->{touched} || time();
            status_update("$facts/$rows");
            
            my @current;
            foreach my $part (split /\s*\|\s*/,$fact) {
                my $alt = ($part =~ /^</);
                if ($alt) {
                    push @current, { alt => 1, text => $part}; 
                } else {
                    push (@current, { alt => 0, text => $_ }) for split (/\s+or\s+/,$part);
                }
                $atoms += scalar(@current);
                
            }
            my $set = {
                   is_are => $table,
               factoids => \@current,
              };

            $store->set('Infobot',"infobot_".lc($what), $set);
            last if $debug_limit > 1 && $facts >= $debug_limit;
            $facts++;
        }
        $importer->finish;
        last;
    }
    status_finish("$facts factoids with $atoms atoms");
}


SEEN:
############################################################
# Now seen
############################################################
{
    status_init("extracting last seen");
    my %seen;
    my $count = 0;
    my $total = 0;
    $importer->fetch("seen");
    while (my $result = $importer->next) {
        my $who  = $result->{key};
        my $seen = $result->{value};
        $total++;
        status_update("$total");
        #local $; = \034;
        my ($when,$where,$what) = split /$;/, $seen;
        next unless defined $when && defined $where && defined $what;
        $store->set('Seen',"seen_$who", { time => $when, channel => $where, what => $what });

        $count++;
        last if $debug_limit > 1 && $count >= $debug_limit;
    }
    $importer->finish;
    $total -= $count;
    status_finish("did $count people, skipped $total");
}


KARMA:
############################################################
# Then karma
############################################################
{
    my %karma;
    my %scores;

    # first off we select stuff from the negative and postive comments
    foreach my $what (qw(positive negative)) 
    {
        status_init("extracting $what karma");
        my $count = 0;
        eval { $importer->fetch("${what}karmacomments") };
        
        while (!$@ && (my $result = $importer->next)) {
            my $who  = $result->{key};
            my $why  = $result->{value};
            my $when = $result->{touched};
            my $pos  = $what eq 'positive';
            
            my $csv = Text::CSV->new(); $csv->parse($why);
            
            foreach my $col ($csv->fields) {                
                push @{$karma{$who}}, { positive => $pos, reason => $col, timestamp => $when, who => 'infobot_importer' };
                $scores{$who} += -1 + (2*$pos);
                $count++;
            }
            status_update("$count");
            last if $debug_limit > 1 && $count >= $debug_limit;
        }
        status_finish("inserted $count real items");
        $importer->finish;
    }
    status_init("fudging non explained karma");
    my $count = 0;
    # then we count the difference between that and the actual tally
    
    my $table = (ref($importer) =~ /DBI$/) ? "plusplus" : "karma";
    $importer->fetch($table);

    while (my $result = $importer->next) {
        my $who    = $result->{key};
        my $many   = $result->{value}   || 0;
        my $when   = $result->{touched} || time();

        $scores{$who} ||= 0;
        
        my $diff   = $many - $scores{$who}; next if $diff == 0;
        my $pos    = $diff > 0; $diff *= -1 if !$pos;
        my $text   = ($pos)? "positive" : "negative";
        #print "  updating $who karma";
        foreach (0..$diff) {
            push @{$karma{$who}}, { positive => $pos, reason => '', timestamp => $when, who => 'infobot_importer' };            
        }
        $count++;
        status_update("$count");
        $importer->finish;
    }
    status_finish("fudged $count items");

    $count = 0;
    my $total = scalar(keys %karma);
    status_init("storing karma"); 
    while (my ($key,$val) = each %karma) {
        $count++;
        status_update("$count/$total");
        $store->set('Karma',"karma_${key}", $val);
        delete $karma{$key};
    }
    status_finish("stored $count karma incidents");
}

IGNORE:
{
    status_init("setting ignore list");
    my $ignore = $config{'ignore_list'} || "";
    my %ignore = map { $_ => 1 } split ' ', $ignore; 
    my $count = 0;
    $importer->fetch('ignore');
    while (my $who = $importer->next) {
    next unless $who->{key};
        $ignore{$who->{key}} = 1;
        $count++;
    }
    $importer->finish;
    $config{'ignore_list'} = join " ", keys %ignore;
    save_config( 'infobot.conf', %config );
    status_finish("added $count people");
}


print "\n\nCompleted - have a nice day!\n\n";




{
    my $status_header = "";

    # hrmm, maybe I could do something with a closure
    # to have to stop the double print and status thingies
    sub status_init {
        $status_header = shift || "";

        print $status_header;
        print "."x($width-length($status_header));
    }

    sub status_update {
        my $update = shift || "";
        print "\r";
        print $status_header;
        print "."x($width-length($status_header.$update));
        print $update;
        
    }

    sub status_finish {
        my $end = shift || "";
                print "\r";
                print $status_header;
                print "."x($width-length($status_header.$end));
                print $end;
        print "\n";
        $status_header = "";
    }
}