function showCompetitionResultsByPerson($resultsTable = 'Results')
{
    #----------------------------------------------------------------------
    global $chosenByPerson, $chosenAllResults, $chosenTop3, $chosenWinners;
    global $chosenCompetitionId;
    #--- Get the results.
    $competitionResults = getCompetitionResults($resultsTable);
    startTimer();
    tableBegin('results', 8);
    foreach ($competitionResults as $result) {
        extract($result);
        $isNewPerson = !isset($previousPersonId) || $personId != $previousPersonId;
        $isNewEvent = !isset($previousEventId) || $eventId != $previousEventId || $isNewPerson;
        #--- Welcome new persons.
        if ($isNewPerson) {
            if (isset($previousPersonId)) {
                tableRowBlank();
            }
            $bo3_as_mo3 = $formatId == '3' && ($eventId == '333bf' || $eventId == '333fm' || $eventId == '333ft');
            $headerAverage = $formatId == 'a' || $formatId == 'm' || $bo3_as_mo3 ? 'Average' : '';
            $headerAllResults = $formatId != '1' ? 'Result Details' : '';
            tableCaptionNew(false, $personId, spaced(array(personLink($personId, $personName), $countryName)));
            tableHeader(explode('|', "Event|Round|Place|Best||{$headerAverage}||{$headerAllResults}"), array(2 => 'class="r"', 3 => 'class="R"', 5 => 'class="R"', 7 => 'class="f"'));
        }
        #--- One result row.
        tableRowStyled($isNewEvent ? '' : 'color:#AAA', array($isNewEvent ? eventLink($eventId, $eventCellName) : '', $roundCellName, $isNewEvent ? "<b>{$pos}</b>" : $pos, formatValue($best, $valueFormat), $regionalSingleRecord, formatValue($average, $valueFormat), $regionalAverageRecord, formatAverageSources($formatId != '1', $result, $valueFormat)));
        $previousPersonId = $personId;
        $previousEventId = $eventId;
    }
    tableEnd();
    stopTimer("printing the huge table");
}
function showResultsByEvents()
{
    #----------------------------------------------------------------------
    global $chosenPersonId;
    $results = dbQuery("\n    SELECT\n                           result.*,\n      event.name           eventName,\n      competition.cellName competitionCellName,\n      event.format         valueFormat,\n      round.cellName       roundCellName\n    FROM\n      Results result,\n      Events  event,\n      Competitions competition,\n      Rounds round\n    WHERE " . randomDebug() . "\n      AND personId = '{$chosenPersonId}'\n      AND event.id = eventId\n      AND event.rank < 1000\n      AND competition.id = competitionId\n      AND round.id = roundId\n    ORDER BY\n      event.rank, year DESC, month DESC, day DESC, competitionCellName, round.rank DESC\n  ");
    tableBegin('results', 8);
    tableCaption(false, "History (<a href='person_map.php?i={$chosenPersonId}'>Map</a>)");
    #--- Process results by event.
    foreach (structureBy($results, 'eventId') as $eventResults) {
        extract($eventResults[0]);
        #--- Announce the event.
        tableCaptionNew(false, $eventId, eventLink($eventId, $eventName));
        tableHeader(explode('|', 'Competition|Round|Place|Best||Average||Result Details'), array(2 => 'class="r"', 3 => 'class="R"', 5 => 'class="R"', 7 => 'class="f"'));
        #--- Initialize.
        $currentCompetitionId = '';
        #--- Compute PB Markers
        //$pbMarkers = [];
        $bestBest = 9999999999;
        $bestAverage = 9999999999;
        foreach (array_reverse($eventResults) as $result) {
            extract($result);
            $pbMarkers[$competitionId][$roundCellName] = 0;
            if ($best > 0 && $best <= $bestBest) {
                $bestBest = $best;
                $pbMarkers[$competitionId][$roundCellName] += 1;
            }
            if ($average > 0 && $average <= $bestAverage) {
                $bestAverage = $average;
                $pbMarkers[$competitionId][$roundCellName] += 2;
            }
        }
        #--- Show the results.
        foreach ($eventResults as $result) {
            extract($result);
            $isNewCompetition = $competitionId != $currentCompetitionId;
            $currentCompetitionId = $competitionId;
            $formatBest = formatValue($best, $valueFormat);
            if ($pbMarkers[$competitionId][$roundCellName] % 2) {
                $formatBest = "<span style='color:#F60;font-weight:bold'>{$formatBest}</span>";
            }
            $formatAverage = formatValue($average, $valueFormat);
            if ($pbMarkers[$competitionId][$roundCellName] > 1) {
                $formatAverage = "<span style='color:#F60;font-weight:bold'>{$formatAverage}</span>";
            }
            tableRowStyled($isNewCompetition ? '' : 'color:#AAA', array($isNewCompetition ? competitionLink($competitionId, $competitionCellName) : '', $roundCellName, $isNewCompetition ? "<b>{$pos}</b>" : $pos, $formatBest, $regionalSingleRecord, $formatAverage, $regionalAverageRecord, formatAverageSources(true, $result, $valueFormat)));
        }
    }
    tableEnd();
}
function showUnfinishedPersons () {
#----------------------------------------------------------------------
  global $personsFromPersons, $personsFromResultsWithoutId, $birthdates;

  #--- Pre-compute the candidate tuples: (id, name, countryId, romanName, romanNameSimilarityPlaceHolder, countryIdSimilarityPlaceHolder)
  $candidates = array();
  foreach( $personsFromPersons as $person ){
    list( $id, $name, $countryId ) = $person;
    $candidates[] = array( $id, $name, $countryId, extractRomanName($name), 0, 0 );
  }

  #--- Begin the form and table.
  echo "<form action='persons_finish_unfinished_ACTION.php' method='post'>";
  tableBegin( 'results', 8 );
  tableHeader( explode( '|', '|personName|countryId|personId|birthdate|personName|countryId|personSemiId' ),
               array( 6=>'class="6"' ) );

  #--- Walk over all persons from the Results table.
  $caseNr = 0;
  foreach( $personsFromResultsWithoutId as $person ){
    list( $name, $countryId, $firstYear ) = $person;
    
    #--- Try to compute the semi-id.
    $quarterId = removeUglyAccentsAndStuff( extractRomanName( $name ));
    $quarterId = preg_replace( '/[^a-zA-Z ]/', '', $quarterId );
    $quarterId = strtoupper( substr( preg_replace( '/(.*)\s(.*)/', '$2$1', $quarterId ), 0, 4 ));
    if ( strlen ( $quarterId ) == 0 ) {
      // if the name comes empty, invent a quarterId
      $quarterId = 'XXXX';
    } else if ( strlen( $quarterId ) < 4 ) {
      // make sure the quarterId is 4-letter long
      while ( strlen( $quarterId ) < 4 ) {
        $quarterId .= $quarterId;
      }
      $quarterId = substr( $quarterId, 0, 4 );
    }
    $semiId = $firstYear . $quarterId;

    #--- Html-ify name and country.
    $nameHtml = htmlEscape( $name );
    $countryIdHtml = htmlEscape( $countryId );

    #--- Hidden field describing the case.
    $caseNr++;
    tableRowFull( "&nbsp;<input type='hidden' name='oldNameAndCountry$caseNr' value='$nameHtml|$countryIdHtml' />" );
    
    #--- Show the person.
    # Note that we set this input to checked, but if there's a better match
    # lower on, then it will take precendence.
    tableRowStyled( 'font-weight:bold', array(
      "<input type='radio' name='action$caseNr' value='new' checked='checked' />",
      visualize( $name ),
      visualize( $countryId ),
      peekLink( $name, $countryId ),
      'mm/dd/yyyy',
      "<input type='text' name='name$caseNr' value='$nameHtml' size='20' />",
      "<input type='text' name='country$caseNr' value='$countryIdHtml' size='20' />",
      "<input type='text' name='semiId$caseNr' value='$semiId' size='10' maxlength='8' />",
    ));

    #--- Show most similar persons.
    $similarsCtr = 0;
    foreach( getMostSimilarPersonsMax( extractRomanName($name), $countryId, $candidates, 10 ) as $similarPerson ){
      list( $other_id, $other_name, $other_countryId ) = $similarPerson;
      
      #--- If name and country match the unfinished persons, pre-select it.
      $checked = ($other_name==$name && $other_countryId==$countryId)
        ? "checked='checked'" : '';
        
      #--- Skip the unfinished person itself. 
      if( $checked && !$other_id )
        continue;

      #--- Html-ify.
      $nameHtml = htmlEscape( $other_name );
      $countryHtml = htmlEscape( $other_countryId );
      $idHtml = htmlEscape( $other_id );
      
      #--- Use "name|country|id" as action.
      $action = "$nameHtml|$countryHtml|$idHtml";
      
      #--- Show the other person.
      tableRow( array(
        "<input type='radio' name='action$caseNr' value='$action' $checked />",
#        ($other_id ? personLink( $other_id, $other_name ) : $other_name),
        visualize( $other_name ),
        visualize( $other_countryId ),
        ($other_id ? "<a class='p' href='../p.php?i=$other_id' target='_blank'>$other_id</a>" : peekLink( $other_name, $other_countryId )),
        $birthdates[ $other_id ],
        '', #sprintf( "%.2f", $similarity ),
        '',
        '',
      ));
      
      #--- Stop after five similar persons.
      if( ++$similarsCtr == 5 )
        break;
    }

    #--- Offer an explicit skip.
    tableRow( array(
      "<input type='radio' name='action$caseNr' value='skip' />",
      'I\'m not sure yet', '', '', '', '', '', ''
    ));
    
    #--- Don't show more than 20 unfinished persons.
    if( $caseNr == 20 )
      break;
  }

  #--- Show 'Update' button, finish table and form.
  tableRowEmpty();
  tableRowFull( "<input type='submit' value='Update' />" );
  tableEnd();
  echo "</form>";
}
        } else {
            $has_scrambles = 'N';
        }
        if ($round['hasevent']) {
            // link to remove results for this round
            // should protect this if we keep using the php system
            // jQuery attempts to load this
            $has_results = "Y&nbsp;&nbsp;&nbsp;(<a class='remove_link' href='scripts/remove_data.php?t=Results&c={$compIdUrl}&amp;e={$round['eventId']}&amp;r={$round['roundId']}' target='_blank' title='Remove Results'>X</a>)";
        } else {
            $has_results = 'N';
        }
        $tableOddRow = false;
        // global used in tableRowStyled; we don't want stripes here.
        $tableAttributes = array('', '', $round['hasevent'] ? $good_cell_attr : $bad_cell_attr, $round['hasscr'] ? $good_cell_attr : $bad_cell_attr);
        // global used in tableRowStyled
        tableRowStyled($class, array($round['event'] . ": ", $round['round'], $has_results, $has_scrambles));
    }
    tableEnd();
    print "<strong style='color: #900'>Please be careful removing data!  Data in the above table is live.</strong><br />";
    print "Remove all results and scrambles only, does not affect persons: <a class='remove_link' href='scripts/remove_data.php?t=All&c={$compIdUrl}' target='_blank' title='Remove All'>X ALL</a>";
} else {
    print "No fully imported result or scramble data exists to compare.";
}
print "</li></ol></li>";
// Final scripts...
$optionalScriptsCronSchedule = `crontab -l | grep cronned_results_scripts.sh`;
print "<li><p><strong>Optional</strong>: Run Some More Scripts (These are run on the following cron schedule: <code>{$optionalScriptsCronSchedule}</code>)</p>\n         <ol type='a'>\n           <li><a href='../statistics.php?update8392=1' target='_blank' class='link-external external'>update_statistics_page</a></li>\n           <li><a href='export_public.php' target='_blank' class='link-external external'>export_public</a></li>\n         </ol>\n       </li>";
print "<li><p>Good job, you're done!</p></li>";
print "</div>";
// upload form is below this; these notices go last.
if (count($competition_has_results) > 0) {
function showUnfinishedPersons()
{
    #----------------------------------------------------------------------
    global $personsFromPersons, $personsFromResultsWithoutId, $birthdates;
    #--- Pre-compute the candidate tuples: (id, name, countryId, romanName, romanNameSimilarityPlaceHolder, countryIdSimilarityPlaceHolder)
    $candidates = array();
    foreach ($personsFromPersons as $person) {
        list($id, $name, $countryId) = $person;
        $candidates[] = array($id, $name, $countryId, extractRomanName($name), 0, 0);
    }
    #--- Begin the form and table.
    echo "<form action='persons_finish_unfinished_ACTION.php' method='post'>";
    tableBegin('results', 8);
    tableHeader(explode('|', '|personName|countryId|personId|birthdate|personName|countryId|personSemiId'), array(6 => 'class="6"'));
    #--- Walk over all persons from the Results table.
    $caseNr = 0;
    $availableSpots = array();
    // array of semiIds in progress
    foreach ($personsFromResultsWithoutId as $person) {
        list($name, $countryId, $firstYear) = $person;
        #--- Try to compute the semi-id.
        $paddingLetter = 'U';
        $neatName = strtoupper(preg_replace('/[^a-zA-Z ]/', '', removeUglyAccentsAndStuff(extractRomanName($name))));
        $nameParts = explode(' ', $neatName);
        $lastName = $nameParts[count($nameParts) - 1];
        $restOfName = implode(array_slice($nameParts, 0, count($nameParts) - 1));
        // follows a simple trick that prevents us from empty or too short restOfNames and provides the appropriate padding
        $restOfName = str_pad($restOfName, 4, $paddingLetter);
        $lettersToShift = max(0, 4 - strlen($lastName));
        $cleared = false;
        while (!$cleared && $lettersToShift <= 4) {
            $quarterId = substr($lastName, 0, 4 - $lettersToShift) . substr($restOfName, 0, $lettersToShift);
            $semiId = $firstYear . $quarterId;
            // update array of persons in progress
            if (!array_key_exists($semiId, $availableSpots)) {
                $lastIdTaken = dbQuery("SELECT id FROM Persons WHERE id LIKE '{$semiId}__' ORDER BY id DESC LIMIT 1");
                if (!count($lastIdTaken)) {
                    $counter = 0;
                } else {
                    $counter = intval(substr($lastIdTaken[0]['id'], 8, 2), 10);
                }
                $availableSpots[$semiId] = 99 - $counter;
            }
            // is there a spot available?
            if ($availableSpots[$semiId]) {
                $availableSpots[$semiId]--;
                $cleared = true;
            } else {
                $lettersToShift++;
            }
        }
        /* The script has tried all the possibilities and none of them was valid.
         * If we reach here with $cleared set to false (something that is not going to happen in centuries) then
         * the person posting will receive an error in persons_finish_unfinished_ACTION.php and the software team
         * of the future will have work to do.
         */
        if (!$cleared) {
            // if we didn't clear a spot we stick with the first combination
            $lettersToShift = max(0, 4 - strlen($lastName));
            $semiId = $firstYear . substr($lastName, 0, 4 - $lettersToShift) . substr($restOfName, 0, $lettersToShift);
            $availableSpots[$semiId] = 0;
        }
        #--- Html-ify name and country.
        $nameHtml = htmlEscape($name);
        $countryIdHtml = htmlEscape($countryId);
        #--- Hidden field describing the case.
        $caseNr++;
        tableRowFull("&nbsp;<input type='hidden' name='oldNameAndCountry{$caseNr}' value='{$nameHtml}|{$countryIdHtml}' />");
        #--- Show the person.
        # Note that we set this input to checked, but if there's a better match
        # lower on, then it will take precendence.
        tableRowStyled('font-weight:bold', array("<input type='radio' name='action{$caseNr}' value='new' checked='checked' />", visualize($name), visualize($countryId), peekLink($name, $countryId), 'mm/dd/yyyy', "<input type='text' name='name{$caseNr}' value='{$nameHtml}' size='20' />", "<input type='text' name='country{$caseNr}' value='{$countryIdHtml}' size='20' />", "<input type='text' name='semiId{$caseNr}' value='{$semiId}' size='10' maxlength='8' />"));
        #--- Show most similar persons.
        $similarsCtr = 0;
        foreach (getMostSimilarPersonsMax(extractRomanName($name), $countryId, $candidates, 10) as $similarPerson) {
            list($other_id, $other_name, $other_countryId) = $similarPerson;
            #--- If name and country match the unfinished persons, pre-select it.
            $checked = $other_name == $name && $other_countryId == $countryId ? "checked='checked'" : '';
            #--- Skip the unfinished person itself.
            if ($checked && !$other_id) {
                continue;
            }
            #--- Html-ify.
            $nameHtml = htmlEscape($other_name);
            $countryHtml = htmlEscape($other_countryId);
            $idHtml = htmlEscape($other_id);
            #--- Use "name|country|id" as action.
            $action = "{$nameHtml}|{$countryHtml}|{$idHtml}";
            #--- Show the other person.
            tableRow(array("<input type='radio' name='action{$caseNr}' value='{$action}' {$checked} />", visualize($other_name), visualize($other_countryId), $other_id ? "<a class='p' href='../p.php?i={$other_id}' target='_blank'>{$other_id}</a>" : peekLink($other_name, $other_countryId), $birthdates[$other_id], '', '', ''));
            #--- Stop after five similar persons.
            if (++$similarsCtr == 5) {
                break;
            }
        }
        #--- Offer an explicit skip.
        tableRow(array("<input type='radio' name='action{$caseNr}' value='skip' />", 'I\'m not sure yet', '', '', '', '', '', ''));
        #--- Don't show more than 20 unfinished persons.
        if ($caseNr == 20) {
            break;
        }
    }
    #--- Show 'Update' button, finish table and form.
    tableRowEmpty();
    tableRowFull("<input type='submit' value='Update' />");
    tableEnd();
    echo "</form>";
}
function checkTooMuchInResults()
{
    #----------------------------------------------------------------------
    global $personsFromPersons, $personsFromResults;
    echo "<hr />";
    $tooMuchInResults = array();
    #--- Find all ('finished') entries in Results that don't have a match in Persons.
    foreach (array_keys($personsFromResults) as $personKey) {
        if ($personsFromResults[$personKey]['id'] && !isset($personsFromPersons[$personKey])) {
            $tooMuchInResults[] = $personKey;
        }
    }
    #--- If all OK, say so and return.
    if (empty($tooMuchInResults)) {
        echo "<p style='color:#6C6'><strong>OK!</strong> All persons in <strong>Results</strong> who have an id also appear in <strong>Persons</strong>.</p>";
        return;
    }
    #--- Otherwise, show the Results troublemakers and possible matches in Persons.
    echo "<p style='color:#F00'><strong>BAD!</strong> Not all persons in <strong>Results</strong> who have an id also appear in <strong>Persons</strong>:</p>";
    tableBegin('results', 4);
    tableHeader(explode('|', '|Name|countryId|id'), array(3 => 'class="f"'));
    foreach ($tooMuchInResults as $personKey) {
        extract($personsFromResults[$personKey]);
        tableRowStyled('font-weight:bold', array('', visualize($name), visualize($countryId), visualize($id)));
        $currId = $id;
        $currName = $name;
        $currCountryId = $countryId;
        foreach (getMostSimilarPersons($id, $name, $countryId, $personsFromPersons) as $similarPerson) {
            extract($similarPerson);
            tableRow(array('<a href="persons_check_finished_ACTION.php?action=fix_results_data' . '&old_name=' . urlEncode($currName) . '&new_name=' . urlEncode($name) . '&old_id=' . urlEncode($currId) . '&new_id=' . urlEncode($id) . '&old_country=' . urlEncode($currCountryId) . '&new_country=' . urlEncode($countryId) . '">Change to:</a>', visualize($name), visualize($countryId), visualize($id)));
        }
        tableRowEmpty();
    }
    tableEnd();
    $GLOBALS["success"] = false;
}
function tableRow($values)
{
    #----------------------------------------------------------------------
    tableRowStyled('', $values);
}