#!/usr/bin/perl -w

my %config = ('debug'     => 1,
              'session'   => "AutoPabs",
              'nick'      => "Pabs-O-Matic",
              'username'  => "pabsomatic",
              'server'    => "irc.openprojects.net",
              'port'      => "6667",
              'ircname'   => "Auto pabs",
              'channels'  => "#poseidon,#joe",
              'cmd_dir'   => "$ENV{'HOME'}/.autopabs/commands",
              'acl_file'   => "$ENV{'HOME'}/.autopabs/access_control_list"
             );

# default commands (these can be overridden with the command file
# direcotry 
my %commands = ('echo'    => \&cmd_echo,
                 'hi'     => \&cmd_hi,
               );

# access controls (for fuckers like giblet ;) )
my %acl = ( 'admins'      => "pabs\@gw-uunet:mikes\@koudelka.res.cmu.edu",
            'default'     => "-",
            'cmd_join'    => "+pabs",
            'cmd_leave'   => "+pabs",
            'cmd_reload'  => "+pabs"
          );

use strict;
use Fcntl ':flock';
use POE;
use POE::Component::IRC;

# set up basic irc object
POE::Session->create( 'inline_states'   =>  {
                        '_start'        => \&tb_start,
                        'increment'     => \&tb_increment,
                        '_stop'         => \&tb_stop,

                        'irc_connected' => \&tb_connected,
                        'irc_001'       => \&tb_ready,
                        'irc_public'    => \&tb_public,
                        'irc_msg'       => \&tb_priv,
                        #'irc_invite'    => \&tb_invite,
                      }
                    );
  
# load custom commands
&load_commands();
# load access control list
&load_acls();

$poe_kernel->run();
exit 0;

################
# poe handlers #
################

sub tb_start {
  my ($kernel,$heap,$session) = @_[KERNEL, HEAP, SESSION];

  POE::Component::IRC->new($config{'session'}) or
    die "Couldn't instantiate POE::Component::IRC.";

  $kernel->post($config{'session'}, 'register', qw(all));
  $kernel->post($config{'session'},
                'connect', 
                { 'Nick'      => $config{'nick'},
                  'Server'    => $config{'server'},
                  'Port'      => $config{'port'},
                  'Username'  => $config{'username'},
                  'Ircname'   => $config{'ircname'} 
                } );
}

sub tb_increment {

}

sub tb_stop {
}


################
# irc handlers #
################

sub tb_connected {
  print "Connected to $config{'server'}.\n";
}

sub tb_ready {
  my $kernel = $_[KERNEL];
  my @chans  = split /,/, $config{'channels'};

  $kernel->post($config{'session'}, "join", "$_") foreach (@chans);
}

