package com.oraclechain.eosio.crypto.util;


import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Utilities for converting between byte arrays and unsigned integer values.
 */
public class BitUtils {

    public static long uint16ToLong(byte[] buf, int offset) {
        return ((buf[offset++] & 0xFFL)) | ((buf[offset++] & 0xFFL) << 8);
    }

    public static long uint32ToLong(byte[] buf, int offset) {
        return ((buf[offset++] & 0xFFL)) | ((buf[offset++] & 0xFFL) << 8) | ((buf[offset++] & 0xFFL) << 16)
                | ((buf[offset] & 0xFFL) << 24);
    }

    public static long uint64ToLong(byte[] buf, int offset) {
        return ((buf[offset++] & 0xFFL)) | ((buf[offset++] & 0xFFL) << 8) | ((buf[offset++] & 0xFFL) << 16)
                | ((buf[offset++] & 0xFFL) << 24) | ((buf[offset++] & 0xFFL) << 32) | ((buf[offset++] & 0xFFL) << 40)
                | ((buf[offset++] & 0xFFL) << 48) | ((buf[offset++] & 0xFFL) << 56);
    }

    public static int uint16FromStream(DataInputStream stream) throws IOException {
        return (int) (((stream.read() & 0xFFL)) | ((stream.read() & 0xFFL) << 8));
    }

    public static int uint16FromStreamBE(DataInputStream stream) throws IOException {
        return (int) (((stream.read() & 0xFFL) << 8) | ((stream.read() & 0xFFL)));
    }

    public static int uint32FromStream(DataInputStream stream) throws IOException {
        return (int) (((stream.read() & 0xFFL)) | ((stream.read() & 0xFFL) << 8) | ((stream.read() & 0xFFL) << 16) | ((stream
                .read() & 0xFFL) << 24));
    }

    public static long uint64FromStream(DataInputStream stream) throws IOException {
        return ((stream.read() & 0xFFL)) | ((stream.read() & 0xFFL) << 8) | ((stream.read() & 0xFFL) << 16)
                | ((stream.read() & 0xFFL) << 24) | ((stream.read() & 0xFFL) << 32) | ((stream.read() & 0xFFL) << 40)
                | ((stream.read() & 0xFFL) << 48) | ((stream.read() & 0xFFL) << 56);
    }

    public static void uint32ToStream(long value, OutputStream stream) throws IOException {
        stream.write((byte) (0xFFL & (value)));
        stream.write((byte) (0xFFL & (value >> 8)));
        stream.write((byte) (0xFFL & (value >> 16)));
        stream.write((byte) (0xFFL & (value >> 24)));
    }

    public static void uint64ToStream(long value, OutputStream stream) throws IOException {
        stream.write((byte) (0xFFL & (value)));
        stream.write((byte) (0xFFL & (value >> 8)));
        stream.write((byte) (0xFFL & (value >> 16)));
        stream.write((byte) (0xFFL & (value >> 24)));
        stream.write((byte) (0xFFL & (value >> 32)));
        stream.write((byte) (0xFFL & (value >> 40)));
        stream.write((byte) (0xFFL & (value >> 48)));
        stream.write((byte) (0xFFL & (value >> 56)));
    }

    public static void uint32ToByteArrayLE(long value, byte[] output, int offset) {
        output[offset] = (byte) (0xFFL & (value));
        output[offset + 1] = (byte) (0xFFL & (value >> 8));
        output[offset + 2] = (byte) (0xFFL & (value >> 16));
        output[offset + 3] = (byte) (0xFFL & (value >> 24));
    }

    public static void uint64ToByteArrayLE(long value, byte[] output, int offset) {
        output[offset] = (byte) (0xFFL & (value));
        output[offset + 1] = (byte) (0xFFL & (value >> 8));
        output[offset + 2] = (byte) (0xFFL & (value >> 16));
        output[offset + 3] = (byte) (0xFFL & (value >> 24));
        output[offset + 4] = (byte) (0xFFL & (value >> 32));
        output[offset + 5] = (byte) (0xFFL & (value >> 40));
        output[offset + 6] = (byte) (0xFFL & (value >> 48));
        output[offset + 7] = (byte) (0xFFL & (value >> 56));
    }

    public static boolean areEqual(byte[] a, byte[] b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        if (a.length != b.length) {
            return false;
        }
        for (int i = 0; i < a.length; i++) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }

    public static byte[] copyByteArray(byte[] source) {
        byte[] buf = new byte[source.length];
        System.arraycopy(source, 0, buf, 0, buf.length);
        return buf;
    }

    /**
     * Returns a copy of the given byte array in reverse order.
     */
    public static byte[] reverseBytes(byte[] bytes) {
        byte[] buf = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++)
            buf[i] = bytes[bytes.length - 1 - i];
        return buf;
    }

    /**
     * Read a number of bytes or throw.
     *
     * @param size The number of bytes read
     * @return The array of bytes read.
     * @throws IOException
     */
    public static byte[] readBytes(DataInputStream stream, int size) throws IOException {
        byte[] buf = new byte[size];
        int toRead = size;
        int done = 0;
        while (toRead > 0) {
            int read = stream.read(buf, done, toRead);
            if (read == -1) {
                throw new IOException();
            }
            done += read;
            toRead -= read;
        }
        return buf;
    }

    // Arrays.copyOf implementation which we can use also on Java versions <
    // 1.6
    public static byte[] copyOf(byte[] original, int newLength) {
        if (newLength < 0) {
            throw new IllegalArgumentException();
        }
        byte[] buf = new byte[newLength];
        int lastIndex = Math.min(original.length, newLength);
        System.arraycopy(original, 0, buf, 0, lastIndex);
        return buf;
    }

    // Arrays.copyOfRange implementation which we can use also on Java versions <
    // 1.6
    public static byte[] copyOfRange(byte[] original, int from, int to) {
        if (to < from || from < 0 || from > original.length) {
            throw new IllegalArgumentException();
        }
        byte[] buf = new byte[to - from];
        int lastIndex = Math.min(original.length, to);
        System.arraycopy(original, from, buf, 0, lastIndex - from);
        return buf;
    }

    /**
     * Return the concatenation of two byte arrays in a new byte array
     */
    public static byte[] concatenate(byte[] a, byte[] b) {
        byte[] result = new byte[a.length + b.length];
        System.arraycopy(a, 0, result, 0, a.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }
}