package io.ray.api.id;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import javax.xml.bind.DatatypeConverter;

public abstract class BaseId implements Serializable {
  private static final long serialVersionUID = 8588849129675565761L;
  private final byte[] id;
  private transient int hashCodeCache = 0;
  private transient Boolean isNilCache = null;

  /** Create a BaseId instance according to the input byte array. */
  protected BaseId(byte[] id) {
    if (id.length != size()) {
      throw new IllegalArgumentException(
          "Failed to construct BaseId, expect "
              + size()
              + " bytes, but got "
              + id.length
              + " bytes.");
    }
    this.id = id;
  }

  /** Get the byte data of this id. */
  public byte[] getBytes() {
    return id;
  }

  /** Convert the byte data to a ByteBuffer. */
  public ByteBuffer toByteBuffer() {
    return ByteBuffer.wrap(id);
  }

  /** Returns true if this id is nil. */
  public boolean isNil() {
    if (isNilCache == null) {
      boolean localIsNil = true;
      for (int i = 0; i < size(); ++i) {
        if (id[i] != (byte) 0xff) {
          localIsNil = false;
          break;
        }
      }
      isNilCache = localIsNil;
    }
    return isNilCache;
  }

  /**
   * Derived class should implement this function.
   *
   * @return The length of this id in bytes.
   */
  public abstract int size();

  @Override
  public int hashCode() {
    // Lazy evaluation.
    if (hashCodeCache == 0) {
      hashCodeCache = Arrays.hashCode(id);
    }
    return hashCodeCache;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null) {
      return false;
    }

    if (!this.getClass().equals(obj.getClass())) {
      return false;
    }

    BaseId r = (BaseId) obj;
    return Arrays.equals(id, r.id);
  }

  @Override
  public String toString() {
    return DatatypeConverter.printHexBinary(id).toLowerCase();
  }

  protected static byte[] hexString2Bytes(String hex) {
    return DatatypeConverter.parseHexBinary(hex);
  }

  protected static byte[] byteBuffer2Bytes(ByteBuffer bb) {
    byte[] id = new byte[bb.remaining()];
    bb.get(id);
    return id;
  }
}
