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.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Sorts bib reference at the end of the file.
 *
 * @author Helmut Juskewycz <hjuskewycz - at - hemju.com>
 */
public class OrderBibRef implements OrderStrategy {

  private static final char[] FOOTNOTE = {'@', 'f', 'o', 'o', 't', 'n', 'o', 't', 'e', ':', '\n'};
  private static final int WRITE_BUFFER_SIZE = 1024 * 16;
  private static final int READ_BUFFER_SIZE = 1024 * 16;
  private static final int BIB_REF_BUFFER_SIZE = 1024 * 2;//Note that is the maximum size of a bib reference entry
  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 char[] BIB_REF_BUFFER = new char[BIB_REF_BUFFER_SIZE];
  private static final int DIGITAL_MIN_VALUE = 0x30;

  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)));

      List<BibRefEntry> list = new LinkedList<BibRefEntry>();
      int charsRead;
      char actChar;
      int charsWritten = 0;
      int bibRefWritten = -1;
      int footnoteIndex = 0;

      while ((charsRead = reader.read(READ_BUFFER)) >= 0) {
        for (int j = 0; j < charsRead; j++) {
          actChar = (char) READ_BUFFER[j];
          //beginning of the text, footnote not found yet
          if (footnoteIndex < FOOTNOTE.length) {
            footnoteIndex = (actChar == FOOTNOTE[footnoteIndex]) ? ++footnoteIndex : 0;
            WRITE_BUFFER[charsWritten++] = actChar;
            if (charsWritten >= WRITE_BUFFER_SIZE) {
              writer.write(WRITE_BUFFER);
              charsWritten = 0;
            }
          } else {//end of the text
            if (charsWritten != 0) {
              writer.write(WRITE_BUFFER);
              charsWritten = 0;
            }
            if (actChar == '[') {
              if (bibRefWritten == -1) {//indicates the first occurrence of '['
                bibRefWritten = 0;
              } else {
                char[] text = Arrays.copyOfRange(BIB_REF_BUFFER, 0, bibRefWritten);
                list.add(new BibRefEntry(text));
                bibRefWritten = 0;
              }
            }
            if (bibRefWritten != -1) {
              BIB_REF_BUFFER[bibRefWritten++] = actChar;
            }
          }
        }
      }
      //sorting the list can probably optimized too
      Collections.sort(list);
      for (BibRefEntry bibRefEntry : list) {
        writer.write(bibRefEntry.text);
      }
    } catch (IOException ex) {
      Logger.getLogger(OrderBibRef.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

  private static class BibRefEntry implements Comparable<BibRefEntry> {

    private final int tag;
    private final char[] text;

    public BibRefEntry(char[] text) {
      //Integer parse is too slow
      int tagLength = 1;
      while (text[tagLength] != ']') {
        tagLength++;
      }
      tagLength--;
      int tagLength2 = tagLength - 1;
      int t = 0;
      int pos = 0;
      for (int i = 0; i < tagLength; i++) {
        if (i != tagLength2) {
          pos = (int) Math.pow(10, tagLength2 - i);
          t += (text[i + 1] - DIGITAL_MIN_VALUE) * pos;
        } else {
          t += (text[i + 1] - DIGITAL_MIN_VALUE);
        }
      }
      this.tag = t;
      this.text = text;
    }

    public int compareTo(BibRefEntry o) {
      return tag - o.tag;
    }

    @Override
    public String toString() {
      return "tag: " + tag + " text: " + new String(text);
    }
  }
}
