<?php
/**
 *    This file is part of "Liberty Gaming NQ Stats Analyzer".
 *
 *    "Liberty Gaming NQ Stats Analyzer" 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 3 of the License, or
 *    (at your option) any later version.
 *
 *    "Liberty Gaming NQ Stats Analyzer" 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, see <http://www.gnu.org/licenses/>.
 */


/**
 * Class PCPIN_Parser
 * Main game parser
 * @author Konstantin Reznichak <k.reznichak@pcpin.com>
 * @copyright Copyright &copy; 2007, Konstantin Reznichak
 */
class PCPIN_Parser extends PCPIN_Session {


  /**
   * Game data
   * @var array
   */
  var $game_data=null;

  /**
   * Game clients data (indexed by slot)
   * @var array
   */
  var $clients_slot=null;

  /**
   * Game clients data (indexed by GUID)
   * @var array
   */
  var $clients_guid=null;

  /**
   * Parsed games count
   * @var int
   */
  var $parsed_games=0;

  /**
   * Flag: Set to TRUE if ShutdownGame was executed
   * @var boolean
   */
  var $shutdowngame_executed=false;

  /**
   * Flag: TRUE if game is started
   * @var boolean
   */
  var $game_started=false;

  /**
   * Flag: TRUE after first kill
   * @var boolean
   */
  var $first_kill=false;

  /**
   * Flag: TRUE after first death
   * @var boolean
   */
  var $first_death=false;



  /**
   * Constructor
   * @param   object  &$sessionhandler  Session handler
   */
  function PCPIN_Parser(&$sessionhandler) {
    // Init object
    $this->_s_init($sessionhandler, $this);
  }


