/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2009 Darklegion Development

This file is part of Tremulous.

Tremulous 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.

Tremulous 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 Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "g_local.h"

qboolean G_SpawnString( const char *key, const char *defaultString, char **out )
{
  int   i;

  if( !level.spawning )
  {
    *out = (char *)defaultString;
//    G_Error( "G_SpawnString() called while not spawning" );
    return qfalse;
  }

  for( i = 0; i < level.numSpawnVars; i++ )
  {
    if( !Q_stricmp( key, level.spawnVars[ i ][ 0 ] ) )
    {
      *out = level.spawnVars[ i ][ 1 ];
      return qtrue;
    }
  }

  *out = (char *)defaultString;
  return qfalse;
}

qboolean  G_SpawnFloat( const char *key, const char *defaultString, float *out )
{
  char    *s;
  qboolean  present;

  present = G_SpawnString( key, defaultString, &s );
  *out = atof( s );
  return present;
}

qboolean G_SpawnInt( const char *key, const char *defaultString, int *out )
{
  char      *s;
  qboolean  present;

  present = G_SpawnString( key, defaultString, &s );
  *out = atoi( s );
  return present;
}

qboolean  G_SpawnVector( const char *key, const char *defaultString, float *out )
{
  char    *s;
  qboolean  present;

  present = G_SpawnString( key, defaultString, &s );
  sscanf( s, "%f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ] );
  return present;
}

qboolean  G_SpawnVector4( const char *key, const char *defaultString, float *out )
{
  char    *s;
  qboolean  present;

  present = G_SpawnString( key, defaultString, &s );
  sscanf( s, "%f %f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ], &out[ 3 ] );
  return present;
}



//
// fields are needed for spawning from the entity string
//
typedef enum
{
  F_INT,
  F_FLOAT,
  F_LSTRING,      // string on disk, pointer in memory, TAG_LEVEL
  F_GSTRING,      // string on disk, pointer in memory, TAG_GAME
  F_VECTOR,
  F_VECTOR4,
  F_ANGLEHACK,
  F_ENTITY,     // index on disk, pointer in memory
  F_ITEM,       // index on disk, pointer in memory
  F_CLIENT,     // index on disk, pointer in memory
  F_IGNORE
} fieldtype_t;

typedef struct
{
  char  *name;
  int   ofs;
  fieldtype_t type;
  int   flags;
} field_t;

field_t fields[ ] =
{
  {"acceleration", FOFS(acceleration), F_VECTOR},
  {"alpha", FOFS(pos1), F_VECTOR},
  {"angle", FOFS(s.angles), F_ANGLEHACK},
  {"angles", FOFS(s.angles), F_VECTOR},
  {"animation", FOFS(animation), F_VECTOR4},
  {"bounce", FOFS(physicsBounce), F_FLOAT},
  {"classname", FOFS(classname), F_LSTRING},
  {"count", FOFS(count), F_INT},
  {"dmg", FOFS(damage), F_INT},
  {"health", FOFS(health), F_INT},
  {"light", 0, F_IGNORE},
  {"message", FOFS(message), F_LSTRING},
  {"model", FOFS(model), F_LSTRING},
  {"model2", FOFS(model2), F_LSTRING},
  {"origin", FOFS(s.origin), F_VECTOR},
  {"radius", FOFS(pos2), F_VECTOR},
  {"random", FOFS(random), F_FLOAT},
  {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT},
  {"spawnflags", FOFS(spawnflags), F_INT},
  {"speed", FOFS(speed), F_FLOAT},
  {"target", FOFS(target), F_LSTRING},
  {"targetname", FOFS(targetname), F_LSTRING},
  {"targetShaderName", FOFS(targetShaderName), F_LSTRING},
  {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING},
  {"team", FOFS(team), F_LSTRING},
  {"wait", FOFS(wait), F_FLOAT}
};


typedef struct
{
  char  *name;
  void  (*spawn)(gentity_t *ent);
} spawn_t;

void SP_info_player_start( gentity_t *ent );
void SP_info_player_deathmatch( gentity_t *ent );
void SP_info_player_intermission( gentity_t *ent );

void SP_info_alien_intermission( gentity_t *ent );
void SP_info_human_intermission( gentity_t *ent );

void SP_info_firstplace( gentity_t *ent );
void SP_info_secondplace( gentity_t *ent );
void SP_info_thirdplace( gentity_t *ent );
void SP_info_podium( gentity_t *ent );

void SP_func_plat( gentity_t *ent );
void SP_func_static( gentity_t *ent );
void SP_func_rotating( gentity_t *ent );
void SP_func_bobbing( gentity_t *ent );
void SP_func_pendulum( gentity_t *ent );
void SP_func_button( gentity_t *ent );
void SP_func_door( gentity_t *ent );
void SP_func_door_rotating( gentity_t *ent );
void SP_func_door_model( gentity_t *ent );
void SP_func_train( gentity_t *ent );
void SP_func_timer( gentity_t *self);

void SP_trigger_always( gentity_t *ent );
void SP_trigger_multiple( gentity_t *ent );
void SP_trigger_push( gentity_t *ent );
void SP_trigger_teleport( gentity_t *ent );
void SP_trigger_hurt( gentity_t *ent );
void SP_trigger_stage( gentity_t *ent );
void SP_trigger_win( gentity_t *ent );
void SP_trigger_buildable( gentity_t *ent );
void SP_trigger_class( gentity_t *ent );
void SP_trigger_equipment( gentity_t *ent );
void SP_trigger_gravity( gentity_t *ent );
void SP_trigger_heal( gentity_t *ent );
void SP_trigger_ammo( gentity_t *ent );

void SP_target_delay( gentity_t *ent );
void SP_target_speaker( gentity_t *ent );
void SP_target_print( gentity_t *ent );
void SP_target_character( gentity_t *ent );
void SP_target_score( gentity_t *ent );
void SP_target_teleporter( gentity_t *ent );
void SP_target_relay( gentity_t *ent );
void SP_target_kill( gentity_t *ent );
void SP_target_position( gentity_t *ent );
void SP_target_location( gentity_t *ent );
void SP_target_push( gentity_t *ent );
void SP_target_rumble( gentity_t *ent );
void SP_target_alien_win( gentity_t *ent );
void SP_target_human_win( gentity_t *ent );
void SP_target_hurt( gentity_t *ent );

void SP_light( gentity_t *self );
void SP_info_null( gentity_t *self );
void SP_info_notnull( gentity_t *self );
void SP_info_camp( gentity_t *self );
void SP_path_corner( gentity_t *self );

void SP_misc_teleporter_dest( gentity_t *self );
void SP_misc_model( gentity_t *ent );
void SP_misc_portal_camera( gentity_t *ent );
void SP_misc_portal_surface( gentity_t *ent );

void SP_shooter_rocket( gentity_t *ent );
void SP_shooter_plasma( gentity_t *ent );
void SP_shooter_grenade( gentity_t *ent );

void SP_misc_particle_system( gentity_t *ent );
void SP_misc_anim_model( gentity_t *ent );
void SP_misc_light_flare( gentity_t *ent );

spawn_t spawns[ ] =
{
  { "func_bobbing",             SP_func_bobbing },
  { "func_button",              SP_func_button },
  { "func_door",                SP_func_door },
  { "func_door_model",          SP_func_door_model },
  { "func_door_rotating",       SP_func_door_rotating },
  { "func_group",               SP_info_null },
  { "func_pendulum",            SP_func_pendulum },
  { "func_plat",                SP_func_plat },
  { "func_rotating",            SP_func_rotating },
  { "func_static",              SP_func_static },
  { "func_timer",               SP_func_timer },      // rename trigger_timer?
  { "func_train",               SP_func_train },

  // info entities don't do anything at all, but provide positional
  // information for things controlled by other processes
  { "info_alien_intermission",  SP_info_alien_intermission },
  { "info_human_intermission",  SP_info_human_intermission },
  { "info_notnull",             SP_info_notnull },    // use target_position instead
  { "info_null",                SP_info_null },
  { "info_player_deathmatch",   SP_info_player_deathmatch },
  { "info_player_intermission", SP_info_player_intermission },
  { "info_player_start",        SP_info_player_start },
  { "light",                    SP_light },
  { "misc_anim_model",          SP_misc_anim_model },
  { "misc_light_flare",         SP_misc_light_flare },
  { "misc_model",               SP_misc_model },
  { "misc_particle_system",     SP_misc_particle_system },
  { "misc_portal_camera",       SP_misc_portal_camera },
  { "misc_portal_surface",      SP_misc_portal_surface },
  { "misc_teleporter_dest",     SP_misc_teleporter_dest },
  { "path_corner",              SP_path_corner },

  // targets perform no action by themselves, but must be triggered
  // by another entity
  { "target_alien_win",         SP_target_alien_win },
  { "target_delay",             SP_target_delay },
  { "target_human_win",         SP_target_human_win },
  { "target_hurt",              SP_target_hurt },
  { "target_kill",              SP_target_kill },
  { "target_location",          SP_target_location },
  { "target_position",          SP_target_position },
  { "target_print",             SP_target_print },
  { "target_push",              SP_target_push },
  { "target_relay",             SP_target_relay },
  { "target_rumble",            SP_target_rumble },
  { "target_score",             SP_target_score },
  { "target_speaker",           SP_target_speaker },
  { "target_teleporter",        SP_target_teleporter },

  // Triggers are brush objects that cause an effect when contacted
  // by a living player, usually involving firing targets.
  // While almost everything could be done with
  // a single trigger class and different targets, triggered effects
  // could not be client side predicted (push and teleport).
  { "trigger_always",           SP_trigger_always },
  { "trigger_ammo",             SP_trigger_ammo },
  { "trigger_buildable",        SP_trigger_buildable },
  { "trigger_class",            SP_trigger_class },
  { "trigger_equipment",        SP_trigger_equipment },
  { "trigger_gravity",          SP_trigger_gravity },
  { "trigger_heal",             SP_trigger_heal },
  { "trigger_hurt",             SP_trigger_hurt },
  { "trigger_multiple",         SP_trigger_multiple },
  { "trigger_push",             SP_trigger_push },
  { "trigger_stage",            SP_trigger_stage },
  { "trigger_teleport",         SP_trigger_teleport },
  { "trigger_win",              SP_trigger_win }
};

/*
===============
G_CallSpawn

Finds the spawn function for the entity and calls it,
returning qfalse if not found
===============
*/
qboolean G_CallSpawn( gentity_t *ent )
{
  spawn_t     *s;
  buildable_t buildable;

  if( !ent->classname )
  {
    G_Printf( "G_CallSpawn: NULL classname\n" );
    return qfalse;
  }

  //check buildable spawn functions
  buildable = BG_BuildableByEntityName( ent->classname )->number;
  if( buildable != BA_NONE )
  {
    // don't spawn built-in buildings if we are using a custom layout
    if( level.layout[ 0 ] && Q_stricmp( level.layout, "*BUILTIN*" ) )
      return qfalse;

    if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN )
    {
      ent->s.angles[ YAW ] += 180.0f;
      AngleNormalize360( ent->s.angles[ YAW ] );
    }

    G_SpawnBuildable( ent, buildable );
    return qtrue;
  }

  // check normal spawn functions
  s = bsearch( ent->classname, spawns, sizeof( spawns ) / sizeof( spawn_t ),
    sizeof( spawn_t ), cmdcmp );
  if( s )
  {
    // found it
    s->spawn( ent );
    return qtrue;
  }

  G_Printf( "%s doesn't have a spawn function\n", ent->classname );
  return qfalse;
}

/*
=============
G_NewString

Builds a copy of the string, translating \n to real linefeeds
so message texts can be multi-line
=============
*/
char *G_NewString( const char *string )
{
  char  *newb, *new_p;
  int   i,l;

  l = strlen( string ) + 1;

  newb = BG_Alloc( l );

  new_p = newb;

  // turn \n into a real linefeed
  for( i = 0 ; i < l ; i++ )
  {
    if( string[ i ] == '\\' && i < l - 1 )
    {
      i++;
      if( string[ i ] == 'n' )
        *new_p++ = '\n';
      else
        *new_p++ = '\\';
    }
    else
      *new_p++ = string[ i ];
  }

  return newb;
}




/*
===============
G_ParseField

Takes a key/value pair and sets the binary values
in a gentity
===============
*/
void G_ParseField( const char *key, const char *value, gentity_t *ent )
{
  field_t *f;
  byte    *b;
  float   v;
  vec3_t  vec;
  vec4_t  vec4;

  f = bsearch( key, fields, sizeof( fields ) / sizeof( field_t ),
    sizeof( field_t ), cmdcmp );
  if( !f )
    return;
  b = (byte *)ent;

  switch( f->type )
  {
    case F_LSTRING:
      *(char **)( b + f->ofs ) = G_NewString( value );
      break;

    case F_VECTOR:
      sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] );

      ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ];
      ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ];
      ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ];
      break;

    case F_VECTOR4:
      sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] );

      ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ];
      ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ];
      ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ];
      ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ];
      break;

    case F_INT:
      *(int *)( b + f->ofs ) = atoi( value );
      break;

    case F_FLOAT:
      *(float *)( b + f->ofs ) = atof( value );
      break;

    case F_ANGLEHACK:
      v = atof( value );
      ( (float *)( b + f->ofs ) )[ 0 ] = 0;
      ( (float *)( b + f->ofs ) )[ 1 ] = v;
      ( (float *)( b + f->ofs ) )[ 2 ] = 0;
      break;

    default:
    case F_IGNORE:
      break;
  }
}




