package Hubot::Adapter::Irc;
{
  $Hubot::Adapter::Irc::VERSION = '0.2.3';
}
use Moose;
use namespace::autoclean;

extends 'Hubot::Adapter';

use AnyEvent;
use AnyEvent::IRC::Client;
use Time::HiRes 'time';
use Encode qw/encode_utf8 decode_utf8/;

use Hubot::Message;

has 'robot' => (
    is  => 'ro',
    isa => 'Hubot::Robot',
);

has 'cv' => (
    is         => 'ro',
    lazy_build => 1,
);

has 'irc' => (
    is         => 'ro',
    lazy_build => 1,
);

sub _build_cv  { AnyEvent->condvar }
sub _build_irc { AnyEvent::IRC::Client->new }

sub notice { }

sub join {
    my ( $self, $channel ) = @_;
    $self->irc->send_srv( JOIN => $channel );
}
sub part    { }
sub kick    { }
sub command { }

sub whois {
    my ( $self, $nick ) = @_;
    return $self->irc->nick_ident($nick);
}

sub parse_msg {
    my ( $self, $irc_msg ) = @_;

    my ($nickname) = $irc_msg->{prefix} =~ m/^([^!]+)/;
    my $message = decode_utf8( $irc_msg->{params}[1] );
    return ( $nickname, $message );
}

sub send {
    my ( $self, $user, @strings ) = @_;
    for my $str (@strings) {
        $self->irc->send_srv( 'PRIVMSG', $user->{room}, encode_utf8($str) );
        Time::HiRes::sleep(0.1);
    }
}

sub whisper {
    my ( $self, $user, $to, @strings ) = @_;
    $self->irc->send_srv( 'PRIVMSG', $to->{name}, encode_utf8($_) )
        for @strings;
}

sub reply {
    my ( $self, $user, @strings ) = @_;
    @strings = map { $user->{name} . ": $_" } @strings;
    $self->send( $user, @strings );
}

sub run {
    my $self = shift;

    $self->checkCanStart;

    my %options = (
        nick => $ENV{HUBOT_IRC_NICK} || $self->robot->name,
        port => $ENV{HUBOT_IRC_PORT} || 6667,
        rooms    => [ split( /,/, $ENV{HUBOT_IRC_ROOMS} ) ],
        server   => $ENV{HUBOT_IRC_SERVER},
        user     => $ENV{HUBOT_IRC_USER},
        password => $ENV{HUBOT_IRC_PASSWORD},
        realname => $ENV{HUBOT_IRC_REALNAME},
        nickserv => $ENV{HUBOT_IRC_NICKSERV} || 'NickServ',
        nickservpw => $ENV{HUBOT_IRC_NICKSERV_PASSWORD},
    );

    $self->robot->name( $options{nick} );

    $self->irc->reg_cb(
        connect => sub {
            my ( $con, $err ) = @_;

            if ( defined $options{nickservpw} ) {
                $self->irc->send_srv(
                    'PRIVMSG' => $options{nickserv},
                    "identify $options{nickservpw}"
                );
            }
            else {
                $self->join($_) for @{ $options{rooms} };
            }
        },
        join => sub {
            my ( $cl, $nick, $channel, $is_myself ) = @_;
            print "$nick joined $channel\n";
            my $user = $self->createUser( $channel, $nick );
            $self->receive( new Hubot::EnterMessage( user => $user ) );
        },
        publicmsg => sub {
            my ( $cl, $channel, $ircmsg ) = @_;
            my ( $nick, $msg ) = $self->parse_msg($ircmsg);
            my $user = $self->createUser( $channel, $nick );
            $user->{room} = $channel if $channel =~ m/^#/;

            my $is_notice = $ircmsg->{command} eq 'NOTICE';
            my $class
                = $is_notice ? 'Hubot::NoticeMessage' : 'Hubot::TextMessage';
            $self->receive(
                $class->new(
                    user => $user,
                    text => $msg,
                )
            );
        },
        privatemsg => sub {
            my ( $cl, $nick, $ircmsg ) = @_;
            my ( $from, $msg ) = $self->parse_msg($ircmsg);
            my ($channel) = $msg =~ m/^\#/ ? split / /, $msg : '';
            $msg =~ s/^$channel\s*//;

            my $is_notice = $ircmsg->{command} eq 'NOTICE';

            # -NickServ- You are now identified for <nick>.
            if (   $is_notice
                && $from eq $options{nickserv}
                && $msg =~ /identified/ )
            {
                $self->join($_) for @{ $options{rooms} };
                return;
            }

            my $class
                = $is_notice
                ? 'Hubot::NoticeMessage'
                : 'Hubot::WhisperMessage';
            my $user = $self->createUser( $channel, $from );
            $self->receive(
                $class->new(
                    user => $user,
                    text => $msg,
                )
            );
        },
        part => sub {
            my ( $cl, $nick, $channel, $is_myself, $msg ) = @_;

            print "$nick leaves $channel: $msg\n";
            my $user = $self->createUser( $channel, $nick );
            $self->receive(
                new Hubot::LeaveMessage( user => $user, text => $msg ) );
        },
        quit => sub {
            my ( $cl, $nick, $msg ) = @_;

            print "$nick quit: $msg\n";
            my $user = $self->createUser( '', $nick )
                ;    # room is empty, maybe raise a error case.
            $self->receive(
                new Hubot::LeaveMessage( user => $user, text => $msg ) );
        },
        irc_330 => sub {
            ## 330 is RPL_WHOWAS_TIME
            my ( $cl, $ircmsg ) = @_;

            my $user = $self->createUser( '', '*' );
            $self->receive(
                new Hubot::NoticeMessage(
                    user => $user,
                    text => sprintf( "%s %s %s",
                        @{ $ircmsg->{params} }[ 1, 3, 2 ] ),
                )
            );
        },
        irc_mode => sub {
            my ( $cl, $ircmsg ) = @_;
            my ( $channel, $mode, $target ) = @{ $ircmsg->{params} };

            $self->robot->mode( $mode || '' )
                if $target && $target eq $self->robot->name;
        },
    );

    $self->emit('connected');
    $self->cv->begin;
    if ( $ENV{HUBOT_IRC_ENABLE_SSL} ) {
        eval "require Net::SSLeay; 1";
        if ($@) {
            die "HUBOT_IRC_ENABLE_SSL requires `Net::SSLeay`: $@\n";
        }
        else {
            $self->irc->enable_ssl;
        }
    }
    $self->irc->connect(
        $options{server},
        $options{port},
        {   nick     => $options{nick},
            user     => $options{user},
            real     => $options{realname},
            password => $options{password},
            timeout  => 10,                   # wait 10 seconds
        }
    );

    $self->cv->recv;
}

