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

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
===========================================================================
*/

// bg_voice.c -- both games voice functions
#include "../qcommon/q_shared.h"
#include "bg_public.h"
#include "bg_local.h"

int  trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
int  trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
int trap_Parse_LoadSource( const char *filename );
int trap_Parse_FreeSource( int handle );
int trap_Parse_ReadToken( int handle, pc_token_t *pc_token );
int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line );

#ifdef CGAME
sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed );
int trap_S_SoundDuration( sfxHandle_t handle );
#endif


/*
============
BG_VoiceParseError
============
*/
static void BG_VoiceParseError( fileHandle_t handle, char *err )
{
  int line;
  char filename[ MAX_QPATH ];

  trap_Parse_SourceFileAndLine( handle, filename, &line );
  trap_Parse_FreeSource( handle );
  Com_Error( ERR_FATAL, "%s on line %d of %s\n", err, line, filename );
}

/*
============
BG_VoiceList
============
*/
static voice_t *BG_VoiceList( void )
{
  char fileList[ MAX_VOICES * ( MAX_VOICE_NAME_LEN + 8 ) ] = {""};
  int numFiles, i, fileLen = 0;
  int count = 0;
  char *filePtr;
  voice_t *voices = NULL;
  voice_t *top = NULL;

  numFiles = trap_FS_GetFileList( "voice", ".voice", fileList,
    sizeof( fileList ) );

  if( numFiles < 1 )
    return NULL;

  // special case for default.voice.  this file is REQUIRED and will
  // always be loaded first in the event of overflow of voice definitions
  if( !trap_FS_FOpenFile( "voice/default.voice", NULL, FS_READ ) )
  {
    Com_Printf( "voice/default.voice missing, voice system disabled." );
    return NULL;
  }
      
  voices = (voice_t*)BG_Alloc( sizeof( voice_t ) );
  Q_strncpyz( voices->name, "default", sizeof( voices->name ) );
  voices->cmds = NULL;
  voices->next = NULL;
  count = 1;

  top = voices;

  filePtr = fileList;
  for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
  {
    fileLen = strlen( filePtr );

    // accounted for above
    if( !Q_stricmp( filePtr, "default.voice" ) )
      continue;

    if( fileLen > MAX_VOICE_NAME_LEN + 8 ) {
      Com_Printf( S_COLOR_YELLOW "WARNING: MAX_VOICE_NAME_LEN is %d. "
        "skipping \"%s\", filename too long", MAX_VOICE_NAME_LEN, filePtr );
      continue;
    }

    // trap_FS_GetFileList() buffer has overflowed
    if( !trap_FS_FOpenFile( va( "voice/%s", filePtr ), NULL, FS_READ ) )
    {
      Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceList(): detected "
        "an invalid .voice file \"%s\" in directory listing.  You have "
        "probably named one or more .voice files with outrageously long "
        "names.  gjbs", filePtr );
      break;
    }

    if( count >= MAX_VOICES )
    {
      Com_Printf( S_COLOR_YELLOW "WARNING: .voice file overflow.  "
        "%d of %d .voice files loaded.  MAX_VOICES is %d",
        count, numFiles, MAX_VOICES );
      break;
    }
 
    voices->next = (voice_t*)BG_Alloc( sizeof( voice_t ) );
    voices = voices->next;

    Q_strncpyz( voices->name, filePtr, sizeof( voices->name ) );
    // strip extension 
    voices->name[ fileLen - 6 ] = '\0';
    voices->cmds = NULL;
    voices->next = NULL;
    count++;
  }
  return top;
}