/*
===================
G_SpawnGEntityFromSpawnVars

Spawn an entity and fill in all of the level fields from
level.spawnVars[], then call the class specfic spawn function
===================
*/
void G_SpawnGEntityFromSpawnVars( void )
{
  int         i;
  gentity_t   *ent;

  // get the next free entity
  ent = G_Spawn( );

  for( i = 0 ; i < level.numSpawnVars ; i++ )
    G_ParseField( level.spawnVars[ i ][ 0 ], level.spawnVars[ i ][ 1 ], ent );

  G_SpawnInt( "notq3a", "0", &i );

  if( i )
  {
    G_FreeEntity( ent );
    return;
  }

  // move editor origin to pos
  VectorCopy( ent->s.origin, ent->s.pos.trBase );
  VectorCopy( ent->s.origin, ent->r.currentOrigin );

  // if we didn't get a classname, don't bother spawning anything
  if( !G_CallSpawn( ent ) )
    G_FreeEntity( ent );
}



/*
====================
G_AddSpawnVarToken
====================
*/
char *G_AddSpawnVarToken( const char *string )
{
  int   l;
  char  *dest;

  l = strlen( string );
  if( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS )
    G_Error( "G_AddSpawnVarToken: MAX_SPAWN_CHARS" );

  dest = level.spawnVarChars + level.numSpawnVarChars;
  memcpy( dest, string, l + 1 );

  level.numSpawnVarChars += l + 1;

  return dest;
}

