/*
 * SonarQube
 * Copyright (C) 2009-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.duplications.block;

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

/**
 * Represents hash for {@link Block}. Immutable.
 * 
 * TODO Godin: would be better to rename to BlockHash,
 * also maybe we can incorporate it into Block to reduce memory footprint during detection of duplicates
 */
public final class ByteArray {
  private static final String HEXES = "0123456789abcdef";

  private final byte[] bytes;

  /**
   * Cache for hash code.
   */
  private int hash;

  public ByteArray(String hexString) {
    int len = hexString.length();
    this.bytes = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
      bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
    }
  }

  public ByteArray(byte[] bytes) {
    this.bytes = new byte[bytes.length];
    System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);
  }

  public ByteArray(long value) {
    this.bytes = new byte[] {
      (byte) (value >>> 56),
      (byte) (value >>> 48),
      (byte) (value >>> 40),
      (byte) (value >>> 32),
      (byte) (value >>> 24),
      (byte) (value >>> 16),
      (byte) (value >>> 8),
      (byte) value};
  }

  public ByteArray(int value) {
    this.bytes = new byte[] {
      (byte) (value >>> 24),
      (byte) (value >>> 16),
      (byte) (value >>> 8),
      (byte) value};
  }

  public ByteArray(int[] intArray) {
    ByteBuffer bb = ByteBuffer.allocate(intArray.length * 4);
    for (int i : intArray) {
      bb.putInt(i);
    }
    this.bytes = bb.array();
  }

  public byte[] getBytes() {
    return bytes;
  }

  public int[] toIntArray() {
    // Pad the size to multiple of 4
    int size = (bytes.length / 4) + (bytes.length % 4 == 0 ? 0 : 1);
    ByteBuffer bb = ByteBuffer.allocate(size * 4);
    bb.put(bytes);
    // see https://github.com/mongodb/mongo-java-driver/commit/21c91bd364d38489e0bbe2e390efdb3746ee3fff
    // The Java 9 ByteBuffer classes introduces overloaded methods with covariant return types for the following methods used by the driver:
// Without casting, exceptions like this are thrown when executing on Java 8 and lower:

   //  java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer

    //This is because the generated byte code includes the static return type of the method, which is not found on Java 8 and lower because
    //the overloaded methods with covariant return types don't exist.
    bb.rewind();
    IntBuffer ib = bb.asIntBuffer();
    int[] result = new int[size];
    ib.get(result);
    return result;
  }

  public String toHexString() {
    StringBuilder hex = new StringBuilder(2 * bytes.length);
    for (byte b : bytes) {
      hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt(b & 0x0F));
    }
    return hex.toString();
  }

  @Override
  public String toString() {
    return toHexString();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    ByteArray other = (ByteArray) o;
    return Arrays.equals(bytes, other.bytes);
  }

  @Override
  public int hashCode() {
    int h = hash;
    int len = bytes.length;
    if (h == 0 && len > 0) {
      h = Arrays.hashCode(bytes);
      hash = h;
    }
    return h;
  }

}
