/********************************************************************/
/*                                                                  */
/*            L   I  QQ  U U I DD    W   W  A  RR    555            */
/*            L   I Q  Q U U I D D   W   W A A R R   5              */
/*            L   I Q  Q U U I D D   W W W AAA RR    55             */
/*            L   I Q Q  U U I D D   WW WW A A R R     5            */
/*            LLL I  Q Q  U  I DD    W   W A A R R   55             */
/*                                                                  */
/*                             b                                    */
/*                             bb  y y                              */
/*                             b b yyy                              */
/*                             bb    y                              */
/*                                 yy                               */
/*                                                                  */
/*                     U U       FFF  O   O  TTT                    */
/*                     U U       F   O O O O  T                     */
/*                     U U TIRET FF  O O O O  T                     */
/*                     U U       F   O O O O  T                     */
/*                      U        F    O   O   T                     */
/*                                                                  */
/********************************************************************/

/*****************************************************************************/
/* Liquid War is a multiplayer wargame                                       */
/* Copyright (C) 1998-2004 Christian Mauduit                                 */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation; either version 2 of the License, or         */
/* (at your option) any later version.                                       */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
/*                                                                           */
/* Liquid War homepage : http://www.ufoot.org/liquidwar                      */
/* Contact author      : ufoot@ufoot.org                                     */
/*****************************************************************************/

/********************************************************************/
/* name          : srvcont.c                                        */
/* content       : global controler of the network channels         */
/* date de modif : 3 mai 98                                         */
/********************************************************************/

/*==================================================================*/
/* includes                                                         */
/*==================================================================*/

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>

#include "srvcont.h"
#include "srvchan.h"
#include "sockgen.h"
#include "sock2gen.h"
#include "log.h"
#include "server.h"
#include "netkey.h"
#include "srvtime.h"
#include "startinf.h"
#include "thrdgen.h"
#include "macro.h"
#include "execgen.h"

/*==================================================================*/
/* structs                                                          */
/*==================================================================*/
typedef struct
{
  int sock;
  char ip[LW_SOCK_IP_SIZE];
  int port;
  int *thread_running;
  char password[PASSWORD_SIZE + 1];
}
LW_SRVCONT_REJECT_DATA;

/*==================================================================*/
/* constants                                                        */
/*==================================================================*/

#define LW_SRVCONT_INCREASE_LAG_LIMIT 10
#define LW_SRVCONT_DECREASE_LAG_LIMIT 20

#define LW_SRVCONT_DISPLAY_STATE_DELAY 60
#define LW_SRVCONT_REGISTER_DELAY      90

/*==================================================================*/
/* static functions                                                 */
/*==================================================================*/

static void reset (LW_SRVCONT * cont);
static int distribute_teams (LW_SRVCONT * cont);
static void recalc_nb_teams (LW_SRVCONT * cont);
static void find_chan_and_team_by_server_id (LW_SRVCONT * cont,
					     int server_id,
					     int *chan, int *team);
static int send_lag_key_presses (LW_SRVCONT * cont, int lag);
static int reject_new_clients (LW_SRVCONT * cont, char *password);
static void reject_new_clients_callback (void *args);
static void disconnect_client (LW_SRVCONT * cont, int i, char *reason);

/*------------------------------------------------------------------*/
/*
 * Resets the LW_SRVCONT struct
 */
static void
reset (LW_SRVCONT * cont)
{
  memset (cont, 0, sizeof (LW_SRVCONT));

  cont->sock = -1;
  cont->nb_teams = 0;
  cont->nb_chans = 0;
}

/*------------------------------------------------------------------*/
/*
 * Affects consistent values to all the teams
 */