/*
====================
G_ParseSpawnVars

Parses a brace bounded set of key / value pairs out of the
level's entity strings into level.spawnVars[]

This does not actually spawn an entity.
====================
*/
qboolean G_ParseSpawnVars( void )
{
  char keyname[ MAX_TOKEN_CHARS ];
  char com_token[ MAX_TOKEN_CHARS ];

  level.numSpawnVars = 0;
  level.numSpawnVarChars = 0;

  // parse the opening brace
  if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
  {
    // end of spawn string
    return qfalse;
  }

  if( com_token[ 0 ] != '{' )
    G_Error( "G_ParseSpawnVars: found %s when expecting {", com_token );

  // go through all the key / value pairs
  while( 1 )
  {
    // parse key
    if( !trap_GetEntityToken( keyname, sizeof( keyname ) ) )
      G_Error( "G_ParseSpawnVars: EOF without closing brace" );

    if( keyname[0] == '}' )
      break;

    // parse value
    if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
      G_Error( "G_ParseSpawnVars: EOF without closing brace" );

    if( com_token[0] == '}' )
      G_Error( "G_ParseSpawnVars: closing brace without data" );

    if( level.numSpawnVars == MAX_SPAWN_VARS )
      G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" );

    level.spawnVars[ level.numSpawnVars ][ 0 ] = G_AddSpawnVarToken( keyname );
    level.spawnVars[ level.numSpawnVars ][ 1 ] = G_AddSpawnVarToken( com_token );
    level.numSpawnVars++;
  }

  return qtrue;
}



