#!/usr/bin/perl -w

use warnings;
use strict;

use POE;
use POE::Component::IRC;

use constant ALPHABET =>
    'A' x 176 .
    'B' x 167 .
    'C' x 251 .
    'D' x 136 .
    'E' x 104 .
    'F' x 101 .
    'G' x 91 .
    'H' x 107 .
    'I' x 105 .
    'J' x 30 .
    'K' x 30 .
    'L' x 89 .
    'M' x 146 .
    'N' x 53 .
    'O' x 50 .
    'P' x 195 .
    'Q' x 13 .
    'R' x 103 .
    'S' x 273 .
    'T' x 132 .
    'U' x 20 .
    'V' x 41 .
    'W' x 71 .
    'X' x 1 .
    'Y' x 11 .
    'Z' x 6;

#sub CHANNEL () { "#fuck_the_world" }
#sub CHANNEL () { "#poopakro" }
#sub CHANNEL () { "#spidercheese" }
sub CHANNEL () { "$ARGV[0]" }
#sub CHANNEL () { "#fantasyfootball" }
sub KICK_RETRY () { 10 } 					# attempt to rejoin CHANNEL _X_ times (10)
sub NICKNAME () { "aemp1re" } 

# scores file
my $scoresfile = '/www/mfreeman/highscores.txt';

my @helpMsg = (	"Welcome to aKKKro 2004!",
				"To start a normal game type: !start [number of rounds]");
my @startGameText = (	"Welcome to aKKKro 2004!",
						"A new game will start in 20 seconds.");

my $B = chr(2);				#bold irc
my $numOfHighScores = 5;	#number of high scores to show

my (%namelist, %votes, %voteIds, %votedFor, %entries, %scores, %highscores);
my ($currentRound,$state);
my $numOfRounds = 8;
my $startGameCmd = "!start";
my (@akro);

my $DEBUG = 0;

my %parameters = ( 
	Nick => NICKNAME, 
	Username => 'aKKKro - bye leku',
	Ircname  => 'akro 2004',
	Server   => 'irc.undernet.org',
	Port     => '6667',
);

# Create the component that will represent an IRC network.
POE::Component::IRC->new("akro");