/*
============
BG_VoiceParseTrack
============
*/
static qboolean BG_VoiceParseTrack( int handle, voiceTrack_t *voiceTrack )
{
  pc_token_t token;
  qboolean found = qfalse;
  qboolean foundText = qfalse;
  qboolean foundToken = qfalse;

  foundToken = trap_Parse_ReadToken( handle, &token );
  while( foundToken )
  {
    if( token.string[ 0 ] == '}' )
    {
      if( foundText )
        return qtrue;
      else
      {
        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): "
          "missing text attribute for track" );
      }
    }
    else if( !Q_stricmp( token.string, "team" ) )
    {
      foundToken = trap_Parse_ReadToken( handle, &token );
      found = qfalse;
      while( foundToken && token.type == TT_NUMBER )
      {
        found = qtrue;
        if( voiceTrack->team < 0 )
          voiceTrack->team = 0;
        voiceTrack->team |= ( 1 << token.intvalue );
        foundToken = trap_Parse_ReadToken( handle, &token );
      }
      if( !found )
      {
        BG_VoiceParseError( handle,
          "BG_VoiceParseTrack(): missing \"team\" value" );
      }
      continue;
    }
    else if( !Q_stricmp( token.string, "class" ) )
    {
      foundToken = trap_Parse_ReadToken( handle, &token );
      found = qfalse;
      while( foundToken && token.type == TT_NUMBER )
      {
        found = qtrue;
        if( voiceTrack->class < 0 )
          voiceTrack->class = 0;
        voiceTrack->class |= ( 1 << token.intvalue );
        foundToken = trap_Parse_ReadToken( handle, &token );
      }
      if( !found )
      {
        BG_VoiceParseError( handle, 
          "BG_VoiceParseTrack(): missing \"class\" value" );
      }
      continue;
    }
    else if( !Q_stricmp( token.string, "weapon" ) )
    {
      foundToken = trap_Parse_ReadToken( handle, &token );
      found = qfalse;
      while( foundToken && token.type == TT_NUMBER )
      {
        found = qtrue;
        if( voiceTrack->weapon < 0 )
          voiceTrack->weapon = 0;
        voiceTrack->weapon |= ( 1 << token.intvalue );
        foundToken = trap_Parse_ReadToken( handle, &token );
      }
      if( !found )
      {
        BG_VoiceParseError( handle,
          "BG_VoiceParseTrack(): missing \"weapon\" value");
      }
      continue;
    }
    else if( !Q_stricmp( token.string, "text" ) )
    {
      if( foundText )
      {
        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): "
          "duplicate \"text\" definition for track" );
      }
      foundToken = trap_Parse_ReadToken( handle, &token );
      if( !foundToken )
      {
        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): "
          "missing \"text\" value" );
      }
      foundText = qtrue;
      if( strlen( token.string ) >= MAX_SAY_TEXT )
      {
        BG_VoiceParseError( handle, va( "BG_VoiceParseTrack(): "
          "\"text\" value " "\"%s\" exceeds MAX_SAY_TEXT length",
          token.string ) );
      }

      voiceTrack->text = (char *)BG_Alloc( strlen( token.string ) + 1 );
      Q_strncpyz( voiceTrack->text, token.string, strlen( token.string ) + 1 );
      foundToken = trap_Parse_ReadToken( handle, &token );
      continue;
    }
    else if( !Q_stricmp( token.string, "enthusiasm" ) )
    {
      foundToken = trap_Parse_ReadToken( handle, &token );
      if( token.type == TT_NUMBER )
      {
        voiceTrack->enthusiasm = token.intvalue;
      }
      else
      {
        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): "
          "missing \"enthusiasm\" value" );
      }
      foundToken = trap_Parse_ReadToken( handle, &token );
      continue;
    }
    else
    {
        BG_VoiceParseError( handle, va( "BG_VoiceParseTrack():"
          " unknown token \"%s\"", token.string ) );
    }
  }
  return qfalse;
}

