#!/usr/bin/perl

use Net::IRC;
use Socket;
use POSIX;

#use warnings;

########
### CONF
########
my %conf = ( "channel" => "#corporate" , 
	  "nick" 		=> "fiontard",
	  "server" 		=> "irc.freenode.net",
	  "ircname" 	=> "Oui mais ?",
	  "react_pub" 	=> "./react_pub",
	  "react_pubme" => "./react_pubme",
	  "react_priv" 	=> "./react_priv",
	  "admin"		=> "binarym",
	  "socket_file" => "/tmp/socks",
	  "debug" => 1
);

my (@quotes,%react_pub,%react_pubme,%react_priv);

sub pdebug {
	my $fmt = shift;
	my @args = @_;
	printf($fmt,@args) if($conf{'debug'});
}

sub give_me_a_quote {
	return($quotes[int(rand(@quotes))]);
}

############
### IRC INIT
############
sub on_connect {
	my $self = shift;
	if($conf{'channel'} ne "" ){
		&pdebug("Joining $conf{'channel'}...\n");
		$self->join("$conf{'channel'}");
	}
}

##
# Administrative functions
##


sub save_datas {
	my $cmd = shift;
	my $fname;
	my %datas = ( 
		$conf{'react_pub'} => \%react_pub ,
		$conf{'react_pubme'} => \%react_pubme,
		$conf{'react_priv'} => \%react_priv
	);
	if($cmd eq "save"){
		foreach $fname (keys(%datas)){
			open(FIC,">",$fname);
			map { print FIC $_." => ".${$datas{$fname}}{$_}."\n"; } keys(%{$datas{$fname}});
			close(FIC);
		}
	}
	if($cmd eq "load"){
		foreach $fname (keys(%datas)){
			map { undef(${$datas{$fname}}{$_}); } keys(%{$datas{$fname}});
			open(FIC,"<",$fname);
			while(chomp($line = <FIC>)) { 
				my ($pattern,$react) = split(/ => /,$line);
				$pattern = qr/$pattern/;
				${$datas{$fname}}{$pattern} = $react if($pattern ne "" && $react ne "");
					
			}
			close(FIC);
		}
	}
}
sub init_bot {
	&save_datas("load");
}

