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>";
}
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 importLocalNames () {
#----------------------------------------------------------------------
  global $chosenUpload, $chosenConfirm, $chosenNamesFile, $chosenFilename;

  $oneBad = false;
  $oneGood = false;

  if( $chosenUpload ){

    $upload_path = '../upload/';
    if( $chosenFilename == '' )
      $chosenFilename = 'tmp' . rand();

    if( ! $chosenConfirm )
      move_uploaded_file( $_FILES['namesFile']['tmp_name'], $upload_path . $chosenFilename . '.txt' );

    $nameLines = file( $upload_path . $chosenFilename . '.txt', FILE_SKIP_EMPTY_LINES );

    foreach( $nameLines as $nameLine ){
      $nameLine = rtrim( $nameLine );
      if( count( explode( ',', $nameLine )) != 2 ){
        echo "<span style='color:#F00'>Wrong line syntax: <br /> " . htmlEscape( $nameLine ) . "</span><br />\n";
        $oneBad = true;
        continue;
      }

      list( $wcaId, $localName ) = explode( ',', $nameLine );
      $wcaId = utf8_decode( $wcaId );
      $persons = dbQuery( "SELECT name personName FROM Persons WHERE id='$wcaId' AND subId=1" );

      if( count( $persons ) == 0 ){
        echo "<span style='color:#DB0'>Unknown WCA id " . htmlEscape( $wcaId ) . "</span><br />\n";
        $oneBad = true;
        continue;
      }

      $person = array_shift( $persons );
      extract( $person );

      if( $chosenConfirm ){
        $localName = mysql_real_escape_string( $localName );
        $name = mysql_real_escape_string( extractRomanName( $personName )) . ' (' . $localName . ')';
        $personName = mysql_real_escape_string( $personName );
        dbCommand( "UPDATE Persons SET name='$name' WHERE id='$wcaId' AND subId=1" );
        dbCommand( "UPDATE Results SET personName='$name' WHERE personId='$wcaId' AND personName='$personName'" );
        $oneGood = true;
      }

      else{
        $personLocalName = extractLocalName( $personName );
        if( $localName == ''){
          if( $personLocalName == '' ){}
          else{
            echo "<span style='color:#3C3'>I will remove name ".htmlEscape( $personLocalName )." from ".htmlEscape( $personName )."($wcaId)</span><br />\n";
          }
        }

        else{
          if( $personLocalName == '' ){
            echo "<span style='color:#3C3'>I will add name ".htmlEscape( $localName )." to ".htmlEscape( $personName )."($wcaId)</span><br />\n";
          }
          else{
            echo "<span style='color:#3C3'>I will change name ".htmlEscape( $personLocalName )." to ".htmlEscape( $localName )." for ".htmlEscape( $personName )."($wcaId)</span><br />\n";
          }
        }
      }
    }

    if( $chosenConfirm ){
      if(( $oneGood ) and ( ! $oneBad ))
        noticeBox3( 1, "Complete. All names were updated." );
      if(( $oneGood ) and ( $oneBad ))
        noticeBox3( 0, "Complete. However, some lines were skipped." );
      if(( ! $oneGood ) and ( $oneBad ))
        noticeBox3( -1, "Cound't update anything." );
      if(( ! $oneGood ) and ( ! $oneBad ))
        noticeBox3( 0, "I found an empty text !?" );
      $chosenUpload = false;
      unlink( $upload_path . $chosenFilename . '.txt' );
    }

    else{
      echo "<form method='POST'>\n";
      echo "<input type='hidden' id='namesFile' name='namesFile' value='".htmlEscape($chosenNamesFile)."' />\n";
      echo "<input type='hidden' id='upload' name='upload' value='$chosenUpload' />\n";
      echo "<input type='hidden' id='filename' name='filename' value='".htmlEscape($chosenFilename)."' />\n";
      echo "<input type='submit' id='confirm' name='confirm' value='Confirm' /></form>\n";
    }
  }

  if( ! $chosenUpload ){

    adminHeadline( 'Add local names' );

    echo "<p>You can add or modify local names here, by upload a file containing the names. The file must be a plain text file encoded in UTF-8. Each line must contain: the WCA id, a comma (',') and the name you would like to add. If you want to remove a name from the database, just leave the name part blank.</p>\n";
  
    echo "<p>Example: <br /><br />2009WANG20,王超<br />2009WANG62,王宇欣<br />2009WANG13,王宇轩<br />etc.</p>\n";
    echo "<hr>\n";

    echo "<table class='prereg'>\n";
    echo "  <form method='POST' enctype='multipart/form-data'>\n";
    echo "  <tr><td width='30%'><label for='namesFile'>Upload file: </label></td>\n";
    echo "      <td><input type='file' id='namesFile' name='namesFile' /></td>\n";
    echo "      <td><input type='submit' id='upload' name='upload' value='Upload' /></td></tr></form>\n";
    echo "</table>\n";
  }
}
function addList($list, $legacyId)
{
    #----------------------------------------------------------------------
    $competitions = readDatabaseTableWithId('Competitions');
    list($id, $title, $subtitle, $columnDefs, $rows) = $list;
    $info = isset($list[5]) ? $list[5] : '';
    #--- From column definitions like "[P] Person [N] Appearances [T] | [P] Person [N] Appearances"
    #--- extract classes and names like:
    #--- ('P', 'N', 'T', 'P', 'N', 'f')
    #--- ('Person', 'Appearances, '&nbsp; &nbsp; | &nbsp; &nbsp;', 'Person', 'Appearances', '&nbsp;')
    $columnDefs = "{$columnDefs} [f] &nbsp;";
    $columnDefs = preg_replace('/\\|/', ' &nbsp; &nbsp; | &nbsp; &nbsp; ', $columnDefs);
    preg_match_all('/\\[(\\w+)\\]\\s*([^[]*[^[ ])/', $columnDefs, $matches);
    $columnClasses = $matches[1];
    $columnNames = $matches[2];
    $ctr = 0;
    foreach ($columnClasses as $class) {
        if ($class == 'P') {
        } elseif ($class == 'E') {
        } elseif ($class == 'C') {
        } elseif ($class == 't') {
        } elseif ($class == 'T') {
            $attributes[$ctr] = 'class="L"';
        } elseif ($class == 'N') {
            $attributes[$ctr] = 'class="R2"';
        } elseif ($class == 'n') {
            $attributes[$ctr] = 'class="r"';
        } elseif ($class == 'R') {
            $attributes[$ctr] = 'class="R2"';
        } elseif ($class == 'r') {
            $attributes[$ctr] = 'class="r"';
        } elseif ($class == 'f') {
            $attributes[$ctr] = 'class="f"';
        } else {
            showErrorMessage("Unknown column type <b>'</b>{$class}<b>'</b>");
        }
        $ctr++;
    }
    if ($subtitle) {
        $subtitle = "<span style='color:#999'>({$subtitle})</span>";
    }
    if ($info) {
        $info = htmlEntities($info, ENT_QUOTES);
        $info = "(<a title='{$info}' style='color:#FC0' onclick='alert(\"{$info}\")'>info</a>)";
    }
    $columnCount = count($columnNames);
    echo "<div id='{$id}'>\n";
    TableBegin('results', $columnCount);
    TableCaptionNew(false, $legacyId, "{$title} {$subtitle} {$info}");
    TableHeader($columnNames, $attributes);
    #--- Display the table.
    $rowCtr = 0;
    foreach ($rows as $row) {
        $values = array();
        $numbers = '';
        #    array_unshift( $row, 0 );
        #    foreach( $row as $key => $value ){
        foreach (range(0, $columnCount - 2) as $i) {
            $value = $row[$i];
            $Class = ucfirst($columnClasses[$i]);
            if ($Class == 'P' && $value) {
                $value = personLink($value, extractRomanName(currentPersonName($value)));
            }
            if ($Class == 'E') {
                $value = eventLink($value, eventCellName($value));
            }
            if ($Class == 'C') {
                $value = competitionLink($value, $competitions[$value]['cellName']);
            }
            if ($Class == 'R') {
                $value = formatValue($value, isset($row['eventId']) ? valueFormat($row['eventId']) : 'time');
            }
            $values[] = $value;
            if ($Class == 'N') {
                $numbers .= "{$value}|";
            }
        }
        #--- Add the rank.
        $rowCtr++;
        $rank = isset($prevNumbers) && $numbers == $prevNumbers ? '' : $rowCtr;
        ###  $rank = $rowCtr;
        $prevNumbers = $numbers;
        #    $values[0] = $rank;
        #--- Add the filler column cell.
        $values[] = '';
        #--- Show the row.
        TableRow($values);
    }
    TableEnd();
    echo "</div>\n";
}