  /**
   * Parse single log file
   * @param   resource    &$fh    Opened file pointer
   */
  function parseFile(&$fh) {
    // Init vars
    $this->game_data=array();
    $this->clients_slot=array();
    $this->clients_guid=array();

    $this->game_started=false;
    $this->first_kill=false;
    $this->first_death=false;
    while (false!==$line=fgets($fh)) {
      $line=trim($line);
      if ($line=='') {
        continue;
      }

      // Parse line
      $time=substr($line, 0, strpos($line, ' '));
      $time_len=strlen($time)+1;
      $cmd=substr($line, $time_len);
      $cmd=substr($cmd, 0, strpos($cmd, ':'));
      $cmd_len=strlen($cmd)+1;
      $args=trim(substr($line, $time_len+$cmd_len+1));

      if ($cmd=='WeaponStats') {
        // Weapon statistics
        $space_pos=strpos($args, ' ');
        $slot=substr($args, 0, $space_pos);
        if (isset($this->clients_slot[$slot])) {
          $stats=$this->parseWeaponStats(substr($args, $space_pos+1));
          // Save weapon stats
          foreach ($stats['weapon_stats'] as $weapon_nr=>$ws) {
            if (!empty($ws)) {
              $gameclientws=new PCPIN_GameClientWS($this);
              $gameclientws->addRecord($this->clients_slot[$slot]->guid,
                                       $weapon_nr,
                                       $ws['hits'],
                                       $ws['shots'],
                                       $ws['kills'],
                                       $ws['deaths'],
                                       $ws['headshots']
                                       );
            }
          }
/*
          // Save damage stats
          $gameclientdamage=new PCPIN_GameClientDamage($this);
          $gameclientdamage->addRecord($this->clients_slot[$slot]->guid,
                                       $stats['damage_stats']['damage_given'],
                                       $stats['damage_stats']['damage_received'],
                                       $stats['damage_stats']['team_damage']
                                       );
*/
        }
        continue;
      } elseif (!$this->game_started && $cmd!='InitGame') {
        continue;
      }

      $time=$this->makeSeconds($time);
      switch ($cmd) {

        case 'Item':
          // Player taken some item
          $space_pos=strpos($args, ' ');
          $slot=substr($args, 0, $space_pos);
          if (!isset($this->clients_slot[$slot])) {
            $this->resetGameFlags();
            break;
          }
          $item=substr($args, $space_pos+1);
          if ($item=='team_CTF_redflag' || $item=='team_CTF_blueflag') {
            // Objectives
            $this->clients_slot[$slot]->tookObjectives();
          } elseif ($item=='item_health') {
            $this->clients_slot[$slot]->tookHealth();
          } elseif (substr($item, 0, 16)=='weapon_magicammo') {
            $this->clients_slot[$slot]->tookAmmo();
          }
        break;

        case 'Kill':
          // Player was killed
          $parts=explode(':', $args);
          $parts_a=explode(' ', trim($parts[0]));
          $parts_b=explode(' ', trim($parts[count($parts)-1]));
          $killer_slot=$parts_a[0];
          $victim_slot=$parts_a[1];
          $mod=$parts_b[count($parts_b)-1];
          if (!isset($this->clients_slot[$victim_slot])) {
            $this->resetGameFlags();
            break;
          }
          if ($killer_slot==$victim_slot) {
            // Suicide
            $this->clients_slot[$killer_slot]->madeSuicide($mod);
            if (!$this->first_death) {
              $this->clients_slot[$killer_slot]->setFirstDeathAward();
              $this->first_death=true;
            }
          } else {
            // A kill
            // Update killer data
            if (isset($this->clients_slot[$killer_slot])) {
              // Killed by player
              $this->clients_slot[$killer_slot]->madeKill($this->clients_slot[$victim_slot]->guid, $this->clients_slot[$victim_slot]->team, $mod);
              $this->clients_slot[$killer_slot]->addBounties(floor($this->clients_slot[$victim_slot]->current_kill_spree/5));
              if (!$this->first_kill && $this->clients_slot[$killer_slot]->team != $this->clients_slot[$victim_slot]->team) {
                $this->clients_slot[$killer_slot]->setFirstKillAward();
                $this->first_kill=true;
              }
              // Update victim
              $this->clients_slot[$victim_slot]->gotKilled($this->clients_slot[$killer_slot]->guid, $this->clients_slot[$killer_slot]->team, $mod);
              if (!$this->first_death && $this->clients_slot[$killer_slot]->team != $this->clients_slot[$victim_slot]->team) {
                $this->clients_slot[$victim_slot]->setFirstDeathAward();
                $this->first_death=true;
              }
            } else {
              // Killed by world
              $this->clients_slot[$victim_slot]->worldDeath($mod);
              if (!$this->first_death) {
                $this->clients_slot[$victim_slot]->setFirstDeathAward();
                $this->first_death=true;
              }
            }
          }
        break;

        case 'Medic_Revive':
          // Player revived someone
          $parts=explode(':', $args);
          $medic_slot=substr($args, 0, strpos($args, ' '));
          $victim_slot=substr($args, strpos($args, ' ')+1);
          if (!isset($this->clients_slot[$medic_slot]) || !isset($this->clients_slot[$victim_slot])) {
            $this->resetGameFlags();
            break;
          }
          $this->clients_slot[$medic_slot]->didRevive($this->clients_slot[$victim_slot]->guid);
        break;

        case 'Health_Pack':
          // Player gave health pack to someone
          $parts=explode(':', $args);
          $medic_slot=substr($args, 0, strpos($args, ' '));
          $victim_slot=substr($args, strpos($args, ' ')+1);
          if (!isset($this->clients_slot[$medic_slot]) || !isset($this->clients_slot[$victim_slot])) {
            $this->resetGameFlags();
            break;
          }
          $this->clients_slot[$medic_slot]->gaveHealthPack($this->clients_slot[$victim_slot]->guid);
        break;

        case 'Ammo_Pack':
          // Player gave ammo pack to someone
          $parts=explode(':', $args);
          $lieut_slot=substr($args, 0, strpos($args, ' '));
          $recv_slot=substr($args, strpos($args, ' ')+1);
          if (!isset($this->clients_slot[$lieut_slot]) || !isset($this->clients_slot[$recv_slot])) {
            $this->resetGameFlags();
            break;
          }
          $this->clients_slot[$lieut_slot]->gaveAmmoPack($this->clients_slot[$recv_slot]->guid);
        break;

        case 'ClientUserinfoChangedGUID':
          // Client data
          $slot=substr($args, 0, strpos($args, ' '));
          $slot_len=strlen($slot)+1;
          $guid=substr($args, $slot_len, 32);
          if (false!==strpos($guid, ' ') && substr($guid, 0, 7)!='OMNIBOT') {
            $guid=substr($guid, 0, strpos($guid, ' '));
          }
          $guid_len=strlen($guid);
          if (!isset($this->clients_guid[$guid])) {
            $this->clients_guid[$guid]=new PCPIN_PlayerData();
          }
          $this->clients_guid[$guid]->ClientUserinfoChanged($guid, substr($args, $slot_len+$guid_len+1), $time);
          $this->clients_slot[$slot]=&$this->clients_guid[$guid];
        break;

        case 'Dynamite_Plant':
          // Player planted a dynamite near obj
          if (!isset($this->clients_slot[$args])) {
            $this->resetGameFlags();
            break;
          }
          $this->clients_slot[$args]->dynamitePlant();
        break;

        case 'Dynamite_Diffuse':
          // Player diffused a dynamite
          if (!isset($this->clients_slot[$args])) {
            $this->resetGameFlags();
            break;
          }
          $this->clients_slot[$args]->dynamiteDifuse();
        break;

        case 'Repair':
          // Player repaired something
          if (!isset($this->clients_slot[$args])) {
            $this->resetGameFlags();
            break;
          }
          $this->clients_slot[$args]->madeRepair();
        break;

        case 'score':
          // Score
          $slot_start=strpos($args, ' client: ')+9;
          $slot=substr($args, $slot_start, strpos($args, ' ', $slot_start)-$slot_start);
          if (!isset($this->clients_slot[$slot])) {
            $this->resetGameFlags();
            break;
          }
          $ping_start=strpos($args, ' ping: ')+7;
          $this->clients_slot[$slot]->setScore(substr($args, 0, strpos($args, ' ')));
          $this->clients_slot[$slot]->setPing(substr($args, $ping_start, strpos($args, ' ', $ping_start)-$ping_start));
        break;


        case 'InitGame':
          // Match or Warmup started
          $this->resetGameFlags(true!==$this->shutdowngame_executed);
          $this->shutdowngame_executed=false;
          $this->game_started=true;
          $this->game_data=array('timer_start'=>$time);
          $this->parseInitGame($args);
          // Reset players stats
          foreach ($this->clients_guid as $playerdata) {
            $playerdata->resetStats($time);
          }
        break;

        case 'ClientDisconnect':
          // Client disconnected
          if (isset($this->clients_slot[$args])) {
            unset($this->clients_slot[$args]);
          }
        break;

        case 'ExitLevel':
          // Round ended
          // Stop all team/class timers
          foreach ($this->clients_guid as $playerdata) {
            $playerdata->toggleTeamClassTimer(-1, -1, $time);
          }
          // Store game data into database
          $game=new PCPIN_Game($this);
          $gameclient=new PCPIN_GameClient($this);
          $clientname=new PCPIN_ClientName($this);
          $gameclientteamclass=new PCPIN_GameClientTeamClass($this);
          $gameclientkill=new PCPIN_GameClientKill($this);
          $gameclientdeath=new PCPIN_GameClientDeath($this);
          $gameclientsuicide=new PCPIN_GameClientSuicide($this);
          $gameclientrevive=new PCPIN_GameClientRevive($this);
          $gameclienthealth=new PCPIN_GameClientHealth($this);
          $gameclientammo=new PCPIN_GameClientAmmo($this);
          $gameclientmisc=new PCPIN_GameClientMisc($this);

          $game->addGame($this->game_data['mapname'], $time-$this->game_data['timer_start'], count($this->clients_guid), 0);
          $game_id=$game->_db_lastInsertID();
          // Store game clients
          $active_players_count=0;
          foreach ($this->clients_guid as $playerdata) {
            if (isset($playerdata->team_class_timers[1]) || isset($playerdata->team_class_timers[2])) {
              $active_players_count++;
            }
            // Used names
            foreach ($playerdata->used_names as $name) {
              $clientname->addNameRecord($game_id, $playerdata->guid, $name);
            }
            // Teams/classes and times
            $time_axis=0;
            $time_allies=0;
            $time_spectator=0;
            foreach ($playerdata->team_class_timers as $team=>$timers) {
              foreach ($timers as $class=>$time) {
                if ($time>0) {
                  $gameclientteamclass->addRecord($game_id, $playerdata->guid, $team, $class, $time);
                  if ($team==1) {
                    // AXIS
                    $time_axis+=$time;
                  } elseif ($team==2) {
                    // ALLIES
                    $time_allies+=$time;
                  } else {
                    // SPECTATOR
                    $time_spectator+=$time;
                  }
                }
              }
            }
            // Kills made by client
            $killed_enemies=0;
            $killed_teammates=0;
            foreach ($playerdata->killed_enemies as $team=>$classes) {
              foreach ($classes as $class=>$victims) {
                foreach ($victims as $guid=>$kills) {
                  foreach ($kills as $mod=>$count) {
                    $gameclientkill->addRecord($game_id, $playerdata->guid, $class, $guid, $team, 'y', $mod, $count);
                    $killed_enemies+=$count;
                  }
                }
              }
            }
            foreach ($playerdata->killed_teammates as $team=>$classes) {
              foreach ($classes as $class=>$victims) {
                foreach ($victims as $guid=>$kills) {
                  foreach ($kills as $mod=>$count) {
                    $gameclientkill->addRecord($game_id, $playerdata->guid, $class, $guid, $team, 'n', $mod, $count);
                    $killed_teammates+=$count;
                  }
                }
              }
            }
            // Deaths
            $killed_by_enemies_count=0;
            $killed_by_teammates_count=0;
            $killed_by_world_count=0;
            $suicides_count=0;
            foreach ($playerdata->killed_by_enemies as $team=>$deaths) {
              foreach ($deaths as $guid=>$kills) {
                foreach ($kills as $mod=>$count) {
                  $gameclientdeath->addRecord($game_id, $playerdata->guid, $guid, $team, 'y', $mod, $count);
                  $killed_by_enemies_count+=$count;
                }
              }
            }
            foreach ($playerdata->killed_by_teammates as $team=>$deaths) {
              foreach ($deaths as $guid=>$kills) {
                foreach ($kills as $mod=>$count) {
                  $gameclientdeath->addRecord($game_id, $playerdata->guid, $guid, $team, 'n', $mod, $count);
                  $killed_by_teammates_count+=$count;
                }
              }
            }
            // World deaths
            foreach ($playerdata->world_deaths as $team=>$deaths) {
              foreach ($deaths as $mod=>$count) {
                $gameclientdeath->addRecord($game_id, $playerdata->guid, '<world>', $team, 'w', $mod, $count);
                $killed_by_world_count+=$count;
              }
            }
            // Suicides
            foreach ($playerdata->suicides as $team=>$suicides) {
              foreach ($suicides as $mod=>$count) {
                $gameclientsuicide->addRecord($game_id, $playerdata->guid, $team, $mod, $count);
                $suicides_count+=$count;
              }
            }
            // Revives
            $revives_count=0;
            foreach ($playerdata->revives as $team=>$victims) {
              foreach ($victims as $guid=>$count) {
                $gameclientrevive->addRecord($game_id, $playerdata->guid, $guid, $team, $count);
                $revives_count+=$count;
              }
            }
            // Health given
            $health_given_count=0;
            foreach ($playerdata->health_given as $team=>$victims) {
              foreach ($victims as $guid=>$count) {
                $gameclienthealth->addRecord($game_id, $playerdata->guid, $guid, $team, $count);
                $health_given_count+=$count;
              }
            }
            // Ammo given
            $ammo_given_count=0;
            foreach ($playerdata->ammo_given as $team=>$victims) {
              foreach ($victims as $guid=>$count) {
                $gameclientammo->addRecord($game_id, $playerdata->guid, $guid, $team, $count);
                $ammo_given_count+=$count;
              }
            }
            // Misc data
            $dynamite_plant_count=0;
            $dynamite_diffuse_count=0;
            $repairs_count=0;
            $disguises_count=0;
            $objectives_taken_count=0;
            $teams=array(1, 2);
            foreach ($teams as $team) {
              if (   !empty($playerdata->dynamite_plant[$team])
                  || !empty($playerdata->dynamite_diffuse[$team])
                  || !empty($playerdata->repairs[$team])
                  || !empty($playerdata->disguises[$team])
                  || !empty($playerdata->objectives_taken[$team])
                  ) {
                $gameclientmisc->addRecord($game_id,
                                           $playerdata->guid,
                                           $team,
                                           !empty($playerdata->dynamite_plant[$team])? $playerdata->dynamite_plant[$team] : 0,
                                           !empty($playerdata->dynamite_diffuse[$team])? $playerdata->dynamite_diffuse[$team] : 0,
                                           !empty($playerdata->repairs[$team])? $playerdata->repairs[$team] : 0,
                                           !empty($playerdata->disguises[$team])? $playerdata->disguises[$team] : 0,
                                           !empty($playerdata->objectives_taken[$team])? $playerdata->objectives_taken[$team] : 0
                                           );
                $dynamite_plant_count+=!empty($playerdata->dynamite_plant[$team])? $playerdata->dynamite_plant[$team] : 0;
                $dynamite_diffuse_count+=!empty($playerdata->dynamite_diffuse[$team])? $playerdata->dynamite_diffuse[$team] : 0;
                $repairs_count+=!empty($playerdata->repairs[$team])? $playerdata->repairs[$team] : 0;
                $disguises_count+=!empty($playerdata->disguises[$team])? $playerdata->disguises[$team] : 0;
                $objectives_taken_count+=!empty($playerdata->objectives_taken[$team])? $playerdata->objectives_taken[$team] : 0;
              }
            }
            // Kill sprees
            $playerdata->killSpreeEnded();
            // Death sprees
            $playerdata->deathSpreeEnded();
            // Store game client
            $gameclient->addClient($game_id,
                                   $playerdata->guid,
                                   $time_axis,
                                   $time_allies,
                                   $time_spectator,
                                   $killed_enemies,
                                   $killed_teammates,
                                   $killed_by_enemies_count,
                                   $killed_by_teammates_count,
                                   $killed_by_world_count,
                                   $suicides_count,
                                   $playerdata->score,
                                   $playerdata->ping,
                                   $revives_count,
                                   $health_given_count,
                                   $ammo_given_count,
                                   $playerdata->health_taken_count,
                                   $playerdata->ammo_taken_count,
                                   $dynamite_plant_count,
                                   $dynamite_diffuse_count,
                                   $repairs_count,
                                   $disguises_count,
                                   $objectives_taken_count,
                                   $playerdata->first_kill,
                                   $playerdata->first_death,
                                   $playerdata->kill_sprees,
                                   $playerdata->longest_kill_spree,
                                   $playerdata->death_sprees,
                                   $playerdata->longest_death_spree,
                                   $playerdata->bounties,
                                   $playerdata->engineer_kills,
                                   $playerdata->fieldops_kills,
                                   $playerdata->covertops_kills
                                   );

          }
          // Store active game players count
          if ($active_players_count>0) {
            $game->_db_updateRow($game_id, 'id', array('players'=>$active_players_count));
          }
          // Stop the game
          $this->resetGameFlags();
          // Increase parsed games counter
          $this->parsed_games++;
        break;

        case 'ShutdownGame':
          // Reset game flags
          $this->resetGameFlags(false);
          $this->shutdowngame_executed=true;
        break;

      }
    }
  }


