package morfologik.stemming;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;

/**
 * An iterator over {@link WordData} entries of a {@link Dictionary}. The stems can be decoded from compressed format or
 * the compressed form can be preserved.
 */
public final class DictionaryIterator implements Iterator<WordData> {
  private final CharsetDecoder decoder;
  private final Iterator<ByteBuffer> entriesIter;
  private final WordData entry;
  private final byte separator;
  private final boolean decodeStems;

  private ByteBuffer inflectedBuffer = ByteBuffer.allocate(0);
  private CharBuffer inflectedCharBuffer = CharBuffer.allocate(0);
  private ByteBuffer temp = ByteBuffer.allocate(0);
  private final ISequenceEncoder sequenceEncoder;

  public DictionaryIterator(Dictionary dictionary, CharsetDecoder decoder, boolean decodeStems) {
    this.entriesIter = dictionary.fsa.iterator();
    this.separator = dictionary.metadata.getSeparator();
    this.sequenceEncoder = dictionary.metadata.getSequenceEncoderType().get();
    this.decoder = decoder;
    this.entry = new WordData(decoder);
    this.decodeStems = decodeStems;
  }

  public boolean hasNext() {
    return entriesIter.hasNext();
  }

  public WordData next() {
    final ByteBuffer entryBuffer = entriesIter.next();

    /*
     * Entries are typically: inflected<SEP>codedBase<SEP>tag so try to find this split.
     */
    byte[] ba = entryBuffer.array();
    int bbSize = entryBuffer.remaining();

    int sepPos;
    for (sepPos = 0; sepPos < bbSize; sepPos++) {
      if (ba[sepPos] == separator) {
        break;
      }
    }

    if (sepPos == bbSize) {
      throw new RuntimeException("Invalid dictionary " + "entry format (missing separator).");
    }

    inflectedBuffer = BufferUtils.clearAndEnsureCapacity(inflectedBuffer, sepPos);
    inflectedBuffer.put(ba, 0, sepPos);
    inflectedBuffer.flip();

    inflectedCharBuffer = BufferUtils.bytesToChars(decoder, inflectedBuffer, inflectedCharBuffer);
    entry.update(inflectedBuffer, inflectedCharBuffer);

    temp = BufferUtils.clearAndEnsureCapacity(temp, bbSize - sepPos);
    sepPos++;
    temp.put(ba, sepPos, bbSize - sepPos);
    temp.flip();

    ba = temp.array();
    bbSize = temp.remaining();

    /*
     * Find the next separator byte's position splitting word form and tag.
     */
    assert sequenceEncoder.prefixBytes() <= bbSize : sequenceEncoder.getClass() + " >? " + bbSize;
    sepPos = sequenceEncoder.prefixBytes();
    for (; sepPos < bbSize; sepPos++) {
      if (ba[sepPos] == separator)
        break;
    }

    /*
     * Decode the stem into stem buffer.
     */
    if (decodeStems) {
      entry.stemBuffer = sequenceEncoder.decode(entry.stemBuffer,
                                            inflectedBuffer,
                                            ByteBuffer.wrap(ba, 0, sepPos));
    } else {
      entry.stemBuffer = BufferUtils.clearAndEnsureCapacity(entry.stemBuffer, sepPos);
      entry.stemBuffer.put(ba, 0, sepPos);
      entry.stemBuffer.flip();
    }

    // Skip separator character, if present.
    if (sepPos + 1 <= bbSize) {
      sepPos++;
    }

    /*
     * Decode the tag data.
     */
    entry.tagBuffer = BufferUtils.clearAndEnsureCapacity(entry.tagBuffer, bbSize - sepPos);
    entry.tagBuffer.put(ba, sepPos, bbSize - sepPos);
    entry.tagBuffer.flip();

    return entry;
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}