sub administrative {
	my $self = shift;
	my $event = shift;
	my $cmd = shift;
	my $arg = shift;
	
	my ($from) = ($event->nick);
	my @to = ($event->to);
	my $is_adm = 0;
	@admins = split(/:/,$conf{'admin'});
	foreach(@admins){
		if($from eq $_){ $is_adm=1; next; }
	}
	if(!$is_adm){
		$self->privmsg([@to], "$from: tu n'es pas admin");
		return;
	}
	if($cmd eq 'reload' || $cmd eq 'save'){
		&save_datas($cmd);
		$self->privmsg([@to],"!admin $cmd succès");
	}
	elsif($cmd eq 'adduser'){
		$conf{'admin'}.= ":".$arg;
		$self->privmsg([@to],"!admin $cmd succès");
	}
	elsif($cmd eq 'deluser'){
		map { delete($admins[$i]) if($admins[$i] eq $arg); } (0 .. $#admins);
		$self->privmsg([@to],"!admin $cmd succès");
	}
	elsif($cmd eq 'listuser'){
		$self->privmsg([@to],join(', ',@admins));
		$self->privmsg([@to],"!admin $cmd succès");
	}
	elsif($cmd eq 'bye'){
		$self->quit("salut");
		$self->privmsg([@to],"!admin $cmd succès");
		exit(0);
	}
	elsif($cmd eq 'help'){
		$self->privmsg([@to],"!admin (del|adduser) <user>");
		$self->privmsg([@to],"!admin (reload|save|bye|help)");
		$self->privmsg([@to],"!admin $cmd succès");
	}
	else{
		$self->privmsg([@to],"(admin) $cmd commande inconnue, tape !admin help");
	}
	&pdebug("==== ADMINISTRATIF TASK DONE: $cmd $args\n");
}

##
# Reaction functions
##
sub reaction {
	my $self = shift;
	my $event = shift;
	
	my @to = ($event->to);
	my ($from) = ($event->nick);
	my ($msg) = ($event->args);

	my ($href,$pubme);
	
	# Select the good hash
	if($msg  =~ /^$conf{'nick'}:?(.+)$/ && ($pubme = 1) ){
		$href = \%react_pubme;
	}else{
		$href = \%react_pub;
	}
	my $said=0;
	foreach $pattern (keys(%$href)){
		if( $msg =~ $pattern ){
			$self->privmsg([@to],($pubme ? $from.': ' : '').$$href{$pattern});
			$said = 1;
			&pdebug("%-10s to %-10s said \%s\n",$conf{'nick'},join(',',@to),$$href{$pattern});
			next;
		}
	}
}

sub reaction_config {
	my $self = shift;
	my $event = shift;
	
	my @to = ($event->to);
	my ($from) = ($event->nick);
	my ($msg) = ($event->args);

	($domain,$cmd,$args) = $msg =~ /^!react\s+(pub|pubme|priv)\s+(\w+)\s?(.+)?$/;
	if($domain eq "" || $cmd eq ""){
		$self->privmsg([@to],"!react: Erreur de syntaxe");
		return;
	}

	# Get correct hash
	$hashref = ($domain eq "pub" ? \%react_pub : ($domain eq "pubme" ? \%react_pubme : \%react_priv));

	# Handle command
	if($cmd eq 'list'){
		foreach $pattern (keys(%{$hashref})){
			$self->privmsg([@to],$pattern." => ".$$hashref{$pattern});
		}
	}
	elsif($cmd eq 'add'){
		my ($pattern,$value) = $args =~ /^(.+) => (.+)$/;
		unless($pattern ne "" && $value ne ""){
			$self->privmsg([@to],"!react: Erreur de syntaxe");
			return;
		}
		# Compile re and check its validity
		eval { $pattern = qr/$pattern/};
		if($@ ne ""){
			$self->privmsg([@to],"Invalid regexp: $@");
			return;
		}
		$$hashref{$pattern} = $value;
	}
	elsif($cmd eq 'del'){
		if(!defined(${%{$hashref}}{$args}) || ${%{$hashref}}{$args} eq ""){
			$self->privmsg([@to],'!react: Aucun motif de ce type: "'.${%{$hashref}}{$args}.'"'.${$hashref{$args}}.'"');
			return;
		}
		else{
			delete $$hashref{$args};
		}
	}
	$self->privmsg([@to],'!react: succés !');
}

sub print_help {
	my $self = shift;
	my $event = shift;

	my ($msg) = ($event->args);
	my ($from)  = ($event->nick);
	my @to	= $event->to;

	@help = ( 
		['!admin help','Affiche les commandes dispos en admin'],
		['!react (pub|pubme|priv) list','Liste les reactions pour le type donné'], 
		['!react (pub|pubme|priv) add <pattern> => <reaction>','Ajoute une réaction pour les messages matchant le motif'], 
		['!react (pub|pubme|priv) del <pattern>','Supprime la réaction associée au motif'], 
		['!bashfr','va chercher une quote sur bashfr']
	);
	foreach $array (@help) {
		$self->privmsg([ @to ],sprintf('%s:',$$array[0])); 
		$self->privmsg([ @to ],sprintf('%-20s%s',' ',$$array[1])); 
		sleep(1); 
	}
}

# Private msg handler
sub on_msg {
}

sub on_public {
	my $self = shift;
	my $event = shift;
	
	my ($msg) = ($event->args);
	my ($from)  = ($event->nick);
	my @to	= $event->to;
	
	&pdebug("%-10s to %-10s said \%s\n",$from,@to,$msg);

	if( $msg =~ /^!react/){
		&reaction_config($self,$event);
	}
	elsif( $msg =~ /^!help$/){ 
		&print_help($self,$event); 
	}
	elsif($msg =~ /^!admin/){
		$msg =~ /^!admin (\w+)\s*(\w+)?$/;
		my ($cmd,$arg) = ($1,$2);
		&administrative($self,$event,$cmd,$arg);
	}
	elsif( $msg =~ /^!bashfr$/){
		my ($quote_id,$note);
		open(WGET,"lynx --dump http://www.bashfr.org/?sort=random2 |");
		for($i=0; $i<2; $i++){
			while( $line !~ /^\s*#/){
				chomp($line = <WGET>);
			      	($quote_id,$note) = $line =~ /^\s*#\[\d+\]\s*(\d+)\s*\[\d+\]\s*\(\+\)\s*(\d+)/;
			}
			$line ="";
		}
		$self->privmsg([ @to ],"=== http://bashfr.org/?$quote_id (score: $note) ===");
		while($line  !~ /^\s*#/){
			chomp($line = <WGET>);
			sleep(1);
			$self->privmsg([ @to ],"$line") if($line !~ /^\s*#/);
		}
		close(WGET);
		undef($line);
	}

	else{
		&reaction($self,$event);
	}
}

# Disconnected ?
sub on_disconnect {

}

# On some special event at init
sub on_init {
	my ($self, $event) = @_;
	my (@args) = ($event->args);
	&pdebug("*** @args\n");
}

# Irc init part
&init_bot();
$irc = new Net::IRC;

# Open PF_UNIX socket
if( -e $conf{"socket_file"}) {
	unlink($conf{"socket_file"}) or die "unable to remove $conf{'socket_file'}";
}
socket(Server, AF_UNIX, SOCK_DGRAM, 0) 
	or die "socket: $!";
bind(Server,sockaddr_un($conf{'socket_file'})) 
	or die "bind: $!";

&pdebug("Connection on $conf{'server'} as $conf{'nick'}\n");

$con = $irc->newconn( Nick => $conf{'nick'}, Username => $conf{'nick'}, Server => $conf{'server'}, Ircname => $conf{'ircname'})
	or $irc->flush_output_queue();

$con->add_global_handler('msg',\&on_msg)
	or $irc->flush_output_queue();
$con->add_global_handler('public',\&on_public)
	or $irc->flush_output_queue();
$con->add_global_handler('on_connect',\&on_connect)
	or $irc->flush_output_queue();
$con->add_global_handler([251, 252, 253, 254, 302, 255], \&on_init)
	or $irc->flush_output_queue();

$con->join("$conf{'channel'}");
$con->privmsg($conf{'channel'},"Salut !");

while(1){
	my ($ready,$timeout,$rin) = ();
	vec($rin,fileno(Server),1) = 1;
	($ready,$timeout) = select($rin,undef,undef,1);
	while($ready){
		$datas = <Server>;
		$con->privmsg($conf{'channel'},$datas);
		vec($rin,fileno(Server),1) = 1;
		($ready,$timeout) = select($rin,undef,undef,1);
	}
	$irc->do_one_loop();
}

close(Server);
unlink($conf{'socket_file'});