  /**
   * Reset game flags
   * @param   boolean   $reset_slots    Optional. Default: TRUE. If TRUE, then player slots will be also deleted.
   */
  function resetGameFlags($reset_slots=true) {
    // Stop the game
    $this->game_started=false;
    // Reset "first kill" and "first death" flags
    $this->first_kill=false;
    $this->first_death=false;
    if ($reset_slots) {
      // Reset slots
      $this->clients_slot=array();
      $this->clients_guid=array();
    }
  }


  /**
   * Parse InitGame record
   * @param   string    $args   InitGame arguments
   */
  function parseInitGame($args) {
    $pairs=explode('\\', ltrim($args, '\\'));
    $pairs_num=count($pairs);
    for ($i=0; $i<$pairs_num; $i+=2) {
      $this->game_data[$pairs[$i]]=$pairs[$i+1];
    }
  }


  /**
   * Parse weaponstats record
   * @param   string    $ws   WS line
   * @return  array
   */
  function parseWeaponStats($ws) {
    $stats=array('weapon_stats'=>array(),
                 'damage_stats'=>array(),
                 'skillpoints'=>array(),
                 );
    $parts=explode(' ', $ws);
    array_shift($parts);
    $weapon_bits=getBits(array_shift($parts));
    if (!empty($weapon_bits)) {
      // There are some weaponstats
      foreach ($weapon_bits as $weapon_nr) {
        $stats['weapon_stats'][$weapon_nr]=array('hits'=>array_shift($parts),
                                                 'shots'=>array_shift($parts),
                                                 'kills'=>array_shift($parts),
                                                 'deaths'=>array_shift($parts),
                                                 'headshots'=>array_shift($parts)
                                                 );
      }
    }
/*
    if (count($parts)>=3) {
      // Damage stats
      $stats['damage_stats']=array('damage_given'=>array_shift($parts),
                                   'damage_received'=>array_shift($parts),
                                   'team_damage'=>array_shift($parts)
                                   );
    }
    if (!empty($parts)) {
      // Parse skillpoints
      $stats['skillpoints']=array();
      $skill_bits=getBits(array_shift($parts));
      foreach ($skill_bits as $skill_nr) {
        $stats['skillpoints'][$skill_nr]=array_shift($parts);
      }
    }
*/
    return $stats;
  }


  /**
   * Convert time from format HH:MM:SS into seconds
   * @param   string    $time   Timestamp in format HH:MM:SS
   * @return int
   */
  function makeSeconds($time) {
    $seconds=0;
    $parts=explode(':', trim($time));
    foreach ($parts as $part) {
      $seconds=$seconds*60+$part;
    }
    return $seconds;
  }

}
?>