sub tb_public {
  my $kernel = $_[KERNEL];
  my ($usermask, $chan_ref, $msg) = @_[ARG0, ARG1, ARG2];
  my @chans = @$chan_ref;
  my $user = get_nick_from_mask($usermask);

  # print the message
  if ($config{'debug'}) {
    print "<$user";
    if ($chan_ref) {
      print " $_" foreach @chans;
    }
    print "> $msg\n";
  }

  if ($msg =~ /^'([\w-]+)/ or $msg =~ /^$config{'nick'}: ?([\w-]+)/) {
    my $lccmd = lc $1;
    next unless &check_command_acl($lccmd, $usermask);
    my $cmd_ref = $commands{$lccmd};
    if (my $cmd_ref = $commands{$lccmd}) {
      &$cmd_ref($kernel, $config{'session'}, $user, $chan_ref, $msg);
    } elsif ($msg =~ /(join|leave)\s+(#?\w+)/i) {
      my $cmd = $1;
      my $chan = $2;

      if ($cmd =~ /join/i) {
        $kernel->post($config{'session'}, 'join', $chan);
      } elsif ($cmd =~ /leave/i) {
        $kernel->post($config{'session'}, 'part', $chan);
      }
    }
  }
}

sub tb_priv {
  my $kernel = $_[KERNEL];
  my ($usermask, $chan_ref, $msg) = @_[ARG0, ARG1, ARG2];
  my @chans = @$chan_ref;
  my $user = get_nick_from_mask($usermask);

  # print the message
  if ($config{'debug'}) {
    print "[$user";
    if ($chan_ref) {
      print " $_" foreach @chans;
    }
    print "] $msg\n";
  }

  if ($msg =~ /^(\w+)/) {
    my $lccmd = lc $1;
    next unless &check_command_acl($lccmd, $usermask);
    my $cmd_ref = $commands{$lccmd};

    if (my $cmd_ref = $commands{$lccmd}) {
      &$cmd_ref($kernel, $config{'session'}, $user, $chan_ref, $msg);
    } elsif ($msg =~ /(join|leave)\s+(#\w+)/i) {
      my $cmd = $1;
      my $chan = $2;

      if ($cmd =~ /join/i) {
        $kernel->post($config{'session'}, 'join', $chan);
      } elsif ($cmd =~ /leave/i) {
        $kernel->post($config{'session'}, 'part', $chan);
      }
    }
  }
}

sub tb_invite {
  my ($kernel,$chan) = @_[KERNEL,ARG1];
  $kernel->post($config{'session'}, 'join', $chan);
}

####################
# command handlers #
####################
sub cmd_echo {
  my ($kernel, $session, $usermask, $chan_ref, $msg) = @_;
  my $user = &get_nick_from_mask($usermask);

  $kernel->post($config{'session'}, 'privmsg', $chan_ref, "$user: $msg\n");
}

sub cmd_hi {
  my ($kernel, $session, $usermask, $chan_ref, $msg) = @_;
  my $user = &get_nick_from_mask($usermask);

  $kernel->post($session, 'privmsg', $chan_ref, "$user: yo");
}

sub cmd_offended {
  my ($kernel, $session, $usermask, $chan_ref, $msg) = @_;
  my $user = &get_nick_from_mask($usermask);
  return "$user: offended is http://www.pablotron.org/offended/";

  $kernel->post($session, 'privmsg', $chan_ref,
    "$user: offended is http://www.pablotron.org/offended/");
}

######################
# misc utility funcs #
######################
sub get_nick_from_mask {
  my $user = shift;
  $user =~ s/!.*$//;
  return $user;
}

sub check_command_acl {
  my ($cmd, $usermask) = @_;
  my @admins = split /:/,$acl{'admins'};
  my $default = $acl{'default'};
  my @cmd_perms = ();

  # load per-command permissions
  @cmd_perms = split /:/, $acl{"cmd_$cmd"} if $acl{"cmd_$cmd"};

  foreach my $admin (@admins) {
    if ($usermask =~ /$admin/i) {
      print "ACL: rule=admin, cmd=$cmd, mask=$usermask, match=$admin : allow\n";
      return 1;
    }
  }

  foreach my $perm (@cmd_perms) {
    $perm =~ /^(.)(.*)$/;
    my $p = $1; my $u = $2;
    if ($usermask =~ /$u/i) {
      if ($p eq "+") {
        print "ACL: rule=perm, cmd=$cmd, mask=$usermask, match=$u : allow\n";
        return 1;
      } else {
        print "ACL: rule=perm, cmd=$cmd, mask=$usermask, match=$u : deny\n";
        return 0;
      }
    } 
  }

  # default rule
  my $result = ($default eq "+");
  my $r_str = "ACL: rule=default, cmd=$cmd, mask=$usermask, match=* : ";
  if ($result) {
    $r_str .= "allow";
  } else {
    $r_str .= "deny";
  }

  print "$r_str\n";
  return $result;
}

sub load_acls {
  my $lines = 0;
  my $count = 0;
  if (open FILE, $config{'acl_file'}) {
    while (my $line = <FILE>) {
      $lines++;
      next if $line =~ /^#/ or $line =~ /^$/;
      chomp $line;
      next unless $line =~ /(\w+)\s+(.+)$/;
      $acl{$1} = $2;
      $count++;
    }
    close FILE;

    print "Loaded $count rules ($lines lines) from file " .
          "\"$config{'acl_file'}\".\n";
  } else {
    warn "WARNING: Couldn't open ACL file.";
  }
}

sub load_commands {
  if (opendir DIR, $config{'cmd_dir'}) {
    while (my $cmdfile = readdir DIR) {
      next if $cmdfile =~ /^\./;
      unless (open FILE, "$config{'cmd_dir'}/$cmdfile") {
        warn "Couldn't read command file $config{'cmd_dir'}/$cmdfile:";
        next;
      }

      # read in full command
      my @lines = <FILE>;
      close FILE;

      # join command together;
      my $cmd = join "", @lines;
      $cmdfile = lc $cmdfile;
      eval "\$commands{\$cmdfile} = $cmd;"
        or warn "WARNING: Couldn't add command \"$cmdfile\": $!";
    }
    closedir DIR;
  } else {
    warn "WARNING: Couldn't open command directory.";
  }
}
