package Vote; # command = vote

use strict;
use warnings;

use Storable;



# Tableau des votes
# On les stocke de telle manière :
# "#chan id" --> "chan" --------> chan/reply name
#         |----> "id" ----------> vote id
#         |----> "added date" --> timestamp
#         |----> "question" ----> yes/no question to answer
#         |----> "voters" ------> [user@host voters]
#         |----> "results" -----> ("yes" => nb, "no" => nb)
#         `----> "author" ------> user@host
# ...
our %votes = ();

# Tableau des derniers votes ajoutés
# Format :
# "chan1" --> dernier vote id
# ...
our %last_added = ();




# ###
#  vote_main
# Permet de gérer des votes
# 
# Usage :
#  !vote new <question>
#  !vote del <id>
#  !vote list [#chan]
#  !vote see <id>
#  !vote [<id>] {yes|no}
# ###
sub vote_main
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	# Don't use in query, only on chans
	unless(substr($reply_to, 0, 1) eq '#')
	{
		$::displayer->sendto($reply_to, "Can't operate in query, sorry.");
		return 1;
	}
	
	if(defined($ref_params->[0]) && $ref_params->[0])
	{
		if($ref_params->[0] eq 'new')
		{
			return 1 if(new_vote($conn, $event, $reply_to, $ref_params));
		}
		elsif($ref_params->[0] =~ m'del(ete)?|remove')
		{
			return 1 if(del_vote($conn, $event, $reply_to, $ref_params));
		}
		elsif($ref_params->[0] eq 'list')
		{
			return 1 if(list_votes($conn, $event, $reply_to, $ref_params));
		}
		elsif($ref_params->[0] eq 'see')
		{
			return 1 if(see_vote($conn, $event, $reply_to, $ref_params));
		}
		else
		{
			return 1 if(vote($conn, $event, $reply_to, $ref_params));
		}
	}
	
	$ref_params->[0] = 'vote';
	Help::help_main($conn, $event, $reply_to, $ref_params);
} # Fin vote_main


# ###
#  new_vote
# Enregistre un nouveau vote
# ###
sub new_vote
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	# On enlève le 'new'
	shift @{$ref_params};
	
	unless(defined($ref_params->[0]) && $ref_params->[0])
	{
		$::displayer->sendto($reply_to, "Didn't find your question.");
		return 0;
	}
	
	my $lc_reply_to = lc $reply_to;
	my $id = get_next_id($lc_reply_to);
	my (undef, $vote_id) = split / +/, $id;
	my $question = join(' ', @{$ref_params});
	my $full_user = (lc $event->{'nick'}).'!'.(lc $event->{'userhost'});
	
	my %new_vote = (
		'chan'       => $lc_reply_to,
		'id'         => $vote_id,
		'added date' => time(),
		'question'   => $question,
		'voters'     => [],
		'results'    => {
			'yes' => 0,
			'no'  => 0
		},
		'author'     => $full_user
	);
	
	$votes{$id} = \%new_vote;
	$last_added{$lc_reply_to} = $vote_id;
	
	my $add_date = localtime($votes{$id}->{'added date'});
	$::displayer->sendto($reply_to, "Vote \x0304[\x0303id #$vote_id\x0304]\x03 added on $add_date");
	$::logger->info("New vote: '$question', id='$id', added by $full_user");
	
	return 1;
} # Fin new_vote


# ###
#  del_vote
# Supprime un vote
# ###
sub del_vote
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	# On enlève le 'del' ou autre
	shift @{$ref_params};
	
	unless(defined($ref_params->[0]))
	{
		$::displayer->sendto($reply_to, "Didn't find the id.");
		return 0;
	}
	
	my $lc_reply_to = lc $reply_to;
	my $vote_id = $ref_params->[0];
	if($ref_params->[0] =~ /^#?(\d+)$/)
	{
		$vote_id = $1;
	}
	my $id = $lc_reply_to.' '.$vote_id;
	my $full_user = (lc $event->{'nick'}).'!'.(lc $event->{'userhost'});
	
	# On vérifie que c'est bien l'auteur qui demande la suppression du vote
	if(defined($votes{$id}) && $votes{$id}->{'author'} eq $full_user)
	{
		return real_del_vote($id, $lc_reply_to);
	}
	
	$::displayer->sendto($reply_to, "Can't find this vote on this chan or you're not authorized.");
	return 1;
} # Fin del_vote