static int
distribute_teams (LW_SRVCONT * cont)
{
  int result = 1;
  int i, j, k;
  int i_orig, j_orig;
  int asm_algorithm;

  /*
   * The first step is to attribute a unique number to each
   * local team for each channel.
   */
  k = 0;
  for (i = 0; i < cont->nb_chans; ++i)
    {
      for (j = 0; j < NB_TEAMS; ++j)
	{
	  if (cont->chan[i].team[j].active)
	    {
	      cont->chan[i].team[j].server_id = k++;
	    }
	}
    }

  /*
   * Now k should be equal to the number of available teams,
   * or it means there's a serious error...
   */
  if (k != cont->nb_teams)
    {
      log_println_str ("Unable to attribute teams!");
      result = 0;
    }

  /*
   * The second step is to define which teams in each channel
   * will be the "network" teams.
   */
  for (i = 0; i < cont->nb_chans; ++i)
    {
      for (k = 0; k < cont->nb_teams; ++k)
	{
	  if (lw_srvchan_find_team_by_server_id (&(cont->chan[i]), k) < 0)
	    {
	      j = lw_srvchan_find_first_free_team (&(cont->chan[i]));
	      if (j >= 0)
		{
		  cont->chan[i].team[j].active = 1;
		  cont->chan[i].team[j].network = 1;
		  cont->chan[i].team[j].server_id = k;
		}
	      else
		{
		  result = 0;
		  log_println_str ("Unable to find a free team!");
		}
	    }
	}
    }

  /*
   * Third step, we copy the team names and other parameters in all
   * the teams structures. This is redundant information but it
   * will save useless CPU waste later...
   */
  for (i = 0; i < cont->nb_chans; ++i)
    {
      for (j = 0; j < NB_TEAMS; ++j)
	{
	  if (cont->chan[i].team[j].active)
	    {
	      find_chan_and_team_by_server_id (cont,
					       cont->chan[i].team[j].
					       server_id, &i_orig, &j_orig);
	      if (i_orig >= 0 && j_orig >= 0)
		{
		  /*
		   * Now we check that we're not uselessely copying
		   * stuff from the same struct. It's a waste of time
		   * and can cause some bugs depending on the implementation
		   * of the LW_MACRO_STRCPY macro.
		   */
		  if (i_orig != i || j_orig != j)
		    {
		      LW_MACRO_STRCPY (cont->chan[i].team[j].name,
				       cont->chan[i_orig].team[j_orig].name);
		      cont->chan[i].team[j].control_type =
			cont->chan[i_orig].team[j_orig].control_type;
		    }
		}
	      else
		{
		  result = 0;
		  log_println_str ("Unable to find a free team!");
		}
	    }
	}
    }

  /*
   * Last step, we check if the "asm_algorithm" parameter should be
   * enabled. Indeed, it must be enabled on _all_ computers or on
   * none of them...
   */
  asm_algorithm = 1;
  for (i = 0; i < cont->nb_chans; ++i)
    {
      asm_algorithm = (cont->chan[i].config.asm_algorithm && asm_algorithm);
    }
  for (i = 0; i < cont->nb_chans; ++i)
    {
      cont->chan[i].config.asm_algorithm = asm_algorithm;
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Calculates the cont->nb_teams value
 */
static void
recalc_nb_teams (LW_SRVCONT * cont)
{
  int i, j;

  cont->nb_teams = 0;
  for (i = 0; i < cont->nb_chans; ++i)
    {
      for (j = 0; j < NB_TEAMS; ++j)
	{
	  if (cont->chan[i].team[j].active)
	    {
	      cont->nb_teams++;
	    }
	}
    }
}

/*------------------------------------------------------------------*/
/*
 * Gets the chan and team index for a given server id
 */
static void
find_chan_and_team_by_server_id (LW_SRVCONT * cont,
				 int server_id, int *chan, int *team)
{
  int i;
  int j;

  *chan = *team = -1;
  for (i = 0; i < cont->nb_chans; ++i)
    {
      for (j = 0; j < NB_TEAMS; ++j)
	{
	  if (cont->chan[i].team[j].active &&
	      (cont->chan[i].team[j].server_id == server_id) &&
	      !(cont->chan[i].team[j].network))
	    {
	      (*chan) = i;
	      (*team) = j;
	    }
	}
    }
}

/*------------------------------------------------------------------*/
/*
 * Prepares the LW_TEAMSTARTINFO structures
 */
static void
prepare_team_start_info (LW_TEAMSTARTINFO * team_start_info,
			 LW_SRVCONT * cont)
{
  int i, j, k;

  for (i = 0; i < NB_TEAMS; ++i)
    {
      lw_teamstartinfo_reset (team_start_info + i);
    }

  k = 0;
  for (i = 0; i < cont->nb_chans; ++i)
    {
      for (j = 0; j < NB_TEAMS; ++j)
	{
	  if (cont->chan[i].team[j].active && k < NB_TEAMS)
	    {
	      team_start_info[k].active = 1;
	      team_start_info[k].start = cont->chan[i].start;
	      LW_MACRO_STRCPY (team_start_info[k].name,
			       cont->chan[i].team[j].name);
	      k++;
	    }
	}
    }
}

/*==================================================================*/
/* global functions                                                 */
/*==================================================================*/

/*------------------------------------------------------------------*/
/* 
 * Waits for a given number of teams to connect
 */
int
lw_srvcont_wait_teams (LW_SRVCONT * cont,
		       LW_WWWSRV_DATA * www_data,
		       int nb_teams, int port, char *password, char *callback)
{
  int free_teams = 0;
  int result = 0;
  int i;
  int start;
  int started = 0;
  LW_TEAMSTARTINFO team_start_info[NB_TEAMS];
  int cur_time, last_time;
  int display_waiting_for = 1;
  LW_CHAT_HISTORY chat;
  LW_CHAT_MESSAGE chat_mess;

  reset (cont);

  cur_time = last_time = lw_srvtime_seconds ();

  lw_chat_reset (&chat);

  /*
   * We register the server on the metaserver for the
   * first time, with 0 active teams.
   */
  www_data->busy_players = cont->nb_teams;
  www_data->max_players = nb_teams;
  www_data->log = 1;
  lw_wwwsrv_register (www_data);

  if (lw_sock_listen (&(cont->sock), port))
    {
      log_print_str ("Listening on port ");
      log_print_int (port);
      log_println_str ("...");
      log_flush ();

      /*
       * Now we just wait until there are enough teams connected
       */
      while (!started)
	{
	  free_teams = nb_teams - cont->nb_teams;

	  if (display_waiting_for)
	    {
	      log_print_str ("Waiting for ");
	      log_print_int (free_teams);
	      if (free_teams > 1)
		{
		  log_print_str (" teams...");
		}
	      else
		{
		  log_print_str (" team...");
		}
	      log_println ();

	      display_waiting_for = 0;
	    }

	  if (lw_srvchan_wait_teams (&(cont->chan[cont->nb_chans]),
				     &free_teams,
				     cont->sock,
				     &(cont->chan[cont->nb_chans].map),
				     &(cont->chan[cont->nb_chans].config),
				     password))
	    {
	      if (cont->nb_teams == 0)
		{
		  /*
		   * OK, this guy is the first one to connect on
		   * the server, so we'll fire the "callback" script/program
		   */
		  if (callback && strlen (callback))
		    {
		      log_print_str ("Executing \"");
		      log_print_str (callback);
		      log_println_str ("\"");
		      lw_exec_cmd (callback);
		    }
		}
	      cont->nb_teams = nb_teams - free_teams;
	      ++(cont->nb_chans);
	      display_waiting_for = 1;
	    }

	  /*
	   * Now we check if me must force a game start
	   */
	  start = 1;
	  for (i = 0; i < cont->nb_chans; ++i)
	    {
	      start = start && cont->chan[i].start;
	    }
	  if (cont->nb_teams >= 2 && (start || cont->nb_teams >= nb_teams))
	    {
	      if (cont->nb_teams < nb_teams)
		{
		  /*
		   * OK, all the players have pressed the "start" button and there
		   * are at least 2 playing teams -> we start the game!
		   */
		  nb_teams = cont->nb_teams;
		  log_print_str ("Starting game with only ");
		  log_print_int (cont->nb_teams);
		  log_println_str (" teams");
		}
	      free_teams = 0;
	    }

	  /*
	   * The official start signal is: free_teams is set to 0.
	   * We use an extra flag "started" in case free_teams would
	   * be recalculated
	   */
	  if (free_teams <= 0)
	    {
	      free_teams = 0;
	      started = 1;
	    }


	  /*
	   * We replicate chat messages
	   */
	  while (lw_chat_pop (&chat, &chat_mess))
	    {
	      for (i = 0; i < cont->nb_chans; ++i)
		{
		  lw_chat_push (&(cont->chan[i].chat),
				chat_mess.author, chat_mess.content);
		}
	    }

	  /*
	   * now we send feedback to all the connected teams
	   */
	  prepare_team_start_info (team_start_info, cont);
	  /*
	   * loop with a step of -1 to handle correcty the
	   * possible disconnection of players
	   */
	  for (i = cont->nb_chans - 1; i >= 0; --i)
	    {
	      if (!lw_srvchan_keepalive (&(cont->chan[i]),
					 free_teams, team_start_info, &chat))
		{
		  /*
		   * There's an error with this client, so 
		   * we're simply going to disconnect it...
		   */
		  disconnect_client (cont, i, "keepalive failed");

		  display_waiting_for = 1;
		}
	    }
	  recalc_nb_teams (cont);

	  www_data->busy_players = cont->nb_teams;
	  www_data->max_players = nb_teams;

	  /*
	   * We register the server again since the number of 
	   * connected teams might have changed
	   */
	  cur_time = lw_srvtime_seconds ();
	  if (cur_time - last_time > LW_SRVCONT_REGISTER_DELAY
	      || display_waiting_for)
	    {
	      www_data->log = 0;
	      lw_wwwsrv_register (www_data);
	      last_time = cur_time;
	    }
	}

      /*
       * For now the server is a "blocking" server which is never statisfied
       * until the good number of players has connected itself, so if we
       * get here it means the operation has been successfull
       */
      result = 1;
    }
  else
    {
      log_print_str ("Unable to bind socket on port ");
      log_print_int (port);
      log_println ("!");

      /*
       * We consider this a fatal error and quit the program
       * right away.
       */
      lw_server_exit (1);
    }

  return result;
}

/*------------------------------------------------------------------*/
/* 
 * Tells the teams about what the other teams are
 */
int
lw_srvcont_tell_who (LW_SRVCONT * cont)
{
  int i;
  int result = 0;

  if (distribute_teams (cont))
    {
      result = 1;
      for (i = cont->nb_chans - 1; i >= 0; --i)
	{
	  if (!lw_srvchan_tell_who (&(cont->chan[i]),
				    cont->chan[0].map,
				    &(cont->chan[0].config)))
	    {
	      /*
	       * There's been an error exchanging data with this team
	       * so we decide to "close" it.
	       */
	      disconnect_client (cont, i, "can't send game info");
	    }
	}

      /*
       * We start the game only if there are more than 1 channels opened...
       */
      if (cont->nb_chans > 0)
	{
	  result = 1;
	}
    }

  return result;
}


/*------------------------------------------------------------------*/
/* 
 * Sends a final OK message to all the clients
 */
int
lw_srvcont_final_ok (LW_SRVCONT * cont)
{
  int i;
  int result = 0;

  for (i = cont->nb_chans - 1; i >= 0; --i)
    {
      if (!lw_srvchan_final_ok (&(cont->chan[i])))
	{
	  disconnect_client (cont, i, "can't send final ok");
	}
    }

  /*
   * We start the game only if there are more than 1 channels opened...
   */
  if (cont->nb_chans > 0)
    {
      result = 1;
    }

  if (result)
    {
      log_print_str ("Game start");
      log_println ();
    }
  else
    {
      log_print_str ("Unable to start game");
      log_println ();
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Sends a serie of blank key presses to create an artificial lag.
 * This lag is usefull, since this way, a few game cycles will 
 * have to be run before a real key press comes back to a given
 * player. This leaves times for other players to send their own
 * key presses and this way the game can run smoothly.
 */
static int
send_lag_key_presses (LW_SRVCONT * cont, int lag)
{
  int result = 1;
  int i, j;
  LW_NETKEY netkey;

  lw_netkey_reset (&netkey);

  for (j = 0; j < lag && result; ++j)
    {
      for (i = cont->nb_chans - 1; i >= 0; --i)
	{
	  /*
	   * We put the checksum to 1, to inform the clients that
	   * everything is OK
	   */
	  cont->chan[i].checksum_send = 1;
	  if (!lw_srvchan_send_keys (&(cont->chan[i]), &netkey))
	    {
	      disconnect_client (cont, i, "error sending lag keys");
	    }
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/* 
 * Checks if the checksums from the clients are all the same
 */
int
check_checksum (LW_SRVCONT * cont)
{
  int i, j;
  int ok = 1;
  int nb_diff;

  for (i = 0; i < cont->nb_chans; ++i)
    {
      cont->chan[i].checksum_send = 1;
      nb_diff = 0;

      /*
       * We do not check checksums if there's only 1 connected client.
       */
      if (cont->nb_chans >= 2)
	{
	  for (j = 0; j < cont->nb_chans; ++j)
	    {
	      if (cont->chan[i].checksum_recv != cont->chan[j].checksum_recv)
		{
		  ok = 0;
		  nb_diff++;
		}
	    }

	  /*
	   * Now if this channel has a different checksum
	   * than everyone else, we consider there's an error
	   * on it. But if there's at least one channel that
	   * has the same checksum, we consider it's OK
	   * (for this channel only of course...)
	   */
	  if (nb_diff >= cont->nb_chans - 1)
	    {
	      cont->chan[i].checksum_send = 0;

	      log_print_str ("Checksum error on client \"");
	      log_print_str (cont->chan[i].ip);
	      log_print_str (":");
	      log_print_int (cont->chan[i].port);
	      log_print_str ("\" (");
	      log_print_int (cont->chan[i].checksum_recv);
	      log_print_str (")");
	      log_println ();
	    }
	}
    }

  return ok;
}

/*------------------------------------------------------------------*/
/* 
 * Sends a final OK message to all the clients
 */
int
lw_srvcont_replicate_keys (LW_SRVCONT * cont,
			   LW_WWWSRV_DATA * www_data, int lag, char *password)
{
  int result = 1;
  LW_NETKEY netkey;
  int i;
  int increase_lag_counter = 0;
  int decrease_lag_counter = 0;
  int all_clients_request_increase_lag;
  int all_clients_request_decrease_lag;
  int a_client_requests_increase_lag;
  int a_client_requests_decrease_lag;
  int rounds = 0;
  int last_rounds = 0;
  int average_lag = 0;
  time_t start, start2, end;
  double elapsed;
  int lag_key_presses_to_send = 0;
  int skip_keys;

  www_data->log = 1;
  lw_wwwsrv_register (www_data);

  lag_key_presses_to_send = lag;

  start2 = start = lw_srvtime_seconds ();

  while (cont->nb_chans > 0)
    {
      lw_netkey_reset (&netkey);

      /*
       * We still listen on the listening socket, to reject
       * newcomers since the game already started
       */
      reject_new_clients (cont, password);

      all_clients_request_increase_lag = 1;
      all_clients_request_decrease_lag = 1;
      a_client_requests_increase_lag = 0;
      a_client_requests_decrease_lag = 0;

      /*
       * First we get the informations from all the players
       */
      for (i = cont->nb_chans - 1; i >= 0; --i)
	{
	  if (lw_srvchan_recv_keys (&(cont->chan[i]), &netkey))
	    {
	      /*
	       * if at least a client does not request a lag
	       * increase, then we don't do it
	       */
	      if (netkey.cmd == LW_NETKEY_CMD_INCREASE_LAG)
		{
		  a_client_requests_increase_lag = 1;
		}
	      else
		{
		  all_clients_request_increase_lag = 0;
		}

	      /*
	       * if at least a client does not request a lag
	       * decrease, then we don't do it
	       */
	      if (netkey.cmd == LW_NETKEY_CMD_DECREASE_LAG)
		{
		  a_client_requests_decrease_lag = 1;
		}
	      else
		{
		  all_clients_request_decrease_lag = 0;
		}
	    }
	  else
	    {
	      /*
	       * There's been an error receiving data from this team
	       * so we decide to "close" it.
	       */
	      disconnect_client (cont, i, "no more keys");
	    }
	}

      /*
       * we reset the cmd and arg fields
       */
      netkey.cmd = LW_NETKEY_CMD_NONE;
      netkey.arg = 0;

      /*
       * We check if the checksums are OK
       */
      check_checksum (cont);

      /*
       * we use a counter system, so that all client must answer
       * "yes we want a lag increase" several times in a row
       * before we actually update the lag
       */
      if (all_clients_request_increase_lag)
	{
	  increase_lag_counter++;
	}
      else
	{
	  increase_lag_counter = 0;
	}

      /*
       * we use a counter system, so that all client must answer
       * "yes we want a lag decrease" several times in a row
       * before we actually update the lag
       */
      if (a_client_requests_decrease_lag)
	{
	  decrease_lag_counter++;
	}
      else
	{
	  decrease_lag_counter = 0;
	}

      /*
       * If we need to increase the lag, we send blank key presses
       */
      if (increase_lag_counter >= LW_SRVCONT_INCREASE_LAG_LIMIT
	  && lag_key_presses_to_send == 0)
	{
	  increase_lag_counter = 0;
	  lag++;
	  lag_key_presses_to_send++;
	}


      /*
       * If we need to decrease the lag, we simply do not send
       * the key presses...
       */
      skip_keys = 0;
      if (decrease_lag_counter >= LW_SRVCONT_DECREASE_LAG_LIMIT)
	{
	  decrease_lag_counter = 0;
	  lag--;
	  if (lag_key_presses_to_send > 0)
	    {
	      /*
	       * We cancel the planified lag keys
	       */
	      lag_key_presses_to_send--;
	    }
	  else
	    {
	      /*
	       * No lag keys planned, we skip the current keys
	       */
	      skip_keys = 1;
	    }
	}

      if (!skip_keys)
	{
	  /*
	   * Now we send the information to all the players
	   */
	  for (i = cont->nb_chans - 1; i >= 0; --i)
	    {
	      if (!lw_srvchan_send_keys (&(cont->chan[i]), &netkey))
		{
		  /*
		   * There's been an error receiving data from this team
		   * so we decide to "close" it.
		   */
		  disconnect_client (cont, i, "error sending keys");
		}
	    }
	}

      /*
       * We send the lag key presses 1 by 1. This is a way
       * to synchronize correctly at the beginning of a game.
       */
      if (lag_key_presses_to_send > 0)
	{
	  lag_key_presses_to_send--;
	  send_lag_key_presses (cont, 1);
	}

      rounds++;
      last_rounds++;
      average_lag += lag;

      end = lw_srvtime_seconds ();
      elapsed = end - start;

      if (((int) elapsed) > LW_SRVCONT_DISPLAY_STATE_DELAY)
	{
	  if (rounds <= 0)
	    {
	      rounds = 1;
	    }
	  if (last_rounds <= 0)
	    {
	      last_rounds = 1;
	    }

	  log_print_int (rounds);
	  log_print_str (" rounds, ");
	  log_print_int (last_rounds / ((int) elapsed));
	  log_print_str (" rounds/sec, average lag is ");
	  log_print_int (average_lag / last_rounds);
	  log_println ();

	  start = end;
	  last_rounds = 0;
	  average_lag = 0;
	}

      if (end - start2 > LW_SRVCONT_REGISTER_DELAY)
	{
	  start2 += LW_SRVCONT_REGISTER_DELAY;

	  www_data->log = 0;
	  lw_wwwsrv_register (www_data);
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/* 
 * Closes everything and frees the resources
 */
void
lw_srvcont_close (LW_SRVCONT * cont)
{
  int i;

  lw_sock_close (&(cont->sock));

  log_print_str ("Game over");
  log_println ();

  for (i = 0; i < cont->nb_chans; ++i)
    {
      lw_srvchan_close (&(cont->chan[i]));
    }

  reset (cont);
}

/*------------------------------------------------------------------*/
/*
 * Launches a thread which will handle new connections while
 * other players are playing.
 */
static int
reject_new_clients (LW_SRVCONT * cont, char *password)
{
  int result = 0;
  static LW_SRVCONT_REJECT_DATA data;
  static int thread_running = 0;

  /*
   * If there's already a thread answering to a client, we do nothing,
   * for we can answer later, and it makes programming easier and
   * safer. In any case, the answer we're going to give to the client
   * is: "there's no room for you guy" so it's not that bad to be
   * a little late when answering.
   */
  if (!thread_running)
    {
      data.sock = -1;
      data.ip[0] = '\0';
      data.port = 0;
      data.thread_running = &thread_running;
      strncpy (data.password, password, PASSWORD_SIZE);
      data.password[PASSWORD_SIZE] = '\0';

      if (lw_sock_accept (&(data.sock), data.ip, &(data.port), cont->sock, 0))
	{
	  thread_running = 1;
	  result =
	    lw_thread_start (reject_new_clients_callback, (void *) &data);
	  if (!result)
	    {
	      lw_sock_close (&(data.sock));
	      thread_running = 0;
	    }
	}
      else
	{
	  /*
	   * privacy is set to 0, we do not tell anyone about this server
	   */
	  result = 1;
	}
    }
  else
    {
      result = 1;
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Main function of the server thread which tries to connect
 * on the metaserver
 */
static void
reject_new_clients_callback (void *args)
{
  LW_SRVCONT_REJECT_DATA data;
  int ret;
  int ret_mess = 1;
  char message[LW_SOCK_MESSAGE_SIZE];
  LW_NETMESS *mess;
  int program = 0;
  int version = 0;
  int password = 0;
  int free_received = 0;
  int normal_quit = 0;

  memcpy (&data, args, sizeof (LW_SRVCONT_REJECT_DATA));

  log_print_str ("Connection from \"");
  log_print_str (data.ip);
  log_print_str (":");
  log_print_int (data.port);
  log_print_str ("\"");
  log_println ();
  log_flush ();

  while ((!free_received) && ret_mess &&
	 ((ret = lw_sock_recv_str (data.sock, message)) >= 0))
    {
      if (ret > 0)
	{
	  mess = lw_netmess_read (message);
	  if (mess != NULL)
	    {
	      /*
	       * switch to handle the different possible messages
	       */
	      switch (mess->code)
		{
		case LW_NETMESS_CODE_PING:
		  ret_mess = lw_srvchan_handle_ping (data.sock, mess);
		  break;
		case LW_NETMESS_CODE_ECHO:
		  ret_mess = lw_srvchan_handle_echo (data.sock, mess);
		  break;
		case LW_NETMESS_CODE_PROGRAM:
		  ret_mess =
		    lw_srvchan_handle_program (data.sock, mess, &program);
		  break;
		case LW_NETMESS_CODE_VERSION:
		  ret_mess =
		    lw_srvchan_handle_version (data.sock, mess, &version);
		  break;
		case LW_NETMESS_CODE_PASSWORD:
		  ret_mess =
		    lw_srvchan_handle_password (data.sock, mess, &password,
						data.password);
		  break;
		case LW_NETMESS_CODE_FREE:
		  ret_mess =
		    lw_srvchan_handle_free (data.sock, mess, 0,
					    &free_received);
		  break;
		case LW_NETMESS_CODE_QUIT:
		  ret_mess =
		    lw_srvchan_handle_quit (data.sock, mess, &normal_quit);
		  break;
		default:
		  ret_mess = lw_srvchan_handle_unknown (data.sock, mess);
		  break;
		}

	      /*
	       * lw_netmess_free must *not* be forgetted 
	       */
	      lw_netmess_free (mess);

	      if (!ret_mess)
		{
		  log_print_str ("Error processing \"");
		  log_print_str (message);
		  log_print_str ("\"");
		  log_println ();
		}
	    }
	  else
	    {
	      log_println_str ("Serious memory problem 8-(");
	      ret_mess = 0;
	    }
	}
    }

  log_print_str ("Refusing \"");
  log_print_str (data.ip);
  log_print_str (":");
  log_print_int (data.port);
  log_print_str ("\"");
  if (normal_quit)
    {
      /*
       * If the client quitted with a normal "QUIT" message, then
       * we mention in the log that it was probably simply pinging.
       */
      log_print_str (" (probably a ping)");
    }
  log_println ();

  lw_sock_close (&(data.sock));
  (*(data.thread_running)) = 0;
}

/*------------------------------------------------------------------*/
/*
 * Disconnects a client. This code supposes that the caller function
 * is executing a loop with a step of -1 (ie a "reverse" loop).
 */
static void
disconnect_client (LW_SRVCONT * cont, int i, char *reason)
{
  int j;

  log_print_str ("Disconnecting \"");
  log_print_str (cont->chan[i].ip);
  log_print_str (":");
  log_print_int (cont->chan[i].port);
  log_print_str ("\" (");
  if (cont->chan[i].normal_quit)
    {
      /*
       * The client decided to quit, therefore this is not
       * an error, we mark it as such
       */
      log_print_str ("decided to quit");
    }
  else
    {
      log_print_str (reason);
    }
  log_println_str (")");

  lw_srvchan_close (&(cont->chan[i]));
  for (j = i + 1; j < cont->nb_chans; ++j)
    {
      cont->chan[j - 1] = cont->chan[j];
    }
  cont->nb_chans--;
}
