/********************************************************************/
/*                                                                  */
/*            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          : netmess.c                                        */
/* content       : functions to create and parse network messages   */
/* last update   : April 14th 2001                                  */
/********************************************************************/

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

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "netmess.h"

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

static int eat_space (char **message);
static int read_separator (char **message);
static char *read_word (char **message);
static int get_code (char **message);
static int get_argc (char *message);
static char **get_argv (char *message, int argc);

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

/*------------------------------------------------------------------*/
/*
 * strcasecmp does not exists on win32, so we make it default to strcmp.
 * It's not a big problem since case insensitive tests are usefull in
 * developpement and network stuff is easier to test under UNIX anyway...
 */
#ifndef strcasecmp
#define strcasecmp(A,B) strcmp((A),(B))
#endif

/*------------------------------------------------------------------*/
/*
 * Skip spaces, tabs and the likes.
 */
static int
eat_space (char **message)
{
  int result = 0;

  while (isspace (**message))
    {
      result++;
      ++(*message);
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Reads a separator token. The separator is ',' 
 */
static int
read_separator (char **message)
{
  int result = 0;

  eat_space (message);
  if ((**message) == ',')
    {
      result++;
      ++(*message);
    }
  eat_space (message);

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Reads a word, a number, basically what's between separators.
 * The returned pointer must be disallocated with a "free".
 */
static char *
read_word (char **message)
{
  char *begin = NULL;
  char *result = NULL;
  int len;
  char quote_char;

  eat_space (message);

  quote_char = **message;

  if (quote_char == '\"' || quote_char == '\'')
    {
      /*
       * We handle strings quoted with ' or ".
       * No escape character is handled yet...
       */
      ++(*message);

      begin = *message;

      while ((**message) != quote_char && (**message) != '\0')
	{
	  ++(*message);
	}

      len = (*message) - begin;

      ++(*message);
    }
  else
    {
      begin = *message;

      while ((!isspace (**message)) && (**message) != ','
	     && (**message) != '\0')
	{
	  ++(*message);
	}

      len = (*message) - begin;
    }

  if (len >= 0)
    {
      result = (char *) malloc (len + 1);
      if (result != NULL)
	{
	  if (len > 0)
	    {
	      strncpy (result, begin, len);
	    }
	  result[len] = 0;

	  lw_netmess_cleanup_arg (result);
	}
    }
  eat_space (message);

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Returns the code of the command.
 */
static int
get_code (char **message)
{
  char *command;
  int result = LW_NETMESS_CODE_UNKNOWN;

  if (read_separator (message) == 0)
    {
      if ((command = read_word (message)) != NULL)
	{
	  if (strcasecmp (command, LW_NETMESS_TEXT_OK) == 0)
	    {
	      result = LW_NETMESS_CODE_OK;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_ERR) == 0)
	    {
	      result = LW_NETMESS_CODE_ERR;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_PING) == 0)
	    {
	      result = LW_NETMESS_CODE_PING;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_ECHO) == 0)
	    {
	      result = LW_NETMESS_CODE_ECHO;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_PROGRAM) == 0)
	    {
	      result = LW_NETMESS_CODE_PROGRAM;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_VERSION) == 0)
	    {
	      result = LW_NETMESS_CODE_VERSION;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_PASSWORD) == 0)
	    {
	      result = LW_NETMESS_CODE_PASSWORD;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_FREE) == 0)
	    {
	      result = LW_NETMESS_CODE_FREE;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_TEAM) == 0)
	    {
	      result = LW_NETMESS_CODE_TEAM;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_READY) == 0)
	    {
	      result = LW_NETMESS_CODE_READY;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_START) == 0)
	    {
	      result = LW_NETMESS_CODE_START;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_WAITING) == 0)
	    {
	      result = LW_NETMESS_CODE_WAITING;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_NEXT) == 0)
	    {
	      result = LW_NETMESS_CODE_NEXT;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_TEAMSTARTINFO) == 0)
	    {
	      result = LW_NETMESS_CODE_TEAMSTARTINFO;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_WHO) == 0)
	    {
	      result = LW_NETMESS_CODE_WHO;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_LOCAL) == 0)
	    {
	      result = LW_NETMESS_CODE_LOCAL;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_NETWORK) == 0)
	    {
	      result = LW_NETMESS_CODE_NETWORK;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_NOBODY) == 0)
	    {
	      result = LW_NETMESS_CODE_NOBODY;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_SENDMAP) == 0)
	    {
	      result = LW_NETMESS_CODE_SENDMAP;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_RECVMAP) == 0)
	    {
	      result = LW_NETMESS_CODE_RECVMAP;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_SENDCONFIG) == 0)
	    {
	      result = LW_NETMESS_CODE_SENDCONFIG;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_RECVCONFIG) == 0)
	    {
	      result = LW_NETMESS_CODE_RECVCONFIG;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_CHATTALK) == 0)
	    {
	      result = LW_NETMESS_CODE_CHATTALK;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_CHATLISTEN) == 0)
	    {
	      result = LW_NETMESS_CODE_CHATLISTEN;
	    }
	  else if (strcasecmp (command, LW_NETMESS_TEXT_QUIT) == 0)
	    {
	      result = LW_NETMESS_CODE_QUIT;
	    }

	  free (command);
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/* 
 * Gets the number of arguments in the message.
 * It's a little different from the C convention used in main(),
 * since 0 means "0 args" (whereas in main() 0 args corresponds to argc==1).
 */