# ###
#  real_del_vote
# S'occupe de vraiment supprimer le vote et d'émettre le résultat à l'utilisateur
# ###
sub real_del_vote
{
	my ($id, $reply_to) = @_;
	
	my $question = $votes{$id}->{'question'};
	my $yes = $votes{$id}->{'results'}->{'yes'};
	my $no  = $votes{$id}->{'results'}->{'no'};
	
	delete $votes{$id};
	$::displayer->sendto($reply_to, "Vote for '$question' deleted");
	$::displayer->sendto($reply_to, "Final results: yes = \x0303$yes\x03 - no = \x0304$no\x03");
	$::logger->info("Vote removed: '$question', id='$id'. Results: yes=$yes, no=$no");
	
	return 1;
} # Fin real_del_vote


# ###
#  list_votes
# Liste les votes en cours sur le chan actuel
# ###
sub list_votes
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	shift @{$ref_params};
	
	my $to_check_chan = lc $reply_to;
	if(defined($ref_params->[0]) && substr($ref_params->[0], 0, 1) eq '#')
	{
		$to_check_chan = lc $ref_params->[0];
	}
	
	my %current_votes = ();
	foreach my $id (sort keys %votes)
	{
		if($votes{$id}->{'chan'} eq $to_check_chan)
		{
			$current_votes{$id} = $votes{$id};
		}
	}
	
	my @curr_votes = sort keys %current_votes;
	my $msg = "";
	
	if(@curr_votes)
	{
		foreach my $id (@curr_votes)
		{
			$msg .= "\x0307{#" . $votes{$id}->{'id'} . "\x03";
			$msg .= " Added by \x0304" . $votes{$id}->{'author'};
			$msg .= "on \x0302" . localtime($votes{$id}->{'added date'});
			$msg .= "\x0307}\x03 ";
		}
	}
	else
	{
		$msg = "There's currently no vote on $to_check_chan";
	}
	$::displayer->sendto($reply_to, $msg);
	
	return 1;
} # Fin list_votes


# ###
#  see_vote
# Affiche un vote particulier
# ###
sub see_vote
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	# On enlève le 'see'
	shift @{$ref_params};
	
	unless(defined($ref_params->[0]))
	{
		$::displayer->sendto($reply_to, "Didn't find the id.");
		return 0;
	}
	
	my $lc_reply_to = lc $reply_to;
	my $vote_id = $ref_params->[0];
	my $id = $lc_reply_to." ".$vote_id;
	
	# On vérifie que le vote existe
	if(defined($votes{$id}))
	{
		my $msg = "Vote \x0304[\x0303id #$vote_id\x0304]\x03: ";
		$msg .= $votes{$id}->{'question'};
		$msg .= " -- \x0302Added on " . localtime($votes{$id}->{'added date'});
		
		$::displayer->sendto($reply_to, $msg);
		
		# Public/private?
# 		if(lc $event->{'nick'} eq $votes{$id}->{'author'})
		{
			my $yes = $votes{$id}->{'results'}->{'yes'};
			my $no  = $votes{$id}->{'results'}->{'no'};
			$msg = "Current results: yes = \x0303$yes\x03 - no = \x0304$no\x03";
			
			$::displayer->sendto($reply_to, $msg);
		}
	}
	else
	{
		$::displayer->sendto($reply_to, "Can't find this vote. It has either expired or been deleted.");
	}
	
	return 1;
} # Fin see_vote


