# Provides captain game support

package Captains;

$main::g_captain = main::free_gid() unless defined $main::g_captain;
$main::groups{'captain'}{'gid'} = $main::g_captain 
  unless defined $main::groups{'captain'};

sub unset_captain {
 return unless defined $captain[$_[0]];
 my $gid = main::client_gid($captain[$_[0]]);
 main::server_auth($captain[$_[0]], $gid & ~$main::g_captain) 
  unless $gid == $main::g_root;
 undef $captain[$_[0]];
};

sub set_captain {
 my ($id, $team) = @_;
 unset_captain $team;
 $captain[$team] = $id;
 main::server_auth($id, main::client_gid($id) | $main::g_captain | $main::g_user);
}

sub captain_team {
 return $captain[0] == $_[0] ? 0 : 1;
};

sub start_captain_game {
 $captain_game_state = 'warmup';
 my $warmup = main::sv_warmup;
 main::restart $warmup > 0 ? $warmup : 30;
 $old_sv_teambalance = main::sv_teambalance();
 main::sv_teambalance(0);
 $old_sv_tournament_mode = main::sv_tournament_mode();
 main::sv_tournament_mode(1);
};

sub ready_captain_game { 
 $captain_game_state = 'ready';
 for (my $i = 0; $i < $main::MAX_CLIENTS; ++$i) {
  if (main::client_active($i)) {
   if    ($i == $captain[0]) { main::set_team $i,  0 }
   elsif ($i == $captain[1]) { main::set_team $i,  1 }
   else                      { main::set_team $i, -1 }
  };
 };

 for (my $i = 0; $i < 2; ++$i) {
  main::send_broadcast($captain[$i], 'Choose your teammates');
  main::send_chat -1, $captain[$i], 
    "Choose your teammates with the 'choose' command";
 }

 $teambalance = 0.5;
};

sub interrupt_captain_game {
 $captain_game_state = 'interrupt';
 main::pause(1);
 send_chat -1, -4, 'Captain game is paused';
 send_broadcast -1, 'Game is paused';
 for (my $i = 0; $i < 2; ++$i) {
  send_chat -1, $captain[$i], "Execute `ready' command when you are ready to continue"
    if defined $captain[$i];
 };
 undef @team_ready;
};

sub resume_captain_game {
 $captain_game_state = 'progress';
 main::pause(0);
};

sub stop_captain_game {
 if ($captain_game_state eq 'interrupt') {
  my $warmup = main::sv_pause_warmup;
  main::sv_pause_warmup(0);
  main::pause(0);
  main::sv_pause_warmup($warmup);
 };

 $captain_game_state = '';
 unset_captain 0;
 unset_captain 1;
 undef @team_ready;
 undef $choosing_team;
 undef $teambalance;
 main::sv_teambalance($old_sv_teambalance);
 main::sv_tournament_mode($old_sv_tournament_mode);
};

sub is_choosing_team {
 return $teambalance > 0 ? $_[0] == 0 : $_[0] == 1;
};

sub update_teambalance {
 my ($team, $add) = @_;
 if ($team == 0) { $teambalance -= $add }
 else            { $teambalance += $add };
};

sub set_team_readiness {
 my ($team, $state) = @_;
 if ($team_ready[$team] != $state) {
  $team_ready[$team] = $state;
  my $msg = ($team == 0 ? 'Red' : 'Blue') 
            . ' team is ' 
            . ($state ? 'ready' : 'not ready');

  main::send_chat -1, -4, $msg;
  main::send_broadcast -1, $msg;
 };
};

# TODO: make a proper overload
sub main::server_set_team {
 my ($cid, $team) = @_;
 if (!$captain_game_state) {
  main::set_team $cid, $team;
  return;
 };

 if ($team == -1) {
  main::set_team $cid, $team;
  if ($captain_game_state eq 'progress') { interrupt_captain_game }
  else { set_team_readiness $team, undef };
  update_teambalance main::client_team($cid), -1;
  return;
 };

 if ($cid == $captain[$team]) {
  if (is_choosing_team $team) { 
   main::set_team $cid, $team;
   update_teambalance $team, 1;
  } else {
   send_chat -1, $cid, "You cannot join your team when it's not your turn to choose";
  };
  return;
 };

 main::send_broadcast $cid, 'The captain has to choose you';
};

