package org.kududb.ts.core;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.primitives.Longs;

import java.util.Arrays;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;

import org.apache.kudu.annotations.InterfaceAudience;

/*
 * DO NOT EDIT
 *
 * Autogenerated from src/templates/Vec.java using the
 * src/templates/build script.
 */

/**
 * A vector of primitive longs.
 *
 * The vector is backed by a contiguous array, and offers efficient random
 * access.
 */
@InterfaceAudience.Private
@NotThreadSafe
final class LongVec {

  /** Default initial capacity for new vectors. */
  @VisibleForTesting
  static final int DEFAULT_CAPACITY = 32;

  /** data backing the vector. */
  private long[] data;

  /** offset of first unused element in data. */
  private int len;

  private LongVec(int capacity) {
    data = new long[capacity];
    len = 0;
  }

  private LongVec(long[] data) {
    this.data = data;
    this.len = data.length;
  }

  /**
   * Creates a new vector.
   * @return the new vector.
   */
  public static LongVec create() {
    return new LongVec(DEFAULT_CAPACITY);
  }

  /**
   * Creates a new vector with the specified capacity.
   * @param capacity the initial capacity of the vector
   * @return a new vector with the specified capacity
   */
  public static LongVec withCapacity(int capacity) {
    return new LongVec(capacity);
  }

  /**
   * Wrap an existing array with a vector.
   * The array should not be modified after this call.
   * @param data the initial data for the vector
   * @return a vector wrapping the data
   */
  public static LongVec wrap(long[] data) {
    return new LongVec(data);
  }

  /** Returns the number of elements the vector can hold without reallocating. */
  public int capacity() {
    return data.length;
  }

  /** Returns the number of elements in the vector. */
  public int len() {
    return len;
  }

  /** Returns {@code true} if the vector is empty. */
  public boolean isEmpty() {
    return len == 0;
  }

  /**
   * Reserves capacity for at least {@code additional} more elements to be
   * inserted into the vector.
   * The vector may reserve more space to avoid frequent reallocations. If the
   * vector already has sufficient capacity, no reallocation will happen.
   *
   * @param additional capacity to reserve
   */
  public void reserve(int additional) {
    if (additional < 0) throw new IllegalArgumentException("negative additional");
    if (data.length - len >= additional) return;
    reserveExact(Math.max(additional, (data.length - len) + data.length));
  }

  /**
   * Reserves capacity for exactly {@code additional} more elements to be
   * inserted into the vector.
   * The vector may reserve more space to avoid frequent reallocations. If the
   * vector already has sufficient capacity, no reallocation will happen.
   *
   * @param additional capacity to reserve
   */
  public void reserveExact(int additional) {
    if (len < 0) throw new IllegalArgumentException("negative additional");
    if (data.length - len > additional) return;
    data = Arrays.copyOf(data, data.length + additional);
  }

  /**
   * Shrink the capacity of the vector to match the length.
   */
  public void shrinkToFit() {
    if (len < data.length) data = Arrays.copyOf(data, len);
  }

  /**
   * Shorten the vector to be {@code len} elements long.
   * If {@code len} is greater than the vector's current length,
   * this has no effect.
   * @param len the new length of the vector
   */
  public void truncate(int len) {
    if (len < 0) throw new IllegalArgumentException("negative len");
    this.len = Math.min(this.len, len);
  }

  /**
   * Removes all elements from the vector.
   * No reallocation will be performed.
   */
  public void clear() {
    truncate(0);
  }

  /**
   * Appends an element to the vector.
   * @param element the element to append
   */
  public void push(long element) {
    reserve(1);
    data[len++] = element;
  }

  /**
   * Sets the element at {@code index} to the provided value.
   * @param index of the element to set
   * @param value to set the element to
   * @throws IndexOutOfBoundsException if {@code} index is not valid
   */
  public void set(int index, long value) {
    if (index >= len) throw new IndexOutOfBoundsException();
    data[index] = value;
  }

  /**
   * Concatenates another vector onto the end of this one.
   * @param other the other vector to concatenate onto this one
   */
  public void concat(LongVec other) {
    reserveExact(other.len);
    System.arraycopy(other.data, 0, data, len, other.len);
    len += other.len;
  }

  /**
   * Returns the element at the specified position.
   * @param index of the element to return
   * @return the element at the specified position
   * @throws IndexOutOfBoundsException if the index is out of range
   */
  public long get(int index) {
    if (index >= len) throw new IndexOutOfBoundsException();
    return data[index];
  }

  /**
   * Sorts the vector.
   */
  public void sort() {
    Arrays.sort(data, 0, len);
  }