/*
============
BG_VoiceParseCommand
============
*/
static voiceTrack_t *BG_VoiceParseCommand( int handle )
{
  pc_token_t token;
  qboolean parsingTrack = qfalse;
  voiceTrack_t *voiceTracks = NULL;
  voiceTrack_t *top = NULL;
  
  while( trap_Parse_ReadToken( handle, &token ) )
  {
    if( !parsingTrack && token.string[ 0 ] == '}' )
      return top;

    if( parsingTrack )
    {
      if( token.string[ 0 ] == '{' )
      {
        BG_VoiceParseTrack( handle, voiceTracks );
        parsingTrack = qfalse;
        continue;
        
      }
      else
      {
        BG_VoiceParseError( handle, va( "BG_VoiceParseCommand(): "
          "parse error at \"%s\"", token.string ) );
      }
    }


    if( top == NULL )
    {
      voiceTracks = BG_Alloc( sizeof( voiceTrack_t ) );
      top = voiceTracks;
    }
    else
    {
      voiceTracks->next = BG_Alloc( sizeof( voiceCmd_t ) );
      voiceTracks = voiceTracks->next;
    }
    
    if( !trap_FS_FOpenFile( token.string, NULL, FS_READ ) )
    {
        int line;
        char filename[ MAX_QPATH ];

        trap_Parse_SourceFileAndLine( handle, filename, &line );
        Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceParseCommand(): "
          "track \"%s\" referenced on line %d of %s does not exist\n",
          token.string, line, filename );
    }
    else
    {
#ifdef CGAME
      voiceTracks->track = trap_S_RegisterSound( token.string, qfalse );
      voiceTracks->duration = trap_S_SoundDuration( voiceTracks->track );
#endif
    }

    voiceTracks->team = -1;
    voiceTracks->class = -1;
    voiceTracks->weapon = -1;
    voiceTracks->enthusiasm = 0;
    voiceTracks->text = NULL;
    voiceTracks->next = NULL;
    parsingTrack = qtrue;

  }
  return NULL;
}

/*
============
BG_VoiceParse
============
*/
static voiceCmd_t *BG_VoiceParse( char *name )
{
  voiceCmd_t *voiceCmds = NULL;
  voiceCmd_t *top = NULL;
  pc_token_t token;
  qboolean parsingCmd = qfalse;
  int handle;
  
  handle = trap_Parse_LoadSource( va( "voice/%s.voice", name ) );
  if( !handle )
    return NULL;

  while( trap_Parse_ReadToken( handle, &token ) )
  {
    if( parsingCmd )
    {
      if( token.string[ 0 ] == '{' )
      {
        voiceCmds->tracks = BG_VoiceParseCommand( handle );
        parsingCmd = qfalse;
	continue;
      }
      else
      {
        int line;
        char filename[ MAX_QPATH ];

        trap_Parse_SourceFileAndLine( handle, filename, &line );
        Com_Error( ERR_FATAL, "BG_VoiceParse(): "
          "parse error on line %d of %s\n", line, filename );
      }
    }
      
    if( strlen( token.string ) >= MAX_VOICE_CMD_LEN )
    {
        int line;
        char filename[ MAX_QPATH ];

        trap_Parse_SourceFileAndLine( handle, filename, &line );
        Com_Error( ERR_FATAL, "BG_VoiceParse(): "
          "command \"%s\" exceeds MAX_VOICE_CMD_LEN (%d) on line %d of %s\n",
          token.string, MAX_VOICE_CMD_LEN, line, filename );
    }
   
    if( top == NULL )
    {
      voiceCmds = BG_Alloc( sizeof( voiceCmd_t ) );
      top = voiceCmds;
    }
    else
    {
      voiceCmds->next = BG_Alloc( sizeof( voiceCmd_t ) );
      voiceCmds = voiceCmds->next;
    }

    Q_strncpyz( voiceCmds->cmd, token.string, sizeof( voiceCmds->cmd ) );
    voiceCmds->next = NULL;
    parsingCmd = qtrue;

  } 

  trap_Parse_FreeSource( handle );
  
  return top;
}

/*
============
BG_VoiceInit
============
*/
voice_t *BG_VoiceInit( void )
{
  voice_t *voices;
  voice_t *voice;

  voices = BG_VoiceList();

  voice = voices;
  while( voice )
  {
    voice->cmds = BG_VoiceParse( voice->name );
    voice = voice->next;
  }

  return voices;
}