# ###
#  vote
# Enregistre le vote d'un votant
# ###
sub vote
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	my $lc_reply_to = lc $reply_to;
	my $vote_id = $last_added{$lc_reply_to};
	my $voter = $event->{'userhost'};
	
	my $code = real_vote(join(' ', @{$ref_params}), $lc_reply_to, $vote_id, $voter);
	
	if($code == 0)
	{
		$::displayer->sendto($reply_to, "You've already answered to this vote \x0304[\x0303id #$vote_id\x0304]\x03");
	}
	elsif($code == 1)
	{
		$::displayer->sendto($reply_to, "Your vote has been added.");
	}
	else
	{
		$::displayer->sendto($reply_to, "Can't find your answer.");
	}
	
	return 1;
} # Fin vote


# ###
#  real_vote
# Enregistre vraiment le vote d'un votant
# ###
sub real_vote
{
	my ($params, $chan, $vote_id, $voter) = @_;
	
	my $id = $chan." ".$vote_id;
	print Data::Dumper->Dump([$votes{$id}, $params], [qw(vote_struct params)]);
	
	if($params =~ /^(?:y(?:es)?|oui)$/i)
	{
		if(can_vote($id, $voter))
		{
			$votes{$id}->{'results'}->{'yes'} += 1;
			push @{$votes{$id}->{'voters'}}, $voter;
			return 1;
		}
		
		return 0;
	}
	elsif($params =~ /^n(?:o(?:n)?)?$/i)
	{
		if(can_vote($id, $voter))
		{
			$votes{$id}->{'results'}->{'no'} += 1;
			push @{$votes{$id}->{'voters'}}, $voter;
			return 1;
		}
		
		return 0;
	}
	elsif($params =~ /^#?(\d+)\s+(.+)$/i)
	{
		$vote_id = $1;
		my $answer = $2;
		$id = $chan." ".$vote_id;
		
		if(can_vote($id, $voter))
		{
			return real_vote($answer, $chan, $vote_id, $voter);
		}
	}
	
	return -1;
} # Fin real_vote


# ###
#  can_vote
# Vérifie qu'un utilisateur n'a pas déjà voté
# ###
sub can_vote
{
	my ($id, $user) = @_;
	
	my $lc_user = lc $user;
	
	if(grep { $_ eq $lc_user } @{$votes{$id}->{'voters'}})
	{
		return 0;
	}
	
	$::logger->info("$user can vote for $id");
	return 1;
}


# ###
#  get_next_id
# Renvoie le prochain vote_id libre
# ###
sub get_next_id
{
	my $base = shift;
	
	my $cpt = 0;
	$cpt++ while(defined($votes{$base." ".$cpt}));
	
	return $base." ".$cpt;
} # Fin get_next_id


# ###
#  vote_help
# Affiche l'aide de la commande '!vote'
# ###
sub vote_help
{
	my ($conn, $event, $reply_to, $ref_params) = @_;
	
	$conn->privmsg($reply_to, "`".$Config::command_sign."vote new <question>` => "
												."suggest a new vote");
	$conn->privmsg($reply_to, "`".$Config::command_sign."vote {del|see} <id>` => "
												."remove or see the vote with id <id>");
	$conn->privmsg($reply_to, "`".$Config::command_sign."vote list [<#chan>]` => "
												."list votes on <#chan> or the current one by default");
	$conn->privmsg($reply_to, "`".$Config::command_sign."vote [<id>] {yes|no}` => "
												."vote for the last created vote or the one specified by its <id>");
	
	return 1;
} # Fin vote_help


# ###
#  vote_save
# Sauvegarde les postit
# ###
sub vote_save
{
	my (undef, $folder, $reloading) = @_;
	my $file_votes = $folder."/vote".$Config::suffixe.".sav";
	
	store [\%votes, \%last_added], $file_votes;
	
	return 1;
} # Fin vote_save


# ###
#  vote_load
# Remplis %postits au démarrage
# ###
sub vote_load
{
	shift; # Pas besoin de la connexion au serveur
	my $folder = shift;
	my $file_votes = $folder."/vote".$Config::suffixe.".sav";
	
	my $file_content = retrieve $file_votes;
	my ($ref_votes, $ref_last_added) = @{$file_content};
	
	%votes = %{$ref_votes};
	%last_added = %{$ref_last_added};
	
	return 1;
} # Fin vote_load



1;



__END__