/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.ipc.invalidation.util;

import com.google.common.base.Preconditions;
import com.google.protobuf.ByteString;

import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * A class that encapsulates a (fixed size) sequence of bytes and provides a
 * equality (along with hashcode) method that considers two sequences to be
 * equal if they have the same contents. Borrowed from protobuf's ByteString
 *
 */
public class Bytes extends InternalBase implements Comparable<Bytes> {

  public static final Bytes EMPTY_BYTES = new Bytes(new byte[0]);

  /**
   * Three arrays that store the representation of each character from 0 to 255.
   * The ith number's octal representation is: CHAR_OCTAL_STRINGS1[i],
   * CHAR_OCTAL_STRINGS2[i], CHAR_OCTAL_STRINGS3[i]
   * <p>
   * E.g., if the number 128, these arrays contain 2, 0, 0 at index 128. We use
   * 3 char arrays instead of an array of strings since the code path for a
   * character append operation is quite a bit shorter than the append operation
   * for strings.
   */
  private static final char[] CHAR_OCTAL_STRINGS1 = new char[256];
  private static final char[] CHAR_OCTAL_STRINGS2 = new char[256];
  private static final char[] CHAR_OCTAL_STRINGS3 = new char[256];

  /** The actual sequence. */
  private final byte[] bytes;

  /** Cached hash */
  private volatile int hash = 0;

  static {
    // Initialize the array with the Octal string values so that we do not have
    // to do String.format for every byte during runtime.
    for (int i = 0; i < CHAR_OCTAL_STRINGS1.length; i++) {
      String value = String.format("\\%03o", i);
      CHAR_OCTAL_STRINGS1[i] = value.charAt(1);
      CHAR_OCTAL_STRINGS2[i] = value.charAt(2);
      CHAR_OCTAL_STRINGS3[i] = value.charAt(3);
    }
  }

  public Bytes(byte[] bytes) {
    this.bytes = bytes;
  }

  /**
   * Creates a Bytes object with the contents of {@code array1} followed by the
   * contents of {@code array2}.
   */
  public Bytes(byte[] array1, byte[] array2) {
    Preconditions.checkNotNull(array1);
    Preconditions.checkNotNull(array2);
    ByteBuffer buffer = ByteBuffer.allocate(array1.length + array2.length);
    buffer.put(array1);
    buffer.put(array2);
    this.bytes = buffer.array();
  }

  /**
   * Creates a Bytes object with the contents of {@code b1} followed by the
   * contents of {@code b2}.
   */
  public Bytes(Bytes b1, Bytes b2) {
    this(b1.bytes, b2.bytes);
  }

  public Bytes(byte b) {
    this.bytes = new byte[1];
    bytes[0] = b;
  }

  public Bytes(ByteString byteString) {
    this(byteString.toByteArray());
  }

  /**
   * Gets the byte at the given index.
   *
   * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size
   */
  public byte byteAt(final int index) {
    return bytes[index];
  }

  /**
   * Gets the number of bytes.
   */
  public int size() {
    return bytes.length;
  }

  /**
   * Returns the internal byte array.
   */
  public byte[] getByteArray() {
    return bytes;
  }

  /** Converts this to a byte string. */
  public ByteString toByteString() {
    return ByteString.copyFrom(getByteArray());
  }

  /**
   * Returns a new {@code Bytes} containing the given subrange of bytes [{@code
   * from}, {@code to}).
   */
  public Bytes subsequence(int from, int to) {
    // Identical semantics to Arrays.copyOfRange() but implemented manually
    // so runs on Froyo (JDK 1.5).
    int newLength = to - from;
    if (newLength < 0) {
      throw new IllegalArgumentException(from + " > " + to);
    }
    byte[] copy = new byte[newLength];
    System.arraycopy(bytes, from, copy, 0, Math.min(bytes.length - from, newLength));
    return new Bytes(copy);
  }

  @Override
  public boolean equals(final Object o) {
    if (o == this) {
      return true;
    }

    if (!(o instanceof Bytes)) {
      return false;
    }

    final Bytes other = (Bytes) o;
    return Arrays.equals(bytes, other.bytes);
  }

  @Override
  public int hashCode() {
    int h = hash;

    // If the hash has been not computed, go through each byte and compute it.
    if (h == 0) {
      final byte[] thisBytes = bytes;
      final int size = bytes.length;

      h = size;
      for (int i = 0; i < size; i++) {
        h = h * 31 + thisBytes[i];
      }
      if (h == 0) {
        h = 1;
      }

      hash = h;
    }

    return h;
  }