  /**
   * Merges another vector into this one, retaining sort order.
   * Both vectors must initially be sorted. The other vector will not be
   * modified.
   * @param other the vector to merge into this vector
   */
  public void merge(LongVec other) {
    // http://www.programcreek.com/2012/12/leetcode-merge-sorted-array-java/
    reserve(other.len());

    int m = len;
    int n = other.len;

    while (m > 0 && n > 0) {
      if (data[m-1] > other.data[n-1]) {
        data[m+n-1] = data[m-1];
        m--;
      } else {
        data[m+n-1] = other.data[n-1];
        n--;
      }
    }

    while (n > 0) {
      data[m+n-1] = other.data[n-1];
      n--;
    }
    len += other.len;
  }

  /**
   * Removes all values from this vector that are not contained in the other
   * vector.
   * Both vectors should initially be sorted. This vector will remain sorted.
   * The other vector will not be modified. Duplicate values in both vectors
   * will be preserved.
   * @param other the vector to intersect with this vector
   */
  public void intersect(LongVec other) {
    int writeOffset = 0;
    int otherOffset = 0;

    for (int thisOffset = 0; thisOffset < len; thisOffset++) {
      int index = Arrays.binarySearch(other.data, otherOffset, other.len, data[thisOffset]);
      if (index < 0) {
        otherOffset = -index - 1;
      } else {
        data[writeOffset++] = other.data[index];
        otherOffset++; // prevent matching the element in the other vector again.
      }
    }
    this.len = writeOffset;
  }

  /**
   * Removes consecutive repeated elements in the vector.
   * If the vector is sorted, this removes all duplicates.
   */
  public void dedup() {
    if (len <= 1) return;

    int writeOffset = 1;
    for (int readOffset = 1; readOffset < len; readOffset++) {
      if (data[writeOffset - 1] != data[readOffset]) data[writeOffset++] = data[readOffset];
    }
    len = writeOffset;
  }

  /**
   * Creates an iterator over this vector.
   * The vector should not be concurrently modified while the iterator is in use.
   * @return an iterator over the vector
   */
  public Iterator iterator() {
    return new Iterator();
  }

  /**
   * Returns a list view of the vector.
   * The vector should not be concurrently modified while the list is in use.
   * @return a list view of the vector
   */
  public List<Long> asList() {
    List<Long> list = Longs.asList(data);
    if (len < data.length) return list.subList(0, len);
    return list;
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    if (len == 0) {
      return "[]";
    }

    StringBuilder builder = new StringBuilder(len * 5);
    builder.append('[');
    builder.append(data[0]);
    for (int i = 1; i < len; i++) {
      builder.append(", ").append(data[i]);
    }
    builder.append(']');
    return builder.toString();
  }

  /** {@inheritDoc} */
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    LongVec other = (LongVec) o;
    if (len != other.len) return false;
    for (int i = 0; i < len; i++) if (data[i] != other.data[i]) return false;
    return true;
  }

  /** {@inheritDoc} */
  @Override
  public int hashCode() {
    long result = len;
    for (int i = 0; i < len; i++) result = 31 * result + data[i];
    return (int) result;
  }

  /** {@inheritDoc} */
  @Override
  protected LongVec clone() {
    LongVec clone = new LongVec(0);
    clone.data = Arrays.copyOf(data, data.length);
    clone.len = len;
    return clone;
  }

  /** An iterator of primitive longs. */
  public class Iterator {
    int index = 0;

    private Iterator() {}

    /**
     * Returns the next element in the iterator.
     * @return the next element
     */
    public long next() {
      return data[index++];
    }

    /**
     * Returns the next element in the iterator without changing the iterator's position.
     * @return the next element
     */
    public long peek() {
      return data[index];
    }

    /**
     * Returns {@code true} if the iterator contains another element.
     * @return {@code true} if the iterator has more elements
     */
    public boolean hasNext() {
      return index < len;
    }

    /**
     * Seeks this iterator to the provided index.
     * @param index the index to seek to
     * @throws IndexOutOfBoundsException if the index is out of bounds of the vector
     */
    public void seek(int index) {
      if (index < 0 || index > len) throw new IndexOutOfBoundsException("seek");
      this.index = index;
    }

    /**
     * Seek to the first datapoint greater than or equal to the provided value.
     * @param value to seek to
     */
    public void seekToValue(long value) {
      int offset = Arrays.binarySearch(data, value);
      index = offset >= 0 ? offset : -offset - 1;
    }

    /**
     * Get the iterator's current index in the vector.
     * @return the index
     */
    public int getIndex() {
      return index;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
      return MoreObjects.toStringHelper(this)
                        .add("index", index)
                        .add("vec", LongVec.this)
                        .toString();
    }
  }
}