<?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]) . ']';
  }
}