  /**
   * Returns whether these bytes are a prefix (either proper or improper) of
   * {@code other}.
   */
  public boolean isPrefixOf(Bytes other) {
    Preconditions.checkNotNull(other);
    if (size() > other.size()) {
      return false;
    }
    for (int i = 0; i < size(); ++i) {
      if (bytes[i] != other.bytes[i]) {
        return false;
      }
    }
    return true;
  }

  /**
   * Returns whether these bytes are a suffix (either proper or improper) of
   * {@code other}.
   */
  public boolean isSuffixOf(Bytes other) {
    Preconditions.checkNotNull(other);
    int diff = other.size() - size();
    if (diff < 0) {
      return false;
    }
    for (int i = 0; i < size(); ++i) {
      if (bytes[i] != other.bytes[i + diff]) {
        return false;
      }
    }
    return true;
  }

  @Override
  public int compareTo(Bytes other) {
    return compare(bytes, other.bytes);
  }

  /** Same specs as Bytes.compareTo except for the byte[] type. */
  public static int compare(byte[] first, byte[] second) {
    int minLength = Math.min(first.length, second.length);
    for (int i = 0; i < minLength; i++) {
      if (first[i] != second[i]) {
        int firstByte = first[i] & 0xff;
        int secondByte = second[i] & 0xff;
        return firstByte - secondByte;
      }
    }
    // At this point, either both arrays are equal length or one of the arrays has ended.
    // * If the arrays are of equal length, they must be identical (else we would have
    //   returned the correct value above
    // * If they are not of equal length, the one with the longer length is greater.
    return first.length - second.length;
  }

  /** Compares lexicographic order of {@code first} and {@code second}. */
  public static int compare(ByteString first, ByteString second) {
    Preconditions.checkNotNull(first);
    Preconditions.checkNotNull(second);

    // Note: size() is O(1) on ByteString.
    for (int i = 0; i < first.size(); ++i) {
      if (i == second.size()) {
        // 'first' is longer than 'second' (logically, think of 'second' as padded with special
        // 'blank' symbols that are smaller than any other symbol per the usual lexicographic
        // ordering convention.)
        return +1;
      }
      byte firstByte = first.byteAt(i);
      byte secondByte = second.byteAt(i);
      if (firstByte != secondByte) {
        return (firstByte & 0xff) - (secondByte & 0xff);
      }
    }
    // We ran through both strings and found no differences. If 'second' is longer than 'first',
    // then we return -1. Otherwise, it implies that both strings have been consumed and no
    // differences discovered in which case we return 0.
    return (second.size() > first.size()) ? -1 : 0;
  }

  /**
   * Renders the bytes as a string in standard bigtable ascii / octal mix
   * compatible with bt and returns it. Borrowed from Bigtable's
   * Util.keyToString().
   */
  public static String toString(ByteString bytes) {
    return toString(bytes.toByteArray());
  }

  /**
   * Renders the bytes as a string in standard bigtable ascii / octal mix
   * compatible with bt and returns it. Borrowed from Bigtable's
   * Util.keyToString().
   */
  public static String toString(byte[] bytes) {
    return toCompactString(new TextBuilder(), bytes).toString();
  }

  /**
   * Renders the bytes as a string in standard bigtable ascii / octal mix
   * compatible with bt and adds it to builder. Borrowed from Bigtable's
   * Util.keyToString().
   */
  @Override
  public void toCompactString(TextBuilder builder) {
    toCompactString(builder, bytes);
  }

  /**
   * Renders the bytes as a string in standard bigtable ascii / octal mix
   * compatible with bt and adds it to builder. Borrowed from Bigtable's
   * Util.keyToString(). Returns {@code builder}.
   */
  public static TextBuilder toCompactString(TextBuilder builder, byte[] bytes) {
    for (byte c : bytes) {
      switch(c) {
        case '\n': builder.append('\\'); builder.append('n'); break;
        case '\r': builder.append('\\'); builder.append('r'); break;
        case '\t': builder.append('\\'); builder.append('t'); break;
        case '\"': builder.append('\\'); builder.append('"'); break;
        case '\\': builder.append('\\'); builder.append('\\'); break;
        default:
          if ((c >= 32) && (c < 127) && c != '\'') {
            builder.append((char) c);
          } else {
            int byteValue = c;
            if (c < 0) {
              byteValue = c + 256;
            }
            builder.append('\\');
            builder.append(CHAR_OCTAL_STRINGS1[byteValue]);
            builder.append(CHAR_OCTAL_STRINGS2[byteValue]);
            builder.append(CHAR_OCTAL_STRINGS3[byteValue]);
          }
      }
    }
    return builder;
  }
}
