<?php
/**
* Read text with footnotes from the input stream, reordering the footnote
* numbers starting from 1.
* Written-for and tested on PHP 5.2.6.
**/

// PHP is typically configured for short-lived Web page rendering applications
// Since this app may potentially be working on very large data sets, remove
// execution time limits, and increase maximum memory consumption.
ini_set('max_execution_time', 0);
ini_set('memory_limit', '512m');

// Handle command line
if (isset($_SERVER['argv'][1])) {
 $filename = $_SERVER['argv'][1];
} else {
 $filename = 'php://stdin';
}

// Open input file
$infp = fopen($filename, "r");
if (!$infp) {
 exit("Unable to open '$filename' for input.\n");
}

// Handle conversion
handleTextBlock($infp);
handleFootnotesBlockInMemory($infp);

// Close input stream and terminate (not strictly necessary)
fclose($infp);
exit();


/////////////////////////////////////////////////////////////////////
/////////// Utility functions (can be in a separate file) ///////////
/////////////////////////////////////////////////////////////////////
/**
* Read the text block and reorder the footnotes as we go
* (automatically maintains a translation table from old footnote numbers
* to new ones)
*
* @param fileStream $infp
*/
function handleTextBlock($infp)
{
 define('FOOTNOTE_HEADER',      '@footnote:');
 define('FOOTNOTE_HEADER_LENGTH',  strlen(FOOTNOTE_HEADER));

 while ($line = fgets($infp)) {
   // Convert footnotes
   $line = preg_replace_callback('/\[(\d+)\]/', 'Footnotes::replaceCallback', $line);

   // Print output
   echo $line;

   // Stop if we reached the footnote target section
   if ($line[0]=='@' && strncmp($line, FOOTNOTE_HEADER, FOOTNOTE_HEADER_LENGTH)==0) {
     break;
   }
 }
}

/**
* Handle reordering of the footnotes in-memory
* @param fileStream $infp
*/
function handleFootnotesBlockInMemory($infp)
{
 // Reorder footnotes if needed, and print them out
 $footnoteTargets = array();

 // Iterate over footnote lines
 while ($line = fgets($infp)) {
   // If line doesn't seem to be a footnote, print as-is
   if ($line[0] != '[') {
     echo $line;
     continue;
   }

   // Get the footnote key (number) and value (text)
   list($key, $val) = explode(' ', $line, 2);
   $key = Footnotes::translate(trim($key, '[]'));

   $footnoteTargets[$key] = $val;
 }

 $numFootnotes = count($footnoteTargets);
 for ($i=1; $i<=$numFootnotes; $i++) {
   echo "[$i] ", $footnoteTargets[$i];
 }
}

class Footnotes
{
 /**
  * Table of footnote number conversion
  *
  * @var array
  */
 static protected $_table = array();

 /**
  * Footnote counter
  * @var integer
  */
 static protected $_count = 0;

 /**
  * Translate a given footnote number according to the translation table
  *
  * If the footnote numer is encountered for the first time, a new entry
  * will be added to the conversion table
  *
  * @param  integer $number
  * @return integer
  */
 static public function translate($number)
 {
   if (! isset(self::$_table[$number])) {
     self::$_table[$number] = ++self::$_count;
   }

   return self::$_table[$number];
 }

 /**
  * A callback function wrapping self::translate() for use with preg_replace_callback
  * @param  array  $match
  * @return string
  */
 static public function replaceCallback($match)
 {
   return '[' . self::translate($match[1]) . ']';
 }
}