sub on_start_round {
 if ($captain_game_state eq 'warmup') {
  my $t;
  if    (!defined $captain[0]) { $t = 0 }
  elsif (!defined $captain[1]) { $t = 1 };
  my $msg;
  if (defined $t) {
   $msg = sprintf "%s team captain is missing", $t == 0 ? 'Red' : 'Blue';
   $team_ready[$t] = 0;
   $captain_game_state = '';
  } elsif (!($team_ready[0] && $team_ready[1])) {
   $msg = sprintf "%s team is not ready", $team_ready[0] ? 'Red' : 'Blue';
   $captain_game_state = 'ready';
  };

  if (defined $msg) {
   main::send_broadcast -1, $msg;
   main::send_chat -1, -4, "Cannot start captain game: $msg";
   return;
  };

  $captain_game_state = 'progress';
 };
};

sub on_end_round {
 stop_captain_game if $captain_game_state eq 'progress';
}

sub on_client_disconnect {
 my $team = main::client_team($_[0]);
 undef $captain[0] if $captain[0] == $_[0];
 undef $captain[1] if $captain[1] == $_[0];
 if ($captain_game_state) {
  if (!defined($captain[0]) && !defined($captain[1])) {
   main::send_broadcast -1, 'Captain game aborted';
   main::send_chat -1, -4, 'All captains have left the game';
   stop_captain_game;
  } elsif ($team != -1) { 
   update_teambalance $team, -1;
   if ($captain_game_state eq 'progress') { interrupt_captain_game }
   else { set_team_readiness $team, undef };
  };
 };
};

sub on_client_enter {
 if ($captain_game_state eq 'ready') {
  main::send_chat -1, $_[0], 'Captain game is starting';
 } elsif ($captain_game_state) {
  main::send_chat -1, $_[0], 'Captain game is going on';
 };
};

sub set_choosing_team {
 $choosing_team = $_[0];
 main::send_broadcast $captain[$_[0]], 'Choose your teammate';
};

$main::commands{'elect'} = {
 gid  => 0,
 help => 'Elect the captain',
 man  => <<END
Usage: elect player_name[, team]
See man player_name for how to specify player name.
team may be either red or blue. A free slot is picked if no team specified.
The captain game starts when two captains are elected
END
 ,
 vgid => $main::g_all,
 vcheck => sub {
  my ($id, $team) = main::parse_player_name $_[2];
  $id = main::player_cid $id;
  die "No such player\n" unless defined $id;
  die "Unknown team: $team\n" if ($team ne '' && $team ne 'red' && $team ne 'blue');
  die "Useless vote\n" 
    if       defined($captain[0]) 
          && $captain[0] == $id 
          && ($team eq '' || $team eq 'red')
       ||    defined($captain[1]) 
          && $captain[1] == $id 
          && ($team eq '' || $team eq 'blue');
  die "Two captains have already been elected. Specify the name of the team to replace captain for\n"
    if $team eq '' && defined($captain[0]) && defined($captain[1]);
  return 'elect ' . main::client_name($id);
 },
 sub => sub {
  my ($id, $team) = main::parse_player_name $_[0];
  $id = main::player_cid $id;
  die "No such player\n" unless defined $id;
  die "Unknown team: $team\n" if ($team ne '' && $team ne 'red' && $team ne 'blue');

  if ($team eq '') {
   if    (!defined $captain[0]) { $team = 'red'  }
   elsif (!defined $captain[1]) { $team = 'blue' }
   else { die "No free slots left\n" };
  };

  my $team = $team eq 'red' ? 0 : 1;
  set_captain($id, $team);

  ready_captain_game
    if (!$captain_game_state
        && defined($captain[0]) 
        && defined($captain[1]));

  set_team $id, $team 
    if $captain_game_state eq 'ready' && client_team($id) != $team;
 }
};

$main::commands{'choose'} = {
 gid  => $main::g_captain,
 help => 'Choose a player for your team',
 man  => <<END
Usage: choose player_name
See man player_name for how to specify player name.
The player chosen will join your team.
END
 ,
 sub  => sub {
  die "Elect the other captain first\n" if !$captain_game_state;
  my $team = captain_team $main::RCON_CID;
  die "It's not your turn\n" unless is_choosing_team $team;
  my $id = main::player_cid main::parse_player_name $_[0];
  die "No such player\n" unless defined $id;
  die sprintf "%s has already been chosen\n", main::client_name($id)
    unless main::client_team($id) == -1;
  
  main::set_team $id, $team;
  update_teambalance $team, 1;
  return;
 }
};