/*
============
BG_PrintVoices
============
*/
void BG_PrintVoices( voice_t *voices, int debugLevel )
{
  voice_t *voice = voices;
  voiceCmd_t *voiceCmd;
  voiceTrack_t *voiceTrack;
  
  int cmdCount;
  int trackCount;

  if( voice == NULL )
  {
    Com_Printf( "voice list is empty\n" );
    return;
  }

  while( voice != NULL )
  {
    if( debugLevel > 0 )
      Com_Printf( "voice \"%s\"\n", voice->name );
    voiceCmd = voice->cmds;
    cmdCount = 0;
    trackCount = 0;
    while( voiceCmd != NULL )
    {
      if( debugLevel > 0 )
        Com_Printf( "  %s\n", voiceCmd->cmd );
      voiceTrack = voiceCmd->tracks;
      cmdCount++;
      while ( voiceTrack != NULL )
      {
        if( debugLevel > 1 )
          Com_Printf( "    text -> %s\n", voiceTrack->text );
        if( debugLevel > 2 )
        {
          Com_Printf( "    team -> %d\n", voiceTrack->team );
          Com_Printf( "    class -> %d\n", voiceTrack->class );
          Com_Printf( "    weapon -> %d\n", voiceTrack->weapon );
          Com_Printf( "    enthusiasm -> %d\n", voiceTrack->enthusiasm );
#ifdef CGAME
          Com_Printf( "    duration -> %d\n", voiceTrack->duration );
#endif
        }
        if( debugLevel > 1 )
          Com_Printf( "\n" );
        trackCount++; 
        voiceTrack = voiceTrack->next;
      }
      voiceCmd = voiceCmd->next;
    }

    if( !debugLevel )
    {
      Com_Printf( "voice \"%s\": %d commands, %d tracks\n",
        voice->name, cmdCount, trackCount );
    }
    voice = voice->next;
  }
}

/*
============
BG_VoiceByName
============
*/
voice_t *BG_VoiceByName( voice_t *head, char *name )
{
  voice_t *v = head;

  while( v )
  {
    if( !Q_stricmp( v->name, name ) )
      return v;
    v = v->next;
  }
  return NULL;
}

/*
============
BG_VoiceCmdFind
============
*/
voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum )
{
  voiceCmd_t *vc = head;
  int i = 0;

  while( vc )
  {
    i++;
    if( !Q_stricmp( vc->cmd, name ) )
    {
      *cmdNum = i;
      return vc;
    }
    vc = vc->next;
  }
  return NULL;
}

/*
============
BG_VoiceCmdByNum
============
*/
voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num )
{
  voiceCmd_t *vc = head;
  int i = 0;

  while( vc )
  {
    i++;
    if( i == num )
      return vc;
    vc = vc->next;
  }
  return NULL;
}

/*
============
BG_VoiceTrackByNum
============
*/
voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num )
{
  voiceTrack_t *vt = head;
  int i = 0;

  while( vt )
  {
    i++;
    if( i == num )
      return vt;
    vt = vt->next;
  }
  return NULL;
}

/*
============
BG_VoiceTrackFind
============
*/
voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team,
                                 class_t class, weapon_t weapon,
                                 int enthusiasm, int *trackNum )
{
  voiceTrack_t *vt = head;
  int highestMatch = 0;
  int matchCount = 0;
  int selectedMatch = 0;
  int i = 0;
  int j = 0;
  
  // find highest enthusiasm without going over
  while( vt )
  {
    if( ( vt->team >= 0 && !( vt->team  & ( 1 << team ) ) ) || 
        ( vt->class >= 0 && !( vt->class & ( 1 << class ) ) ) || 
        ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) ||
        vt->enthusiasm > enthusiasm )
    {
      vt = vt->next;
      continue;
    }

    if( vt->enthusiasm > highestMatch )
    {
      matchCount = 0;
      highestMatch = vt->enthusiasm; 
    }
    if( vt->enthusiasm == highestMatch )
      matchCount++;
    vt = vt->next;
  }

  if( !matchCount )
    return NULL;

  // return randomly selected match
  selectedMatch = rand() % matchCount;
  vt = head;
  i = 0;
  j = 0;
  while( vt )
  {
    j++;
    if( ( vt->team >= 0 && !( vt->team  & ( 1 << team ) ) ) || 
        ( vt->class >= 0 && !( vt->class & ( 1 << class ) ) ) || 
        ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) ||
        vt->enthusiasm != highestMatch )
    {
      vt = vt->next;
      continue;
    }
    if( i == selectedMatch )
    {
      *trackNum = j;
      return vt; 
    }
    i++;
    vt = vt->next;
  }
  return NULL;
}
