package App::Pocoirc::ReadLine;
BEGIN {
  $App::Pocoirc::ReadLine::AUTHORITY = 'cpan:HINRIK';
}
BEGIN {
  $App::Pocoirc::ReadLine::VERSION = '0.43';
}

use strict;
use warnings FATAL => 'all';
use Carp;
use Data::Dump 'dump';
use POE;
use POE::Component::IRC::Plugin qw(PCI_EAT_NONE);
use POE::Wheel::ReadLine;
use POE::Wheel::ReadWrite;
use Symbol qw(gensym);

sub new {
    my ($package) = shift;
    croak "$package requires an even number of arguments" if @_ & 1;
    my $self = bless { @_ }, $package;

    POE::Session->create(
        object_states => [
            $self => [qw(
                _start
                got_user_input
                got_output
                restore_stdio
            )],
        ],
    );
    return $self;
}

sub PCI_register {
    my ($self, $irc, %args) = @_;

    if (!defined $self->{ui_irc}) {
        $self->{ui_irc} = $irc;
        $self->{console}->get("$args{network}> ");
    }

    $self->{ircs}{$args{network}} = $irc;
    $irc->plugin_register($self, 'SERVER', 'network');
    $self->{registered}++;
    return 1;
}

sub PCI_unregister {
    my ($self, $irc, %args) = @_;
    $self->{registered}--;
    if ($self->{registered} == 0) {
        $poe_kernel->call($self->{session_id}, 'restore_stdio');
    }
    return 1;
}

sub _start {
    my ($kernel, $session, $self) = @_[KERNEL, SESSION, OBJECT];

    $self->{session_id} = $session->ID();
    $self->{console} = POE::Wheel::ReadLine->new(
        InputEvent => 'got_user_input',
        PutMode    => 'immediate',
        AppName    => 'pocoirc',
    );

    open my $orig_stderr, '>&', STDERR or die "Can't dup STDERR: $!";
    $self->{orig_stderr} = $orig_stderr;

    open my $orig_stdout, '>&', STDOUT or die "Can't dup STDOUT: $!";
    $self->{orig_stdout} = $orig_stdout;

    my ($read_stdout, $write_stdout) = (gensym(), gensym());
    pipe $read_stdout, $write_stdout;
    $self->{read_stdout} = $read_stdout;
    open STDOUT, '>&', $write_stdout or die "Can't pipe STDOUT: $!";

    my ($read_stderr, $write_stderr) = (gensym(), gensym());
    pipe $read_stderr, $write_stderr;
    $self->{read_stderr} = $read_stderr;
    open STDERR, '>&', $write_stderr or die "Can't pipe STDERR: $!";
    STDERR->autoflush(1);

    binmode $_, ':utf8' for (*STDOUT, *STDERR);

    $self->{stderr_reader} = POE::Wheel::ReadWrite->new(
        Handle     => $read_stderr,
        InputEvent => 'got_output',
    );
    $self->{stdout_reader} = POE::Wheel::ReadWrite->new(
        Handle     => $read_stdout,
        InputEvent => 'got_output',
    );

    $self->{console}->get();
    return;
}

sub got_output {
    my ($self, $line) = @_[OBJECT, ARG0];
    $self->{console}->put($line);
    return;
}

sub got_user_input {
    my ($self, $line, $ex) = @_[OBJECT, ARG0, ARG1];

    if (defined $ex && $ex eq 'interrupt') {
        $self->{Pocoirc}->shutdown('Exiting due to user interruption');
        return;
    }

    if (defined $line && length $line) {
        $self->{console}->add_history($line);

        if (my ($new_network) = $line =~ /^network\s*(.+)/) {
            my $found;
            while (my ($network, $irc) = each %{ $self->{ircs} }) {
                if ($network =~ /^\Q$new_network\E$/i) {
                    $self->{ui_irc} = $irc;
                    $self->{console}->get("$network> ");
                    $found = 1;
                    last;
                }
            }
            $self->_print_networks() if !$found;
        }
        elsif ($line =~ /^networks\s*$/) {
            $self->_print_networks();
        }
        elsif (my ($feature) = $line =~ /^(verbose|trace)\s*$/) {
            if ($self->{Pocoirc}->$feature()) {
                $self->{Pocoirc}->$feature(0);
                print "Disabled '$feature'\n";
            }
            else {
                $self->{Pocoirc}->$feature(1);
                print "Enabled '$feature'\n";
            }
        }
        elsif (my ($cmd, $args) = $line =~ m{^/([a-z_]+)\s*(.+)?}) {
            my @args = defined $args ? eval $args : ();
            $self->{ui_irc}->yield($cmd, @args);
        }
        elsif (my ($method, $params) = $line =~ m{^\.([a-z_]+)\s*(.+)?}) {
            my @params = defined $params ? eval $params : ();

            local ($@, $!);
            eval {
                print dump($self->{ui_irc}->$method(@params)), "\n";
            };
            if (my $err = $@) {
                chomp $err;
                warn $err, "\n";
            }
        }
        else {
            $self->_print_help();
        }
    }

    $self->{console}->get();
    return;
}

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

    print <<'EOF';
Type "network foo" to switch networks, or "networks" for a list of networks.

Type ".foo 'bar', 'baz'" to call the method "foo" with the arguments 'bar'
and 'baz' on the IRC component. You must quote your arguments since they
will be eval'd, and don't forget to use commas between arguments.

Type "/foo 'bar', 'baz'" to call the POE::Component::IRC command foo with the
arguments 'bar' and 'baz'. This is equivalent to: .yield 'foo', 'bar', 'baz'

Type "verbose" and "trace" to flip those features on/off.
EOF

    return;
}

sub _print_networks {
    my ($self) = @_;
    print "Available networks: ", join(', ', keys %{ $self->{ircs} }), "\n";
    return;
}

sub S_network {
    my ($self, $irc) = splice @_, 0, 2;
    my $network = ${ $_[0] };

    $self->{console}->get("$network> ");
    for my $net (keys %{ $self->{ircs} }) {
        if ($self->{ircs}{$net} == $irc) {
            delete $self->{ircs}{$net};
            $self->{ircs}{$network} = $irc;
        }
    }
    return PCI_EAT_NONE;
}

sub restore_stdio {
    my ($self) = $_[OBJECT];

    my $orig_stderr = delete $self->{orig_stderr};
    open STDERR, '>&', $orig_stderr or warn "Failed to restore STDERR: $!";
    STDERR->autoflush(1);

    my $orig_stdout = delete $self->{orig_stdout};
    open STDOUT, '>&', $orig_stdout or warn "Failed to restore STDERR: $!";

    binmode $_, ':encoding(utf8)', for (*STDERR, *STDOUT);

    delete $self->{console};
    delete $self->{stderr_reader};
    delete $self->{stdout_reader};
    return;
}

1;

=encoding utf8

=head1 NAME

App::Pocoirc::ReadLine - A PoCo-IRC plugin which provides a ReadLine UI

=head1 DESCRIPTION

This plugin is used internally by L<App::Pocoirc|App::Pocoirc>. No need for
you to use it.

=head1 AUTHOR

Hinrik E<Ouml>rn SigurE<eth>sson, hinrik.sig@gmail.com

=cut