sub close {
    my $self = shift;
    $self->irc->disconnect;
    $self->cv->send;
}

sub exist {
    my ( $self, $user, $nick ) = @_;
    return $self->findUser( $user->{room}, $nick );
}

sub createUser {
    my ( $self, $channel, $from ) = @_;
    my $user = $self->userForName($from);
    unless ($user) {
        my $id = time;
        $id =~ s/\.//;
        $user = $self->userForId(
            $id,
            {   name => $from,
                room => $channel,
            }
        );
    }

    return $user;
}

sub findUser {
    my ( $self, $channel, $nick ) = @_;
    return $self->irc->nick_modes( $channel, $nick );
}

sub checkCanStart {
    my $self = shift;

    if ( !$ENV{HUBOT_IRC_NICK} && !$self->robot->name ) {
        ## use die?
        print STDERR
            "HUBOT_IRC_NICK is not defined, try: export HUBOT_IRC_NICK='mybot'\n";
        exit(2);    # TODO: research standard exit value
    }
    elsif ( !$ENV{HUBOT_IRC_ROOMS} ) {
        print STDERR
            "HUBOT_IRC_ROOMS is not defined, try: export HUBOT_IRC_ROOMS='#myroom'\n";
        exit(2);
    }
    elsif ( !$ENV{HUBOT_IRC_SERVER} ) {
        print STDERR
            "HUBOT_IRC_SERVER is not defined, try: export HUBOT_IRC_SERVER='irc.myserver.com'\n";
        exit(2);
    }
}

__PACKAGE__->meta->make_immutable;

1;

=pod

=encoding utf-8

=head1 NAME

Hubot::Adapter::Irc - IRC adapter for L<Hubot>

=head1 SYNOPSIS

    $ export HUBOT_IRC_SERVER='irc.myserver.com'
    $ export HUBOT_IRC_ROOMS='#mychannel'
    $ export HUBOT_IRC_ENABLE_SSL=1    # use SSL connection?
    $ hubot -a irc

=head1 DESCRIPTION

IRC is a fairly old protocol for Internet chat.

=head1 CONFIGURATION

=head2 REQUIRED

=over

=item HUBOT_IRC_SERVER

This is the full hostname or IP address of the IRC server you want your hubot to connect to. Make a note of it.

=item HUBOT_IRC_ROOMS

This is a comma separated list of the IRC channels you want your hubot to join. They must include the C<#>. Make a note of them.

=back

=head2 OPTIONAL

=over

=item HUBOT_IRC_NICK

This is the optional nick you want your hubot to join with. If omitted it will default to the name of your hubot.

=item HUBOT_IRC_PORT

This is the optional port of the IRC server you want your hubot to connect to. If omitted the default is C<6667>. Make a note of it if required.

=item HUBOT_IRC_USER

This is the optional username of the IRC server you want your hubot to connect to.

=item HUBOT_IRC_PASSWORD

This is the optional password of the IRC server you want your hubot to connect to. If the IRC server doesn't require a password, this can be omitted. Make a note of it if required.

=item HUBOT_IRC_REALNAME

Your realname on IRC server.

=item HUBOT_IRC_ENABLE_SSL

using L<AnyEvent::IRC::Connection> C<enable_ssl> at connect.

requires L<Net::SSLeay>.

=back

=head1 SEE ALSO

L<https://github.com/github/hubot/wiki/Adapter:-IRC>

=head1 AUTHOR

Hyungsuk Hong <hshong@perl.kr>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Hyungsuk Hong.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
