package com.hemju.biborder;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Renumbers all bib references in the text.
 *
 * @author Helmut Juskewycz, <hjuskewycz - at - hemju.com>
 */
public class OrderAll implements OrderStrategy {

  private static final int WRITE_BUFFER_SIZE = 1024 * 16;
  private static final int READ_BUFFER_SIZE = 1024 * 16;
  private static final char[] READ_BUFFER = new char[READ_BUFFER_SIZE];
  private static final char[] WRITE_BUFFER = new char[READ_BUFFER_SIZE];
  private static final int DIGITAL_MIN_VALUE = 0x30;
  private static final int DIGITAL_MAX_VALUE = 0x39;

  /**
   * Renumbers all entries, not threadsafe.
   */
  public void order(InputStream in, OutputStream out) {
    try {
      Reader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(in)));
      Writer writer = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(out)));

      StringBuilder bibEntry = null;
      boolean bibRefStart = false;
      HashMap<String, String> mapping = new HashMap<String, String>(1000);
      int counter = 1;

      char actChar;
      int charsRead;
      int charsWritten = 0;
      int bibRefLength;

      while ((charsRead = reader.read(READ_BUFFER)) >= 0) {
        for (int j = 0; j < charsRead; j++) {
          actChar = (char) READ_BUFFER[j];

          if (bibRefStart) {
            if (actChar == ']' && bibEntry != null) {//bibRefEnd so flush to bib ref entry

              String key = bibEntry.toString();
              String value = mapping.get(key);

              if (value == null) {//create new bib ref entry if none exists
                String newBibRef = (counter++ + "");
                mapping.put(key, newBibRef);
                value = newBibRef;
              }

              bibRefLength = value.length();
              for (int m = 0; m < bibRefLength; m++) {//save the entry in the buffer
                WRITE_BUFFER[charsWritten++] = value.charAt(m);
                if (charsWritten >= WRITE_BUFFER_SIZE) {
                  writer.write(WRITE_BUFFER);
                  charsWritten = 0;
                }
              }
            }

            if (actChar >= DIGITAL_MIN_VALUE && actChar <= DIGITAL_MAX_VALUE) {//is digit
              if (bibEntry == null) {
                bibEntry = new StringBuilder();
              }
              bibEntry.append(actChar);
            } else {
              bibRefStart = false;
              bibEntry = null;
              WRITE_BUFFER[charsWritten++] = actChar;
            }
          } else {
            bibRefStart = (actChar == '[');
            WRITE_BUFFER[charsWritten++] = actChar;
          }

          // buffer full, flush to the stream
          if (charsWritten >= WRITE_BUFFER_SIZE) {
            writer.write(WRITE_BUFFER);
            charsWritten = 0;
          }

        }
      }
      writer.write(WRITE_BUFFER);//write the rest to the stream
    } catch (IOException ex) {
      Logger.getLogger(OrderAll.class.getName()).log(Level.SEVERE, null, ex);
    }
  }
}
