The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
use utf8;
use FindBin;
use File::Spec;
use lib File::Spec->catfile($FindBin::RealBin, 'lib');

use Irssi;

use Glib;
use POE::Sugar::Args;
use POE qw/Session::Irssi Loop::Glib/;

use Encode;
use App::Mobirc;
use App::Mobirc::Util;
use Module::Reload;
use Config::Tiny;

our %IRSSI = ( name => 'mobirc' );

Irssi::settings_add_str('mobirc', 'mobirc_config_path', '');
Irssi::settings_add_bool('mobirc', 'mobirc_auto_start', 0);

POE::Session::Irssi->create(
    inline_states => {
        map { ( $_ => __PACKAGE__->can("poe_$_") ) }
            qw/_start load initialize_mobirc unload/
    },

    irssi_commands => {
        mobirc => sub {
            my $poe = sweet_args;
            my ($data, $server, $witem) = @{ $poe->args->[1] };

            if (($data || '') =~ /start/) {
                if ($poe->kernel->alias_resolve('mobirc_httpd')) {
                    Irssi::print('mobirc is already started!');
                    return;
                }
                $poe->kernel->yield('load');
            }
            elsif (($data || '') =~ /stop/) {
                $poe->kernel->yield('unload');
            }
        },
    },

    irssi_signals => {
        map( { ( "message $_" => bind_signal("irssi_$_") ) }
            qw/public private own_public own_private join part quit kick nick own_nick invite topic/
        ),
        map( { ( "message irc $_" => bind_signal("irssi_irc_$_") ) }
            qw/op_public own_wall own_action action own_notice notice own_ctcp ctcp/
        ),
        'server event' => bind_signal('irssi_irc_snotice'),
        'print text' => bind_signal('irssi_print_text'),
        'command script unload' => \&script_unload,
    },
);

sub nick_name {
    my $server = Irssi::active_server() or return '';
    $server->{nick};
}

sub bind_signal {
    my $sub = __PACKAGE__->can(shift) or return;

    return sub {
        return unless $_[KERNEL]->alias_resolve('mobirc_httpd');
        $sub->(@_);
    };
}

sub poe__start {
    my $poe = sweet_args;

    if (Irssi::settings_get_bool('mobirc_auto_start')) {
        $poe->kernel->yield('load');
    }
}

sub poe_load {
    my $poe = sweet_args;
                    Irssi::print('mobirc is already started!');
    Module::Reload->check;

    my $mobirc = $poe->kernel->call( $poe->session, 'initialize_mobirc')
        or return;

    # dummy irc session to handle post message from httpd
    POE::Session->create(
        inline_states => {
            _start => sub {
                $_[KERNEL]->alias_set('irc_session');
            },
        },
        heap => {
            irc => __PACKAGE__,
            config => { incode => 'utf-8' },
        },
    );

    POE::Session->create(
        inline_states => {
            _start => sub {
                $_[KERNEL]->alias_set('mobirc_irc');
            },

        },
    );

    $mobirc->register_hook(
        process_command => ( undef, sub {
            my ( $self, $global_context, $command, $channel ) = @_;

            ($channel) = grep { $_->{name} eq $channel->name } Irssi::channels();
            if ($channel) {
                $channel->{server}->command("MSG $channel->{name} $command");
            }
        })
    );

    Irssi::print('mobirc is already started!');
    $mobirc->run_hook('run_component');

    Irssi::print('started mobirc') if $poe->kernel->alias_resolve('mobirc_httpd');
}

sub irssi_print_text {
    my $poe = sweet_args;
    my ($dest, $text, $stripped) = @{ $poe->args->[1] };

    if ($dest->{level} & MSGLEVEL_HILIGHT) {
        App::Mobirc::Model::Channel->update_keyword_buffer($poe->heap->{__last_row});
    }
}

sub irssi_public {
    my $poe = sweet_args;
    my ($server, $msg, $nick, $address, $target) = @{ $poe->args->[1] };

    add_message( $poe, $target, $nick, $msg, 'public' );
}

sub irssi_private {}

sub irssi_own_public {
    my $poe = sweet_args;
    my ($server, $msg, $target) = @{ $poe->args->[1] };

    add_message( $poe, $target, $server->{nick}, $msg, 'public' );
}
sub irssi_own_private {}

sub irssi_join {
    my $poe = sweet_args;
    my ($server, $channel, $nick, $address) = @{ $poe->args->[1] };

    my $mobirc = $poe->heap->{mobirc};

    $channel = $mobirc->get_channel(decode_utf8 $channel->{name});

    unless ($server->{nick} eq $nick) {
        add_message( $poe, $channel, undef, "$nick joined", 'join');
    }
}

sub irssi_part {
    my $poe = sweet_args;
    my ($server, $channel, $nick, $address, $reason) = @{ $poe->args->[1] };

    my $mobirc = $poe->heap->{mobirc};

    $channel = normalize_channel_name( decode('utf-8', $channel) );
    if ($server->{nick} eq $nick) {
        delete $mobirc->{channels}->{$channel};
    }
    else {
        add_message($poe, $channel, undef, "$nick leaves", 'leave');
    }
}

