/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.io;

import java.util.Arrays;

/**
 * Utility for hexidecimal encoding and decoding.
 *
 * @author  Middleware Services
 */
public final class Hex
{

  /** Hexidecimal characters. */
  private static final char[] HEX_CHARS = {
    '0',
    '1',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9',
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
  };

  /**
   * Decode table which stores characters from 0 to f. Anything higher than 'f' is invalid so that is the max size of
   * the array.
   */
  private static final byte[] DECODE = new byte['f' + 1];

  // Initialize the DECODE table
  // CheckStyle:MagicNumber OFF
  static {
    // set all values to -1 to indicate error
    Arrays.fill(DECODE, (byte) -1);
    // set values for hex 0-9
    for (int i = '0'; i <= '9'; i++) {
      DECODE[i] = (byte) (i - '0');
    }
    // set values for hex A-F
    for (int i = 'A'; i <= 'F'; i++) {
      DECODE[i] = (byte) (i - 'A' + 10);
    }
    // set values for hex a-f
    for (int i = 'a'; i <= 'f'; i++) {
      DECODE[i] = (byte) (i - 'a' + 10);
    }
  }
  // CheckStyle:MagicNumber ON


  /** Default constructor. */
  private Hex() {}


  /**
   * This will convert the supplied value to a hex encoded string. Returns null if the supplied value is null.
   *
   * @param  value  to hex encode
   *
   * @return  hex encoded value
   */
  public static char[] encode(final byte... value)
  {
    if (value == null) {
      return null;
    }

    final int l = value.length;
    final char[] encoded = new char[l << 1];
    // CheckStyle:MagicNumber OFF
    for (int i = 0, j = 0; i < l; i++) {
      encoded[j++] = HEX_CHARS[(0xF0 & value[i]) >>> 4];
      encoded[j++] = HEX_CHARS[0x0F & value[i]];
    }
    // CheckStyle:MagicNumber ON
    return encoded;
  }


  /**
   * This will convert the supplied value from a hex encoded string. Returns null if the supplied value is null.
   *
   * @param  value  to hex decode
   *
   * @return  hex decoded value
   *
   * @throws  IllegalArgumentException  if value is not valid hexidecimal
   */
  public static byte[] decode(final char... value)
  {
    if (value == null) {
      return null;
    }

    final int l = value.length;
    // CheckStyle:MagicNumber OFF
    if ((l & 0x01) != 0) {
      throw new IllegalArgumentException(
        String.format("Cannot decode odd number of characters for %s", String.valueOf(value)));
    }
    // CheckStyle:MagicNumber ON

    final byte[] decoded = new byte[l >> 1];

    // CheckStyle:MagicNumber OFF
    for (int i = 0, j = 0; j < l; i++, j += 2) {
      final int high = decode(value, j) << 4;
      final int low = decode(value, j + 1);
      decoded[i] = (byte) ((high | low) & 0xFF);
    }
    // CheckStyle:MagicNumber ON
    return decoded;
  }


  /**
   * Decodes the supplied character to its corresponding nibble.
   *
   * @param  hex  to read character from
   * @param  i  index of hex to read
   *
   * @return  0-15 integer
   *
   * @throws  IllegalArgumentException  if the character is not valid hex
   */
  protected static int decode(final char[] hex, final int i)
  {
    final char c = hex[i];
    if (c > 'f') {
      throw new IllegalArgumentException(
        String.format("Invalid hex character '%s' at position %s in %s", c, i, Arrays.toString(hex)));
    }

    final byte b = DECODE[c];
    if (b < 0) {
      throw new IllegalArgumentException(
        String.format("Invalid hex character '%s' at position %s in %s", c, i, Arrays.toString(hex)));
    }
    return b;
  }
}
