package com.milaboratory.core.motif;

import java.util.Arrays;

public final class LongArrayBitHelper {
    private LongArrayBitHelper() {
    }

    public static void assign(long[] a, long[] b) {
        assert a.length == b.length;
        System.arraycopy(b, 0, a, 0, a.length);
    }

    /** a &= b */
    public static void andAssign(long[] a, long[] b) {
        if (a.length != b.length)
            throw new IllegalArgumentException();
        for (int i = 0; i < a.length; i++)
            a[i] &= b[i];
    }

    /** a &= b & c & d */
    public static void andAssign(long[] a, long[] b, long[] c, long[] d) {
        if (a.length != b.length || b.length != c.length || c.length != d.length)
            throw new IllegalArgumentException();
        for (int i = 0; i < a.length; i++)
            a[i] &= b[i] & c[i] & d[i];
    }

    /** a |= b */
    public static void orAssign(long[] a, long[] b) {
        if (a.length != b.length)
            throw new IllegalArgumentException();
        for (int i = 0; i < a.length; i++)
            a[i] |= b[i];
    }

    public static int upDivideBy64(int i) {
        return (i + 63) / 64;
    }

    public static long[] ones(int elements) {
        long[] a = new long[elements];
        Arrays.fill(a, ~0L);
        return a;
    }

    public static long[][] ones2D(int elements1, int elements2) {
        long[][] a = new long[elements1][elements2];
        for (int i = 0; i < elements1; i++)
            Arrays.fill(a[i], ~0L);
        return a;
    }

    public static void setAll(long[] a) {
        Arrays.fill(a, ~0L);
    }

    public static void resetBitLittleEndian(long[] a, int idx) {
        a[idx / 64] &= ~(1L << (idx % 64));
    }

    public static void setBitLittleEndian(long[] a, int idx) {
        a[idx / 64] |= (1L << (idx % 64));
    }

    public static boolean getBitLittleEndian(long[] a, int idx) {
        return ((a[idx / 64] >>> (idx % 64)) & 1L) == 1L;
    }

    // Following code adopted from https://github.com/patrickfav/bytes-java

    private static final long LONG_ALL_ONE = 0xFFFFFFFFFFFFFFFFL;

    public static void rightShiftBigEndian(long[] a, int shift) {
        final int shiftMod = shift % 64;
        final long carryMask = LONG_ALL_ONE << (64 - shiftMod);
        final int offsetBytes = shift / 64;

        int sourceIndex;
        for (int i = a.length - 1; i >= 0; i--) {
            sourceIndex = i - offsetBytes;
            if (sourceIndex < 0)
                a[i] = 0;
            else {
                long src = a[sourceIndex];
                long dst = src >>> shiftMod;
                if (sourceIndex - 1 >= 0)
                    dst |= a[sourceIndex - 1] << (64 - shiftMod) & carryMask;
                a[i] = dst;
            }
        }
    }

    public static void rightShiftLittleEndian(long[] a, int shift) {
        final int shiftMod = shift % 64;
        final long carryMask = LONG_ALL_ONE << (64 - shiftMod);
        final int offsetBytes = shift / 64;

        int sourceIndex;
        for (int i = 0; i < a.length; i++) {
            sourceIndex = i + offsetBytes;
            if (sourceIndex >= a.length)
                a[i] = 0;
            else {
                long src = a[sourceIndex];
                long dst = src >>> shiftMod;
                if (sourceIndex + 1 < a.length)
                    dst |= a[sourceIndex + 1] << (64 - shiftMod) & carryMask;
                a[i] = dst;
            }
        }
    }

    public static void leftShiftBigEndian(long[] a, int shift) {
        final int shiftMod = shift % 64;
        final long carryMask = (1L << shiftMod) - 1L;
        final int offsetBytes = shift / 64;

        int sourceIndex;
        for (int i = 0; i < a.length; i++) {
            sourceIndex = i + offsetBytes;
            if (sourceIndex >= a.length)
                a[i] = 0;
            else {
                long src = a[sourceIndex];
                long dst = src << shiftMod;
                if (sourceIndex + 1 < a.length)
                    dst |= a[sourceIndex + 1] >>> (64 - shiftMod) & carryMask;
                a[i] = dst;
            }
        }
    }

    public static void leftShiftLittleEndian(long[] a, int shift) {
        final int shiftMod = shift % 64;
        final long carryMask = (1L << shiftMod) - 1L;
        final int offsetBytes = shift / 64;

        int sourceIndex;
        for (int i = a.length - 1; i >= 0; i--) {
            sourceIndex = i - offsetBytes;
            if (sourceIndex < 0)
                a[i] = 0;
            else {
                long src = a[sourceIndex];
                long dst = src << shiftMod;
                if (sourceIndex - 1 >= 0)
                    dst |= a[sourceIndex - 1] >>> (64 - shiftMod) & carryMask;
                a[i] = dst;
            }
        }
    }
}