static int
get_argc (char *message)
{
  int result = 0;
  char *word = NULL;
  int null_arg = 0;

  if (read_separator (&message) == 0)
    {
      if (message[0] == '\0')
	{
	  null_arg = 1;
	}

      word = read_word (&message);
      if (word != NULL)
	{
	  free (word);
	  result = 1;
	  while (read_separator (&message) == 1)
	    {
	      word = read_word (&message);
	      if (word != NULL)
		{
		  free (word);
		  result++;
		}
	    }
	}
    }

  if (result == 1 && null_arg)
    {
      result = 0;
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Parses all the arguments of the message.
 */
static char **
get_argv (char *message, int argc)
{
  char **result = NULL;
  char *word;
  int i = 0;

  if (argc > 0 && read_separator (&message) == 0)
    {
      result = (char **) malloc (argc * sizeof (char *));
      if (result != NULL)
	{
	  word = read_word (&message);
	  if (word != NULL)
	    {
	      result[i] = word;
	      i = 1;
	      while (i < argc && read_separator (&message) == 1)
		{
		  word = read_word (&message);
		  if (word != NULL)
		    {
		      result[i] = word;
		      i++;
		    }
		}
	    }
	}
      if (i != argc)
	{
	  free (result);
	  result = NULL;
	}
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * Parses and interprets a network message.
 * The syntaxe must be "COMMAND arg1,arg2,arg3,...".
 * This function should never fail, unless there are some problems
 * in memory allocation. Parsing an invalid message should simply
 * return a LW_NETMESS_CODE_UNKNOWN type with no args at all.
 * It's important to free the obtained pointer with lw_netmess_free.
 */
LW_NETMESS *
lw_netmess_read (char *message)
{
  LW_NETMESS *result = NULL;

  result = (LW_NETMESS *) malloc (sizeof (LW_NETMESS));
  if (result != NULL)
    {
      result->code = get_code (&message);
      result->argc = get_argc (message);
      result->argv = get_argv (message, result->argc);
    }

  return result;
}

/*------------------------------------------------------------------*/
/*
 * To be called after each successfull call to lw_netmess_read
 * This function calls free() internally.
 */
void
lw_netmess_free (LW_NETMESS * ptr)
{
  int i;

  if (ptr != NULL)
    {
      if (ptr->argv != NULL)
	{
	  for (i = 0; i < ptr->argc; ++i)
	    {
	      free (ptr->argv[i]);
	    }
	  if (ptr->argc > 0)
	    {
	      free (ptr->argv);
	    }
	}
    }
}

/*------------------------------------------------------------------*/
/*
 * Cleanups strings so that they can be used as arguments in
 * network messages
 */
void
lw_netmess_cleanup_arg (char *string)
{
  char *pos;
  unsigned char c;

  pos = string;
  while ((c = *pos) != '\0')
    {
      if (c < 32 || c > 127 || strchr ("'\",", c))
	{
	  (*pos) = ' ';
	}
      pos++;
    }
}
