import java.io.*;
import java.util.*;

/**
* Fußnoten-Sortierprogramm mit Ausrichtung auf eine möglichst hohe Effizienz
* und Skalierbarkeit sowie einem geringen Speicherverbrauch.
* <p>
* Zugunsten der Verarbeitungsgeschwindigkeit wurde auf die Nutzung von
* regulären Ausdrücken (<code>Pattern</code>/<code>Matcher</code>),
* sowie des <code>PrintWriter</code>s verzichtet.<br>
* Zugunsten des Speicherverbrauchs arbeitet das Programm Stream-basiert,
* was jedoch die re-nummerierung der im Text befindlichen Verweise in
* der Reihenfolge der Verweisziele aus dem Fußnotenabschnitt ohne
* zweiten Durchlauf unmöglich macht. Des Weiteren wurde auf die Verwendung
* eines <code>Comparator</code>s verzichtet, der die Vorhaltung aller
* Verweisziele im Speicher bedingt hätte um die Sortierung durchführen zu
* können.<br>
* Nachteil der Optimierungen ist ein deutlich längerer Code - die Klassen
* <code>FootnoteFinder</code> und <code>Footnotes</code>, sowie die vielen
* <code>writer.write()</code> würde es ohne diese Optimierungen nicht geben.
* <p>
* Die Formatierung des Codes, die Stern-Imports, das fehlende
* Exception-Handling, sowie die Verwendung innerer Klassen dienen
* ausschließlich der möglichst kompakten Umsetzung der gestellten Aufgabe
* und kann nicht als Beispiel guten Codes dienen.
* <p>
* Dieser Code wurde mit Eclipse 3.4 / Java 6 unter Kubuntu 8.04 erstellt. :-)
*
* @author Oliver Siegmar
*/
public class FootnoteProcessor {

   /** Fußnotenmarkierung **/
   private static final String FOOT_MARKER = "@footnote:";

   /** Zeichensatz der Quelldatei **/
   private static final String CHARSET = "ISO-8859-15";

   /** Betriebssystemspezifisches Zeilenende **/
   private static final String NEWLINE = System.getProperty("line.separator");

   /** Zeichen, mit dem die Fußnoten begonnen werden **/
   private static final char FOOTNOTE_OPEN = '[';

   /** Zeichen, mit dem die Fußnoten geschlossen werden **/
   private static final char FOOTNOTE_CLOSE = ']';

   /**
    * Speicher für die neu nummerierten Fußnoten
    * (Key = alte Nummer, Value = neue Nummer)
    **/
   private final Map<Integer, Integer> noteMap =
       new HashMap<Integer, Integer>();

   /** Writer für die Ausgabe des re-nummerierten Textes **/
   private final Writer writer;

   public static void main(final String[] args) throws IOException {
       if (args.length == 0) {
           System.err.println("No file specified. " +
               "Usage: FootnoteProcessor input.txt > output.txt");
           System.exit(1);
       }
       new FootnoteProcessor().processFile(new File(args[0]));
   }

   private FootnoteProcessor() throws IOException {
       writer = new BufferedWriter(new OutputStreamWriter(System.out));
   }

   /**
    * Einstiegsmethode zur Verarbeitung der Daten.
    *
    * @param file die zu verarbeitende Quelldatei.
    * @throws IOException tritt bei Lese- oder Schreibfehlern auf.
    */
   private void processFile(final File file) throws IOException {
       final BufferedReader br = new BufferedReader(new InputStreamReader(
           new FileInputStream(file), CHARSET));
       Scanner scanner = new BodyScanner();
       for (String line; (line = br.readLine()) != null;) {
           if (FOOT_MARKER.equals(line)) {
               scanner = new FootScanner();
               writer.write(FOOT_MARKER);
               writer.write(NEWLINE);
           } else {
               scanner.process(line);
           }
       }

       br.close();
       writer.close();
   }

   /**
    * Klasse zur effizienten Ermittlung der Position von Fußnoten
    */
   private class FootnoteFinder {

       /** Zu untersuchende Zeile **/
       private final char[] ca;

       /** Startposition des Fußnotenmarkers ([) **/
       private int start;

       /** Endposition des Fußnotenmarkers (]) **/
       private int end;

       /**
        * Initialisierung.
        *
        * @param line die zu untersuchende Zeile.
        */
       public FootnoteFinder(final String line) {
           ca = line.toCharArray();
       }

       /**
        * Sucht nach der nächsten Fußnote
        *
        * @return Start- und Endposition der Fußnote, oder <code>null</code>,
        *         wenn keine (weitere) Fußnote gefunden wurde.
        */
       private int[] find() {
           boolean open = false;
           int[] ret = null;
           for (int i = end; i < ca.length; i++) {
               if (!open) {
                   if (ca[i] == FOOTNOTE_OPEN) {
                       start = i + 1;
                       open = true;
                   }
               } else {
                   if (ca[i] == FOOTNOTE_CLOSE) {
                       if (i > start) {
                           ret = new int[] { start, i };
                       }
                       end = i + 1;
                       break;
                   } else if (ca[i] < '0' || ca[i] > '9') {
                       break;
                   }
               }
           }

           return ret;
       }
   }

