use strict;
use warnings;
use experimental 'signatures';

our $VERSION = '0.1';
our %IRSSI = (
    authors     => 'Nei',
    contact     => 'Nei @ anti@conference.jabber.teamidiot.de',
    url         => "http://anti.teamidiot.de/",
    name        => 'bbbpm',
    description => 'Run Bot::BasicBot::Pluggable::Module_s in Irssi',
    license     => 'Perl 5',
   );

use POSIX;
use CPAN::Meta::YAML;
use Irssi::Log::Log4perl;
use Log::Log4perl::Level;
use Irssi::Bot::BasicBot::Pluggable;

my $bot;

sub _get_store {
    my $store_config = Irssi::settings_get_str('bbbpm_store');
    $store_config =~ s/\s+$//;
    $store_config =~ s/^((?:\w|::)+)://;
    my $store_class = $1;
    unless ($store_class) {
        return Bot::BasicBot::Pluggable::Store->new_from_hashref(
            { type => ($store_config || 'Memory') } );
    }

    my %config;
    while ($store_config =~ s/^((?:\\\\|\\:|[^:])+)(?::|$)//) {
        my $kv = $1;
        $kv =~ s/\\(.)/$1/g;
        my ($k, $v) = split '=', $kv, 2;
        $config{$k} = $v;
    }
    $config{type} = $store_class;
    return Bot::BasicBot::Pluggable::Store->new_from_hashref(
        \%config );
}

sub init {
    my $store = _get_store();
    $bot = Irssi::Bot::BasicBot::Pluggable->new(
        irssi => __PACKAGE__,
        store => $store,
       );

    print "store = $store";
    $bot->load($_) for qw(Auth Loader);

    Irssi::timeout_add_once(5000, 'timeout_bot_tick', '');

    sig_setup_changed();
}

sub cmd_bbbpm ($data, $server, $item) {
    Irssi::command_runsub('bbbpm', $data, $server, $item);
}

sub cmd_bbbpm_load ($data, $server, $item) {
    $data =~ s/\s+$//;
    $bot->load($data);
}

sub UNLOAD {
    $bot->stop;
}

# => connected
sub sig_event_connected ($server) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    $bot->connected;
}

# => chanjoin
sub sig_message_join ($server, $channel, $nick, $address, $account, $realname) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    local $bot->{chanjoin_account} = $account;
    Irssi::signal_continue($server, $channel, $nick, $address, $account, $realname);
    $bot->irc_chan_received_state('chanjoin', $nick, $address, $channel);
}

# => chanpart
sub sig_message_part ($server, $channel, $nick, $address, $reason) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    local $bot->{chanpart_reason} = $reason;
    Irssi::signal_continue($server, $channel, $nick, $address, $reason);
    $bot->irc_chan_received_state('chanpart', $nick, $address, $channel);
}

# => userquit
sub sig_message_quit ($server, $nick, $address, $reason) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $nick, $address, $reason);
    $bot->userquit({ who => $nick, body => $reason });
}

# => nick_change
sub sig_message_nick ($server, $newnick, $oldnick, $address) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $newnick, $oldnick, $address);
    $bot->nick_change($oldnick, $newnick);
}

# => topic
sub sig_message_topic ($server, $channel, $topic, $nick, $address) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $channel, $topic, $nick, $address);
    $bot->topic({ channel => $channel, who => $nick, topic => $topic });
}

# => kicked
sub sig_message_kick ($server, $channel, $nick, $kicker, $address, $reason) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $channel, $nick, $kicker, $address, $reason);
    $bot->kicked({ channel => $channel, who => $kicker, kicked => $nick, reason => $reason });
}

sub sig_send_text ($line, $server, $item) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    $bot->irc_received_state('said', 'localresponse', '@@@ local user @@@', '@@@', $bot->nick, $line);
    Irssi::signal_stop;
}

# => said
sub sig_message_public ($server, $msg, $nick, $address, $target) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $msg, $nick, $address, $target);
    $bot->irc_received_state('said', 'say', $nick, $address, $target, $msg);
}

# => said
sub sig_message_private ($server, $msg, $nick, $address, $target) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $msg, $nick, $address, $target);
    $bot->irc_received_state('said', 'say', $nick, $address, $bot->nick, $msg);
}

# => emoted
sub sig_message_irc_action ($server, $msg, $nick, $address, $target) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    Irssi::signal_continue($server, $msg, $nick, $address, $target);
    $bot->irc_received_state('emoted', 'emote', $nick, $address, $target, $msg);
}

# => noticed
sub sig_message_irc_notice ($server, $msg, $nick, $address, $target) {
    local $bot->{conn_tag} = $server ? $server->{tag} : undef;
    local $bot->{server} = $server;
    local $bot->{noticed} = 1;
    Irssi::signal_continue($server, $msg, $nick, $address, $target);
    # $bot->irc_received_state('said', 'say', $nick, $address, $target, $msg);
}