/*QUAKED worldspawn (0 0 0) ?

Every map should have exactly one worldspawn.
"music"   music wav file
"gravity" 800 is default gravity
"message" Text to print during connection process
*/
void SP_worldspawn( void )
{
  char *s;

  G_SpawnString( "classname", "", &s );

  if( Q_stricmp( s, "worldspawn" ) )
    G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" );

  // make some data visible to connecting client
  trap_SetConfigstring( CS_GAME_VERSION, GAME_VERSION );

  trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) );

  G_SpawnString( "music", "", &s );
  trap_SetConfigstring( CS_MUSIC, s );

  G_SpawnString( "message", "", &s );
  trap_SetConfigstring( CS_MESSAGE, s );        // map specific message

  trap_SetConfigstring( CS_MOTD, g_motd.string );   // message of the day

  if( G_SpawnString( "gravity", "", &s ) )
    trap_Cvar_Set( "g_gravity", s );

  if( G_SpawnString( "humanMaxStage", "", &s ) )
    trap_Cvar_Set( "g_humanMaxStage", s );

  if( G_SpawnString( "alienMaxStage", "", &s ) )
    trap_Cvar_Set( "g_alienMaxStage", s );

  G_SpawnString( "disabledEquipment", "", &s );
  trap_Cvar_Set( "g_disabledEquipment", s );

  G_SpawnString( "disabledClasses", "", &s );
  trap_Cvar_Set( "g_disabledClasses", s );

  G_SpawnString( "disabledBuildables", "", &s );
  trap_Cvar_Set( "g_disabledBuildables", s );

  g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD;
  g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn";

  if( g_restarted.integer )
    trap_Cvar_Set( "g_restarted", "0" );

  // see if we want a warmup time
  trap_SetConfigstring( CS_WARMUP, "-1" );
  if( g_doWarmup.integer )
  {
    level.warmupTime = level.time - level.startTime + ( g_warmup.integer * 1000 );
    trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) );
    G_LogPrintf( "Warmup: %i\n", g_warmup.integer );
  }

}


/*
==============
G_SpawnEntitiesFromString

Parses textual entity definitions out of an entstring and spawns gentities.
==============
*/
void G_SpawnEntitiesFromString( void )
{
  level.numSpawnVars = 0;

  // the worldspawn is not an actual entity, but it still
  // has a "spawn" function to perform any global setup
  // needed by a level (setting configstrings or cvars, etc)
  if( !G_ParseSpawnVars( ) )
    G_Error( "SpawnEntities: no entities" );

  SP_worldspawn( );

  // parse ents
  while( G_ParseSpawnVars( ) )
    G_SpawnGEntityFromSpawnVars( );
}