# Create the bot session.  The new() call specifies the events the bot
# knows about and the functions that will handle those events.
POE::Session->new
  ( _start 				=> \&bot_start,
    irc_001    			=> \&on_connect,
	irc_353				=> \&on_names,
	irc_351				=> \&ctcp_version,
    irc_public 			=> \&on_public,
	irc_invite 			=> \&irc_invite,
	irc_quit	 		=> \&on_quit,
	irc_error 			=> \&reconnect,
	irc_socketerr 		=> \&reconnect,
	irc_disconnected 	=> \&reconnect,
	irc_part			=> \&on_part,
	irc_kick			=> \&on_kick,
	irc_join			=> \&on_join,
	irc_nick			=> \&on_nick,
#	irc_kick 			=> \&irc_kick,			#program this to only care if we get kicked or not
	irc_mode 			=> \&irc_mode,			#check to see if we were banned
	irc_msg 			=> \&irc_msg,			#when we get a message
	irc_ctcp_version 	=> \&ctcp_version,
	new_game 			=> \&new_game,			#start new game
	abort_game 			=> \&abort_game,		#ABORT!
	start_round 		=> \&start_round,		#start a new round
	akro_vote 			=> \&akro_vote,			#vote
	results 			=> \&akro_results,			#show results
	game_over 			=> \&akro_game_over,			#FATALITY
	highscores 			=> \&list_highscores,	#show high scores	
	ten_seconds			=> \&ten_seconds,
	autoping 			=> \&auto_ping,			#ping the server.. keep connection alive
	_default => sub {
		return unless $DEBUG;
	    my ( $event, $args ) = @_[ ARG0 .. $#_ ];
		print "unhandled $event\n";

		my $arg_number = 0;
		foreach (@$args) {
			print "  ARG$arg_number = ";
			if ( ref($_) eq 'ARRAY' ) {
				print "$_ = [", join ( ", ", @$_ ), "]\n";
			}
		else {
			print "'$_'\n";
		}
		$arg_number++;
 	}
    return 0;    # Don't handle signals.
	},
);

# The bot session has started.  Register this bot with the "akro"
# IRC component.  Select a nickname.  Connect to a server.
sub bot_start {
    my $kernel  = $_[KERNEL];
    my $heap    = $_[HEAP];
    my $session = $_[SESSION];

	$heap->{count} = 0;					# counter for kick retry

    $kernel->post( akro => register => "all" );

	$kernel->post( akro => connect => \%parameters );
}

sub on_join {
	my ($nick, $address) = $_[ARG0] =~ /(.+?)!(.+)/;
	$namelist{$nick} = $address;
}

sub on_quit {
	my ($nick, $address) = $_[ARG0] =~ /(.+?)!(.+)/;
	delete $namelist{$nick}
}

sub on_part {
	my ($nick, $address) = $_[ARG0] =~ /(.+?)!(.+)/;
	delete $namelist{$nick};
}

sub on_names {
	%namelist = ();
	my $namelist = $_[ARG1];
	my @namelist;
	$namelist =~ s/^[^:]+://;
	$namelist =~ s/^\s+|\s+$//g;
	$namelist =~ tr/+//d;
	$namelist =~ tr/@//d;
    
	@namelist = split(/\s/, $namelist);
	foreach (@namelist) {
		$namelist{$_} = 1;
	}
}

# If we get kicked try to rejoin
sub on_kick {
	my( $kernel, $heap, $arg2 ) = @_[KERNEL, HEAP, ARG2];
	my ($nick, $address) = $arg2 =~ /(.+?)!(.+)/;
	
	delete $namelist{$nick} if $nick;

	$heap->{count}++;

	# only keep retrying so many times..
	if ($heap->{count} > KICK_RETRY) {
		print "Tried to re-join channel " . KICK_RETRY . " times: FAILED\n";
		$heap->{count} = 0;
	}

    $kernel->post( akro => join => CHANNEL );
}

sub on_nick {
	my ($old, $address) = $_[ARG0] =~ /(.+?)!(.+)/;
	my $new = $_[ARG1];
	$namelist{$new} = $namelist{$old};
	delete $namelist{$old};
}

# The bot has successfully connected to a server.  Join a channel.
sub on_connect {
	my ($kernel,$heap) = @_[KERNEL, HEAP];
    $kernel->post( akro => join => CHANNEL );
	#foreach (@helpMsg) {
	#	$kernel->post( akro => privmsg => CHANNEL, $_);
	#}

	$heap->{sean_traffic} = 1;
	$kernel->delay( autoping => 300 );
}

# Reconnct to the server if we get disconnected.
sub reconnect {
	my $kernel = $_[KERNEL];
	print "RECONNECT: $_[ARG0]\n";
	$kernel->delay( autoping => undef );
	$kernel->delay( connect  => 60 );
}

# If we get invited to CHANNEL, join it
sub irc_invite {
	my ($kernel, $heap, $who, $channel) = @_[KERNEL, HEAP, ARG0, ARG1];

	print "IRC_INVITE: received invite from $who to $channel\n";

    $kernel->post( akro => join => CHANNEL ) if ($channel eq CHANNEL);

}
# ctcp_version
sub ctcp_version {
	my ( $kernel, $heap, $who ) = @_[ KERNEL, HEAP, ARG0 ];

   	my $nick = ( split /!/, $who )[0];
	print "CTCP_VERSION: $who $nick\n";
	
	$kernel->post( akro => notice => $nick, "aKKKro 2004");
}
	
# auto_ping
sub auto_ping {
	my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];

	$kernel->post( akro => userhost => NICKNAME )
		unless $heap->{seen_traffic};
	$heap->{seen_traffic} = 0;
	$kernel->delay( autoping => 300 );
}

sub irc_mode {
	my $mode = $_[ARG0];

	print "IRC_MODE: $mode\n";
}

# The bot has received a private message.
sub irc_msg {
	my ( $kernel, $heap, $who, $where, $msg) = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2 ];

	print "IRC_MSG: $who $msg\n";
    my $nick = ( split /!/, $who )[0];

	unless(grep(/\Q$nick\E/, keys %namelist) != 0) {
			$kernel->post( akro => notice => $nick, qw(We have to be in the same channel buddy));
		return;
	}
	
	return if(handle_commands($kernel, $nick, $msg));

	if ($state eq 'entering' && grep(/\Q$nick\E/, keys %namelist) != 0) {
		if(&validate($msg)) {
			$entries{$nick} = $msg;
			$kernel->post( akro => privmsg => $nick, qw(Entry Accepted));
		} else {
			$kernel->post( akro => privmsg => $nick, qw(Your entry is Shite!));
		}
	} elsif ($state eq 'voting') {
		if($msg >= 1 && $msg <= (keys %entries) && $nick ne $voteIds{$msg}) {
			$votedFor{$nick} = $msg;
			$kernel->post( akro => privmsg => $nick, qw(Vote Accepted));
		} elsif ($voteIds{$msg} eq $nick) {
			$kernel->post( akro => privmsg => $nick, qw(Cheater));
		} else {
			$kernel->post( akro => privmsg => $nick, qw(Vote Invalid));
		}
	}
}

