package io.lacuna.bifurcan.durable.blocks;

import io.lacuna.bifurcan.DurableInput;
import io.lacuna.bifurcan.DurableOutput;
import io.lacuna.bifurcan.durable.BlockPrefix.BlockType;
import io.lacuna.bifurcan.durable.io.DurableBuffer;
import io.lacuna.bifurcan.utils.Iterators;

import java.util.PrimitiveIterator;
import java.util.PrimitiveIterator.OfInt;
import java.util.PrimitiveIterator.OfLong;

/**
 * A block representing a sorted sequence of 32-bit integers:
 * - the initial value [int32]
 * - zero or more deltas from the previous value [VLQs]
 *
 * @author ztellman
 */
public class HashDeltas {

  public static class Writer {
    private final DurableBuffer acc = new DurableBuffer();
    private long prevHash;
    private boolean init = false;

    public Writer() {
    }

    public void append(long hash) {
      if (init) {
        acc.writeUVLQ(hash - prevHash);
      } else {
        init = true;
        acc.writeVLQ(hash);
      }
      prevHash = hash;
    }

    public void flushTo(DurableOutput out) {
      acc.flushTo(out, BlockType.TABLE);
    }
  }

  public static class IndexRange {
    public final int start, end;
    public final boolean isBounded;

    public IndexRange(int start, int end, boolean isBounded) {
      this.start = start;
      this.end = end;
      this.isBounded = isBounded;
    }

    public boolean isEmpty() {
      return start < 0;
    }

    public boolean contains(int n) {
      return start <= n & n < end;
    }

    @Override
    public String toString() {
      return "[" + start + ", " + end + ", " + isBounded + "]";
    }
  }

  public static HashDeltas decode(DurableInput in) {
    return new HashDeltas(in.sliceBlock(BlockType.TABLE).pool());
  }

  public final DurableInput.Pool pool;

  private HashDeltas(DurableInput.Pool pool) {
    this.pool = pool;
  }

  public long nth(long index) {
    OfLong it = iterator();
    Iterators.drop(it, index);
    return it.nextLong();
  }

  public OfLong iterator() {
    DurableInput in = pool.instance();

    return new OfLong() {
      boolean hasNext = true;
      int next = (int) in.readVLQ();

      @Override
      public long nextLong() {
        long result = next;
        if (in.remaining() > 0) {
          next += (int) in.readUVLQ();
        } else {
          hasNext = false;
        }
        return result;
      }

      @Override
      public boolean hasNext() {
        return hasNext;
      }
    };
  }

  public IndexRange candidateIndices(long hash) {
    int start = -1, end = -1;
    OfLong it = iterator();

    for (int i = 0; it.hasNext(); i++) {
      long curr = it.nextLong();
      if (curr == hash) {
        start = i;
        break;
      } else if (curr > hash) {
        return new IndexRange(-1, -1, true);
      }
    }

    for (end = start; it.hasNext(); end++) {
      if (it.nextLong() > hash) {
        return new IndexRange(start, end + 1, true);
      }
    }

    return new IndexRange(start, end + 1, false);
  }
}