sub sig_setup_changed {
    my $curlevel = Irssi::Log::Log4perl->level();
    my $level = $Log::Log4perl::Level::PRIORITY{ uc Irssi::settings_get_str('bbbpm_loglevel') };
    unless ($level) {
        print CLIENTERROR "Invalid choice, must be one of "
            . (join ', ', map { lc }
               sort { $Log::Log4perl::Level::PRIORITY{ $a } <=> $Log::Log4perl::Level::PRIORITY{ $b } }
               keys %Log::Log4perl::Level::PRIORITY);
        Irssi::settings_set_str('bbbpm_loglevel', lc $Log::Log4perl::Level::LEVELS{$curlevel} );
    } elsif ($level != $curlevel) {
        Irssi::Log::Log4perl->level($level);
    }
}

sub timeout_bot_tick {
    return unless $bot;
    my $delay = $bot->tick;
    Irssi::timeout_add_once($delay * 1000, 'timeout_bot_tick', '')
            if $delay;
}

sub bg_do ($self, $code, $rhandler) {
    my ($rh, $wh);
    pipe($rh, $wh);

    my $pid = fork();
    if ($pid > 0) {
        close $wh;
        Irssi::pidwait_add($pid);
        my $pipetag;
        my @args = ($rh, \$pipetag, $rhandler);
        $pipetag = Irssi::input_add(fileno($rh), INPUT_READ, 'pipe_input', \@args);
        print "forked pid $pid pipe $pipetag";
        return $pipetag;
    } elsif (!defined $pid) {
        warn "fork failed: $!";
    } else {
        select STDOUT;
        POSIX::close($_) for grep { $_ != fileno($wh) } 3..13;
        eval {
            my $result = $code->();
            my $yaml = CPAN::Meta::YAML->new(+{ result => $result });
            my $data = $yaml->write_string();
            print($wh $data);
        };
        if ($@) {
            print($wh CPAN::Meta::YAML->new(+{ error => $@ })
                      ->write_string());
        }
        close($wh);
        POSIX::_exit(1);
    }
}

sub pipe_input {
    my ($rh, $pipetag, @args) = @{$_[0]};
    my $text = do { local $/; <$rh>; };
    close($rh);
    Irssi::input_remove($$pipetag);
    unless ($text) {
        warn "Something weird happend (no text => @args)";
        return;
    }
    utf8::decode($text);
    my $incoming = CPAN::Meta::YAML->read_string($text)->[0];
    if ($incoming->{result}) {
        $bot->pipe_input_handler($incoming->{result}, $$pipetag, @args);
        return;
    }
    if (defined $incoming->{error}) {
        chomp($incoming->{error});
        warn "There was an error in background processing: $incoming->{error}";
    }
}

sub own_nick ($self, $bot) {
    my $server = $bot->{conn_tag} ? Irssi::server_find_tag($bot->{conn_tag}) : undef;
    if ($server) {
        $server->{nick}
    } else {
        Irssi::parse_special('$nick')
    }
}

sub find_alternate_nicks ($self, $bot) {
    my @nicks;
    my $server = $bot->{conn_tag} ? Irssi::server_find_tag($bot->{conn_tag}) : undef;
    if ($server) {
        push @nicks, $bot->{server}{nick}, $bot->{server}{wanted_nick}, $bot->{server}{alternate_nick};
    } else {
        push @nicks, Irssi::parse_special('$nick'), Irssi::parse_special('$alternate_nick');
    }
    return grep { length } @nicks;
}

sub notice ($self, $bot, $who, $body) {
    warn "lost server (=> $who)",
        return unless $bot->{conn_tag};
    my $server = Irssi::server_find_tag($bot->{conn_tag}) || return;
    $server->command("notice $who $body");
}

sub privmsg ($self, $bot, $who, $body) {
    warn "lost server (=> $who)",
        return unless $bot->{conn_tag};
    my $server = Irssi::server_find_tag($bot->{conn_tag}) || return;
    $server->command("msg $who $body");
}

Irssi::settings_add_str('bbbpm', 'bbbpm_store', '');
Irssi::settings_add_str('bbbpm', 'bbbpm_loglevel', 'debug');

init();

Irssi::signal_add_last('event connected' => 'sig_event_connected'); # => connected
Irssi::signal_add_last('message join' => 'sig_message_join'); # => chanjoin
Irssi::signal_add_last('message part' => 'sig_message_part'); # => chanpart
Irssi::signal_add_last('message quit' => 'sig_message_quit'); # => userquit
Irssi::signal_add_last('message nick' => 'sig_message_nick'); # => nick_change
Irssi::signal_add_last('message topic' => 'sig_message_topic'); # => topic
Irssi::signal_add_last('message kick' => 'sig_message_kick'); # => kicked
# => raw_in
# => raw_out

Irssi::signal_add('send text' => 'sig_send_text');
Irssi::signal_add_last('message public' => 'sig_message_public'); # => said
Irssi::signal_add_last('message private' => 'sig_message_private'); # => said
Irssi::signal_add_last('message irc action' => 'sig_message_irc_action'); # => emoted
Irssi::signal_add_last('message irc notice' => 'sig_message_irc_notice'); # => noticed

Irssi::signal_add_last('setup changed' => 'sig_setup_changed');

Irssi::command_bind('bbbpm', 'cmd_bbbpm');
Irssi::command_bind('bbbpm load', 'cmd_bbbpm_load');

1;