sub validate($) {
	my $input = shift;

	$input =~ s/^\s+|\s+$//g;
	my @checkArray = $input =~ /^\s*[()]*(.)?[^\s]*\s*[()]*(.)?[^\s]*\s*[()]*(.)?[^\s]*\s*[()]*(.)?[^\s]*\s*[()]*(.)?[^\s]*\s*$/;

	for(my $i = 0;$i < 5;$i++) {
		return 0 unless(uc($checkArray[$i]) eq uc($akro[$i]));
	}
	return 1;
}

sub handle_commands {
	my($kernel, $nick, $msg) = @_;
	
	my $arg;
	if ($msg eq "!quit") {
		if ($nick eq 'leku-') {
			$kernel->post('akro', 'quit', "See ya fellas");	
#			$kernel->shutdown;
			exit;
		}
	}
	elsif ($msg eq "help") {
		foreach (@helpMsg) {
			$kernel->post( akro => privmsg => $nick, $_);
		}
	}
	elsif ($msg =~ /^\Q$startGameCmd\E\s?(\d*)$/i) {
		my $rounds = $1;
		if ($rounds) {
			if ( $rounds > 0 ) {
				$numOfRounds = $rounds;
			}
		} else {
			$numOfRounds = 6;
		}
		
		$kernel->yield('new_game');
		return 1;
	}

	elsif ($msg eq "!abort") {
		if ($nick eq 'leku-') {
			$kernel->yield('abort_game');
			return 1;
		}
		return 1;
	}
		
}

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

	$state = 'waiting';
	$kernel->post( akro => privmsg => CHANNEL, "$B Game Aborted!");
	&reset_all;
	%scores = ();
	$heap->{started} = 0;
	$kernel->alarm_remove_all;
	return;
}
	
		
sub new_game {
	my($kernel,$heap) = @_[KERNEL, HEAP];

	print "Starting new game\n";

	if ($heap->{started}) {
		$kernel->post( akro => privmsg => CHANNEL, qw(Game already started));
		return;
	}

	$heap->{started} = 1;

	%scores = ();
	$currentRound = 1;
	foreach (@startGameText) {
		$kernel->post( akro => privmsg => CHANNEL, $_ );
	}
	$kernel->delay('start_round', 20);
	return;
}

sub ten_seconds {
	my $kernel = $_[KERNEL];
	$kernel->post( akro => privmsg => CHANNEL, "$B You have 10 seconds left!");
}

# The bot has received a public message.  Parse it for commands, and
# respond to interesting things.
sub on_public {
    my ( $kernel, $heap, $who, $where, $msg ) = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2 ];

    my $nick = ( split /!/, $who )[0];
    my $channel = $where->[0];

    my $ts = scalar localtime;
    print " [$ts] <$nick:$channel> $msg\n";

}

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

	&reset_all;
	&make_akro;
	my $seconds = 15 * @akro;
	$kernel->post( akro => privmsg => CHANNEL, "Round starting $currentRound of $numOfRounds");
	$kernel->post( akro => privmsg => CHANNEL, "Akro is $B@akro$B, /msg me your answer");
	$kernel->post( akro => privmsg => CHANNEL, "You have $seconds seconds");
	$state = 'entering';
	$kernel->delay('akro_vote', $seconds);
	$kernel->delay('ten_seconds', $seconds - 10);
	return;
}

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

	$state = 'waiting';
	my $voted;

	$kernel->post( akro => privmsg => CHANNEL, "$B Voting Results: ");
	foreach (keys %votedFor) {
		$votes{$voteIds{$votedFor{$_}}}++;
		$scores{$voteIds{$votedFor{$_}}} += (8/$numOfRounds) if ($votedFor{$voteIds{$votedFor{$_}}});
	}

	foreach (keys %entries) {
		$votes{$_} = 0 unless($votes{$_});
		$scores{$_} = 0 unless($scores{$_});
		$voted = '';
		$voted = '(No vote, No Points)' if(!$votedFor{$_} && $votes{$_});
		$kernel->post( akro => privmsg => CHANNEL, "$B $_ -> $B" . $entries{$_}
			. " (Votes: " . $votes{$_} . " Score: " . (sprintf("%.3f", $scores{$_}) + 0) . ")
			$voted");
	}

	$currentRound++;

	if ($currentRound <= $numOfRounds) {
		$kernel->post( akro => privmsg => CHANNEL, "Round $currentRound of $numOfRounds will start in 20 seconds.");
		$kernel->delay('start_round', 20);
		return;
	} else {
		$kernel->yield('game_over');
		return;
	}
}
	