sub irssi_quit {}
sub irssi_kick {}
sub irssi_nick {}
sub irssi_own_nick {}
sub irssi_invite {}

sub irssi_topic {
    my $poe = sweet_args;
    my ($server, $channel, $topic, $nick, $address) = @{ $poe->args->[1] };

    my $mobirc = $poe->heap->{mobirc};

    $channel = $mobirc->get_channel( normalize_channel_name(decode('utf-8', $channel)) );
    $channel->topic( decode('utf-8', $topic) );

    add_message($poe, $channel, undef, "$nick set topic: $topic", 'topic');
}

sub irssi_irc_op_public {}
sub irssi_irc_own_wall {}

sub irssi_irc_own_action {
    my $poe = sweet_args;
    my ($server, $msg, $target) = @{ $poe->args->[1] };

    $msg = sprintf('* %s %s', $server->{nick}, decode('utf-8', $msg));
    add_message( $poe, $target, '', $msg, 'ctcp_action');
}

sub irssi_irc_action {
    my $poe = sweet_args;
    my ($server, $msg, $nick, $address, $target) = @{ $poe->args->[1] };

    $msg = sprintf('* %s %s', $nick, decode('utf-8', $msg));
    add_message( $poe, $target, '', $msg, 'ctcp_action');
}

sub irssi_irc_own_notice {
    my $poe = sweet_args;
    my ($server, $msg, $target) = @{ $poe->args->[1] };

    add_message($poe, $target, $server->{nick}, $msg, 'notice');
}

sub irssi_irc_notice {
    my $poe = sweet_args;
    my ($server, $msg, $nick, $address, $target) = @{ $poe->args->[1] };

    add_message($poe, $target, $nick, $msg, 'notice');
}

sub irssi_irc_snotice {
    my $poe = sweet_args;
    my ($server, $msg, $nick, $address, $target) = @{ $poe->args->[1] };
    return unless $msg =~ /^\d/; # messages only

    add_message($poe, '*server*', undef, $msg, 'snotice');
}
sub irssi_irc_own_ctcp {}
sub irssi_irc_ctcp {}

sub poe_initialize_mobirc {
    my $poe = sweet_args;

    delete $poe->heap->{mobirc} if $poe->heap->{mobirc};

    my $conffname = Irssi::settings_get_str('mobirc_config_path');
    unless ($conffname) {
        Irssi::print('mobirc_config_path is not defined, please do "/set mobirc_config_path your_ini_path" first');
        return;
    }
    unless (-f $conffname && -r _) {
        Irssi::print("file does not exist: $conffname");
        return;
    }

    my $mobirc;
    eval { $mobirc = App::Mobirc->new(config => $conffname) };
    if ($@) {
        Irssi::print("can't initialize mobirc: $@");
        return;
    }

    $poe->heap->{mobirc} = $mobirc;
    $poe->heap->{config} = $mobirc->config;

    $mobirc->add_channel( App::Mobirc::Model::Channel->new(name => U '*server*') );
    for my $channel (Irssi::channels()) {
        my $channel_name = normalize_channel_name(decode_utf8 $channel->{name});
        $mobirc->add_channel( App::Mobirc::Model::Channel->new(name => $channel_name) );
    }

    $mobirc;
}

sub poe_unload {
    my $kernel = $_[KERNEL];

    if (my $httpd_session = $kernel->alias_resolve('mobirc_httpd')) {
        $kernel->call( $httpd_session => 'shutdown' );
        delete $_[HEAP]->{mobirc};
        Irssi::print('stopped mobirc');
    }
}

sub script_unload {
    my ($kernel, $session, $args) = @_[KERNEL, SESSION, ARG1];
    my ($script) = @$args;

    if ($script =~ /mobirc/) {
        $kernel->call($session, 'unload');
    }
}

# XXX: to avoid weird warnings
{ package Irssi::Nick }

{
    no warnings 'redefine';
    sub add_message {
        my ($poe, @args) = @_;
        for my $arg (@args) {
            $arg = decode('utf-8', $arg) unless utf8::is_utf8($arg);
        }
        my ($channel, $who, $body, $class) = @args;

        $channel = $poe->heap->{mobirc}->get_channel(normalize_channel_name($channel))
            or return;

        my $message = App::Mobirc::Model::Message->new(
            who   => $who,
            body  => $body,
            class => $class,
        );
        $channel->add_message($poe->heap->{__last_row} = $message);
    }
}

=pod

=head1 NAME

mobirc.pl - irssi plugin for Mobirc

=head1 SYNOPSIS

    1.. copy (or link) this script into irssi script directory
    
    2. run irssi with Mobirc
    
       PERL5LIB=/path/to/mobirc/lib:/path/to/mobirc/extlib/ irssi
    
    3. start script in irssi
    
       /run mobirc
    
    4. set config.ini path
    
       /set mobirc_config_path /path/to/your/config.ini
    
    5. start mobirc
    
       /mobirc start

=head1 KNOWN BUGS

irssi dumps core at exit irssi. anyone can fix this?

=head1 AUTHOR

Daisuke Murase <typester@cpan.org>

Kazuhiro Osawa

Tokuhiro Matsuno

=cut