/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.pinterest.pinlater.commons.util;

import com.google.common.base.Charsets;
import org.jboss.netty.buffer.ChannelBuffer;

import java.nio.ByteBuffer;
import java.util.Formatter;

public class BytesUtil {

  /**
   * Converts a hex string to bytes.
   *
   * @param s a hex string
   * @return byte array
   */
  public static byte[] hexStringToByteArray(String s) {
    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
      data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                + Character.digit(s.charAt(i + 1), 16));
    }
    return data;
  }

  /**
   * Converts a byte array to hex string.
   *
   * @param bytes a byte array.
   * @param pos   the start position
   * @param len   number of bytes to be converted
   * @return hex string.
   */
  public static String byteArrayToHexString(byte[] bytes, int pos, int len) {
    StringBuilder sb = new StringBuilder(bytes.length * 2);
    Formatter formatter = new Formatter(sb);
    for (int i = pos; i < pos + len; ++i) {
      formatter.format("%02x", bytes[i]);
    }
    return sb.toString();
  }

  /**
   * Reads the remaining bytes in a ByteBuffer into a byte[].
   *
   * @param byteBuffer byte buffer to read from.
   * @return byte[] containing the bytes read.
   */
  public static byte[] readBytesFromByteBuffer(ByteBuffer byteBuffer) {
    byte[] buffer = new byte[byteBuffer.remaining()];
    byteBuffer.get(buffer);
    return buffer;
  }

  /**
   * Reads the remaining bytes in a ByteBuffer into a byte[] without consuming.
   *
   * @param byteBuffer byte buffer to read from.
   * @return byte[] containing the bytes read.
   */
  public static byte[] readBytesFromByteBufferWithoutConsume(ByteBuffer byteBuffer) {
    byte[] buffer = new byte[byteBuffer.remaining()];
    byteBuffer.duplicate().get(buffer);
    return buffer;
  }

  public static ByteBuffer stringToByteBuffer(String str) {
    return ByteBuffer.wrap(str.getBytes(Charsets.UTF_8));
  }

  public static String stringFromByteBuffer(ByteBuffer byteBuf) {
    return new String(readBytesFromByteBuffer(byteBuf), Charsets.UTF_8);
  }

  public static ByteBuffer longToByteBuffer(long num) {
    return ByteBuffer.allocate(Long.SIZE / 8).putLong(0, num);
  }

  public static long longFromByteBuffer(ByteBuffer byteBuffer) {
    return ByteBuffer.wrap(readBytesFromByteBuffer(byteBuffer)).getLong();
  }

  public static ByteBuffer intToByteBuffer(int num) {
    return ByteBuffer.allocate(Integer.SIZE / 8).putInt(0, num);
  }

  public static int intFromByteBuffer(ByteBuffer byteBuffer) {
    return ByteBuffer.wrap(readBytesFromByteBuffer(byteBuffer)).getInt();
  }

  public static ByteBuffer doubleToByteBuffer(double val) {
    return ByteBuffer.allocate(Double.SIZE / 8).putDouble(val);
  }

  public static double doubleFromByteBuffer(ByteBuffer byteBuffer) {
    return ByteBuffer.wrap(readBytesFromByteBuffer(byteBuffer)).getDouble();
  }

  public static ByteBuffer byteToByteBuffer(byte num) {
    return ByteBuffer.allocate(1).put(num);
  }

  public static byte byteFromByteBuffer(ByteBuffer byteBuffer) {
    return ByteBuffer.wrap(readBytesFromByteBuffer(byteBuffer)).get();
  }

  public static byte[] toBytes(long num) {
    return readBytesFromByteBuffer(longToByteBuffer(num));
  }

  public static byte[] toBytes(String str) {
    return readBytesFromByteBuffer(stringToByteBuffer(str));
  }

  /**
   * Converts UNREAD BYTES to byte array from ChannelBuffer
   * NOTE: this will consume all the readable bytes from channel buffer
   */
  public static byte[] toBytesWithoutConsume(ChannelBuffer input) {
    // save old reader's index & reset it
    int oldIndex = input.readerIndex();
    input.resetReaderIndex();
    // read bytes out
    byte[] output = new byte[input.readableBytes()];
    input.readBytes(output);
    // set reader's index back to
    input.readerIndex(oldIndex);
    return output;
  }
}