sub akro_game_over {
	my $kernel = $_[KERNEL];

	$state = 'waiting';

	$kernel->post( akro => privmsg => CHANNEL, "$B Game Over!");

	my $nick;
	my @high;
	my $last = 0;

	foreach $nick (keys %scores) {
		if($scores{$nick} > $last) {
			$last = $scores{$nick};
			@high = ($nick); 	
		} elsif ($scores{$nick} == $last) {
			push(@high, $nick);
		}
	}
	
	if (@high > 1) {
		$kernel->post( akro => privmsg => CHANNEL, "Tie Game!") if (@high > 1);
		$kernel->post( akro => privmsg => CHANNEL, "The Winners are: ");
	} else {
		$kernel->post( akro => privmsg => CHANNEL, "The Winner is: ");
	}

	foreach $nick (@high) {
		$last = $scores{$nick};
		$kernel->post( akro => privmsg => CHANNEL, "$B $nick $B (Score: $B $last $B)");
	}

	$kernel->alarm_remove_all;
	&save_highscores;
	$kernel->yield('highscores');
	$kernel->post( akro => privmsg => CHANNEL, "Thats it..");
	return;
}

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

	$state = 'waiting';
	my $i = 1;

	$kernel->post( akro => privmsg => CHANNEL, "$B Time's up!");
	if ((keys %entries) < 3) {
		$kernel->post( akro => privmsg => CHANNEL, "$B Round aborted! $B (Fewer than 3 entries)");
		$currentRound++;
		if ($currentRound <= $numOfRounds) {
			$kernel->yield('start_round');
			return;
		} else {
			$kernel->yield('game_over');
			return;
		}
	}
	
	my @keys = keys %entries;
	shuffle(\@keys);
	foreach(@keys) {
		$voteIds{$i} = $_;
		$kernel->post( akro => privmsg => CHANNEL, "$B $i:$B " . $entries{$_});
		$i++;
	}	

	$kernel->post( akro => privmsg => CHANNEL, "/msg me your votes, you have 30 seconds.");
	$kernel->delay( 'results', 30);
	$state = 'voting';
	return;
}

sub shuffle {
	my $array = shift;
	my $i;

	for ($i = @$array; --$i; ) {
		my $j = int rand($i+1);
		@$array[$i,$j] = @$array[$j,$i];
	}
}

sub make_akro {
	for(my $i = 1;$i <= int(rand 3) + 3;$i++) {
		push(@akro, substr(ALPHABET, int(rand length(ALPHABET)), 1));
	}
}

sub reset_all {
	%votes 		= ();
	%voteIds	= ();
	%entries	= ();
	%votedFor	= ();
	$state		= 'waiting';
	@akro 		= ();
}

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

	my %highscores = ();
	open(SCORES, "$scoresfile") or 		
		print "Couldn't open $scoresfile: $!\n";
		#$kernel->post( akro => privmsg => CHANNEL, qw(Couldn't open scores file) );
	while(<SCORES>) {
		$highscores{$2} .= "$1," if /([^:]+):(\d+\.?\d*)/;
	}
	close(SCORES);

	foreach (keys %scores) {
		$highscores{$_} = sprintf("%.3f", $scores{$_}) if($scores{$_} > $highscores{$_} || !$highscores{$_});
	}

	open(SCORES, ">$scoresfile") or
		$kernel->post( akro => privmsg => CHANNEL, qw(Couldn't write to scores file) );
	foreach (sort keys %highscores) {
		print SCORES "$_:" . $highscores{$_} . "\n";
	}
	close(SCORES);
}

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

	my %highscores = ();
	my $count = 1;
	my(@nicklist);
	open(SCORES, "$scoresfile") or die "Couldn't open $scoresfile: $!\n";
		#$kernel->post( akro => privmsg => CHANNEL, qw(Couldn't open scores file) );
	while(<SCORES>) {
		$highscores{$2} .= "$1," if /([^:]+):(\d+\.?\d*)/;
	}
	close(SCORES);

	$kernel->post( akro => privmsg => CHANNEL, "$B Top $numOfHighScores High Scores");
	foreach (sort {$b <=> $a} keys %highscores) {
		chop $highscores{$_};
		@nicklist = split(/,/, $highscores{$_});
		$kernel->post( akro => privmsg => CHANNEL, "$B Score: $_$B (" . join(", ", @nicklist) . ")");
		last if ($count >= $numOfHighScores);
		$count++;
	}
}

# Run the bot until it is done.
$poe_kernel->run();
exit 0;