   /** Interface für den zeilenbasierten Fußnotenscanner **/
   private interface Scanner {
       /**
        * Verarbeitet eine Zeile der Quelldatei.
        * @param line eingelesene Zeile der Quelldatei.
        * @throws IOException tritt bei einem Schreibfehler auf.
        */
       void process(String line) throws IOException;
   }

   /** Von den Scannerimplementierungen gemeinsam genutzte Funktionalität **/
   private abstract class AbstractScanner implements Scanner {
       public void process(final String line) throws IOException {
           final FootnoteFinder matcher = new FootnoteFinder(line);
           for (int[] matches; (matches = matcher.find()) != null;) {
               process(line, matches[0], matches[1]);
           }
           postProcess(line);
       }

       /**
        * Diese Methode wird für jeden Treffer einer Fußnote aufgerufen.
        *
        * @param line die zu verarbeitende Zeile.
        * @param start Startposition der Fußnote.
        * @param end Endposition der Fußnote.
        * @throws IOException tritt bei einem Schreibfehler auf.
        */
       abstract void process(String line, int start, int end)
       throws IOException;

       /**
        * Diese Methode wird aufgerufen, sobald alle Treffer für eine
        * Zeile abgearbeitet sind.
        *
        * @param line die zu verarbeitende Zeile.
        * @throws IOException tritt bei einem Schreibfehler auf.
        */
       void postProcess(final String line) throws IOException {}
   }

   /** Scannerimplementierung, welche die Verweise im Text untersucht **/
   private class BodyScanner extends AbstractScanner {
       private int lastNumber = 0;
       private int lastEnd = 0;

       @Override
       public void process(final String line, final int begin, final int end)
       throws IOException {
           final Integer number = new Integer(line.substring(begin, end));

           Integer remap = noteMap.get(number);
           if (remap == null) {
               remap = ++lastNumber;
               noteMap.put(number, remap);
           }

           writer.write(line, lastEnd, (begin - lastEnd));
           writer.write(Integer.toString(remap));

           lastEnd = end;
       }

       @Override
       public void postProcess(final String line) throws IOException {
           writer.write(line, lastEnd, (line.length() - lastEnd));
           writer.write(NEWLINE);
           lastEnd = 0;
       }
   }

   /** Scannerimplementierung, welche die Verweisziele untersucht **/
   private class FootScanner extends AbstractScanner {
       private final Footnotes footnotes = new Footnotes();

       @Override
       public void process(final String line, final int begin, final int end)
       throws IOException {
           final Integer number = new Integer(line.substring(begin, end));
           final String text = line.substring(end + 2);
           final Integer remap = noteMap.get(number);
           footnotes.add(remap, text);
       }
   }

   /** Speicher-, Sortier- und Ausgabelogik für Verweisziele **/
   private class Footnotes {
       /** Zwischenspeicher für Fußnoten, die noch nicht ausgegeben wurden **/
       private final Map<Integer, String> footnotes =
           new HashMap<Integer, String>();

       /** Ziffer der Fußnote, die als nächstes ausgegeben werden muss **/
       private Integer nextNumber = 1;

       /**
        * Fügt dem Zwischenspeicher eine Fußnote hinzu. Prüft anschließend,
        * ob die hinzugefügte Fußnote (sowie evtl. bereits gespeicherte)
        * Fußnoten ausgegeben werden können.
        *
        * @param number Nummer der hinzuzufügenden Fußnote.
        * @param footnote Text der hinzuzufügenden Fußnote.
        * @throws IOException tritt bei einem Schreibfehler auf.
        */
       public void add(final Integer number, final String footnote)
       throws IOException {
           footnotes.put(number, footnote);
           if (number.equals(nextNumber)) {
               printFootnotes();
           }
       }

       /**
        * Gibt alle im Zwischenspeicher befindlichen Fußnoten in
        * sortierter Reihenfolge aus. Bricht ab, wenn der Zwischenspeicher
        * noch keine Fußnote der nächsten Nummer findet.
        *
        * @throws IOException tritt bei einem Schreibfehler auf.
        */
       private void printFootnotes() throws IOException {
           String nextFootnote;
           while ((nextFootnote = footnotes.get(nextNumber)) != null) {
               writer.write(FOOTNOTE_OPEN);
               writer.write(nextNumber.toString());
               writer.write(FOOTNOTE_CLOSE);
               writer.write(" ");
               writer.write(nextFootnote);
               writer.write(NEWLINE);
               nextNumber++;
               footnotes.remove(nextFootnote);
           }
       }
   }

}