$main::commands{'skip'} = {
 gid  => $main::g_captain,
 help => 'Pass your turn to choose the player to the other team',
 man  => 'Usage: skip',
 sub  => sub { 
  die "Elect the other captain first\n"
    unless $captain_game_state;
  my $team = captain_team $main::RCON_CID;
  die "It's not your turn anyway\n" unless is_choosing_team $team;
  update_teambalance $team, 1;
  return;
 }
};

$main::commands{'ready'} = {
 gid  => $main::g_captain,
 help => 'Set team status to ready',
 man  => <<END
Usage: ready
Tell the world you are ready to start the game. The captain game will be
started as soon as both teams say they are ready. Execute again if you have
changed your mind.
END
 ,
 sub => sub {
  die "Elect the other captain first\n" if $captain_game_state eq '';
  my $team = captain_team $main::RCON_CID;

  set_team_readiness $team, !$team_ready[$team];

  if ($team_ready[0] && $team_ready[1]) {
   if ($captain_game_state eq 'ready') { start_captain_game } 
   else { resume_captain_game };
  };
 }
};

$main::commands{'drop_game'} = {
 gid  => $main::g_admin,
 help => 'Drop the captain game',
 man  => <<END
Usage: drop_game
Stops the captain game.
END
 ,
 sub  => \&stop_captain_game,
 vgid => $main::g_all,
 vcheck => sub {
  die "Captain game is not running\n" unless $captain_game_state;
 }
};

$main::commands{'drop_captain'} = {
 gid  => 0,
 help => 'Drop the captain',
 man  => <<END
Usage: drop_captain player_name
    or drop_captain team
See man player_name for how to specify player name.
team may be either 'red' or 'blue'.
Take away captain rights from the player. The game will be paused if it is
running.
END
 ,
 sub  => sub { 
  unset_captain $main::commands{'drop_captain'}{'vcheck'}->(undef, undef, $_[0]);
 },
 vgid => $main::g_all,
 vcheck => sub {
  my $team;
  if    ($_[2] eq 'red')  { $team = 0 }
  elsif ($_[2] eq 'blue') { $team = 1 }
  else {
   my $id = main::player_cid main::parse_player_name $_[2];
   die "No such player\n" unless defined $id;
   if    ($captain[0] == $id) { $team = 0 }
   elsif ($captain[1] == $id) { $team = 1 }
   else { die sprintf "%s is not captain\n", main::client_name($id) };
  };
  die "$_[0] team has no captain\n" unless defined $team;
  return $team if wantarray;
  return;
 }
};

$main::commands{'spec'} = {
 gid  => $main::g_captain,
 help => 'Kick your teammate from the team',
 man  => <<END
Usage: spec player_name
See man player_name for how to specify player name.
END
 ,
 sub => sub {
  my $id = main::player_cid main::parse_player_name $_[0];
  die "No such player\n" unless defined $id;
  my $team = main::client_team($id);
  die sprintf "%s is not your team mate\n", main::client_name($id)
    unless captain_team($main::RCON_CID) == $team;
  main::set_team $id, -1;
  update_teambalance $team, -1;
  return;
 }
};

sub is_captain {
 my $gid = main::client_id($_[0]);
 return $gid != $main::g_root 
        && $gid & $main::g_captain 
        && !($gid & $main::g_admin);
};

$main::commands{'stop'} = {
 gid  => $main::g_all,
 help => 'Pause the captain game',
 man  => 'Usage: stop',
 sub  => sub {
  die "The game is already stopped\n" if $captain_game_state eq 'interrupt';
  die "No captain game is in progress\n" if $captain_game_state ne 'progress';
  die "You are not playing\n" if main::client_team($main::RCON_CID) == -1;
  main::send_chat -1, -4, 
    main::client_name($main::RCON_CID) . ' has requested to pause the game';
  interrupt_captain_game;
 }
};

$main::hooks{'start round'}{'captains'} = \&on_start_round;
$main::hooks{'end round'  }{'captains'} = \&on_end_round;
$main::hooks{'enter'      }{'captains'} = \&on_client_enter;
$main::hooks{'leave'      }{'captains'} = \&on_client_disconnect;

1;
