package org.fisco.bcos.web3j.crypto.gm.sm3;

public class SM3 {
    /*public static final byte[] iv = { 0x2C, (byte) 0x91, (byte) 0xB4, 0x01,
    (byte) 0xFC, 0x64, (byte) 0xB2, (byte) 0xCE, 0x7C, 0x4E,
    (byte) 0xAE, (byte) 0xFB, (byte) 0xB1, 0x3B, (byte) 0xB6,
    (byte) 0xD3, 0x17, 0x60, (byte) 0xB6, 0x35, (byte) 0xF3, 0x6F,
    0x13, (byte) 0xEB, (byte) 0xC8, 0x77, (byte) 0xE9, (byte) 0xA0,
    (byte) 0xC2, 0x76, (byte) 0xA8, 0x17 };*/

    public static final byte[] iv = {
        0x73,
        (byte) 0x80,
        0x16,
        0x6f,
        0x49,
        0x14,
        (byte) 0xb2,
        (byte) 0xb9,
        0x17,
        0x24,
        0x42,
        (byte) 0xd7,
        (byte) 0xda,
        (byte) 0x8a,
        0x06,
        0x00,
        (byte) 0xa9,
        0x6f,
        0x30,
        (byte) 0xbc,
        (byte) 0x16,
        0x31,
        0x38,
        (byte) 0xaa,
        (byte) 0xe3,
        (byte) 0x8d,
        (byte) 0xee,
        0x4d,
        (byte) 0xb0,
        (byte) 0xfb,
        0x0e,
        0x4e
    };

    public static int[] Tj = new int[64];

    static {
        for (int i = 0; i < 16; i++) {
            Tj[i] = 0x79cc4519;
        }

        for (int i = 16; i < 64; i++) {
            Tj[i] = 0x7a879d8a;
        }
    }

    public static byte[] CF(byte[] V, byte[] B) {
        int[] v, b;
        v = convert(V);
        b = convert(B);
        return convert(CF(v, b));
    }

    private static int[] convert(byte[] arr) {
        int[] out = new int[arr.length / 4];
        byte[] tmp = new byte[4];
        for (int i = 0; i < arr.length; i += 4) {
            System.arraycopy(arr, i, tmp, 0, 4);
            out[i / 4] = bigEndianByteToInt(tmp);
        }
        return out;
    }

    private static byte[] convert(int[] arr) {
        byte[] out = new byte[arr.length * 4];
        byte[] tmp = null;
        for (int i = 0; i < arr.length; i++) {
            tmp = bigEndianIntToByte(arr[i]);
            System.arraycopy(tmp, 0, out, i * 4, 4);
        }
        return out;
    }

    public static int[] CF(int[] V, int[] B) {
        int a, b, c, d, e, f, g, h;
        int ss1, ss2, tt1, tt2;
        a = V[0];
        b = V[1];
        c = V[2];
        d = V[3];
        e = V[4];
        f = V[5];
        g = V[6];
        h = V[7];

        /*System.out.println("IV: ");
        System.out.print(Integer.toHexString(a)+" ");
        System.out.print(Integer.toHexString(b)+" ");
        System.out.print(Integer.toHexString(c)+" ");
        System.out.print(Integer.toHexString(d)+" ");
        System.out.print(Integer.toHexString(e)+" ");
        System.out.print(Integer.toHexString(f)+" ");
        System.out.print(Integer.toHexString(g)+" ");
        System.out.print(Integer.toHexString(h)+" ");
        System.out.println("");
        System.out.println("");

        System.out.println("填充后的消息: ");
        for(int i=0; i<B.length; i++)
        {
        	System.out.print(Integer.toHexString(B[i])+" ");
        }
        System.out.println("");
        System.out.println("");*/

        int[][] arr = expand(B);
        int[] w = arr[0];
        int[] w1 = arr[1];

        /*System.out.println("扩展后的消息： ");
        System.out.println("W0W1...W67");
        print(w);
        System.out.println("");
        System.out.println("W'0W'1...W'67");
        print(w1);
        System.out.println("迭代压缩中间值: ");*/

        for (int j = 0; j < 64; j++) {
            ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(Tj[j], j));
            ss1 = bitCycleLeft(ss1, 7);
            ss2 = ss1 ^ bitCycleLeft(a, 12);
            tt1 = FFj(a, b, c, j) + d + ss2 + w1[j];
            tt2 = GGj(e, f, g, j) + h + ss1 + w[j];
            d = c;
            c = bitCycleLeft(b, 9);
            b = a;
            a = tt1;
            h = g;
            g = bitCycleLeft(f, 19);
            f = e;
            e = P0(tt2);

            /*System.out.print(j+" ");
            System.out.print(Integer.toHexString(a)+" ");
            System.out.print(Integer.toHexString(b)+" ");
            System.out.print(Integer.toHexString(c)+" ");
            System.out.print(Integer.toHexString(d)+" ");
            System.out.print(Integer.toHexString(e)+" ");
            System.out.print(Integer.toHexString(f)+" ");
            System.out.print(Integer.toHexString(g)+" ");
            System.out.print(Integer.toHexString(h)+" ");
            System.out.println("");*/
        }
        //		System.out.println("");

        int[] out = new int[8];
        out[0] = a ^ V[0];
        out[1] = b ^ V[1];
        out[2] = c ^ V[2];
        out[3] = d ^ V[3];
        out[4] = e ^ V[4];
        out[5] = f ^ V[5];
        out[6] = g ^ V[6];
        out[7] = h ^ V[7];

        return out;
    }

    private static int[][] expand(int[] B) {
        int W[] = new int[68];
        int W1[] = new int[64];
        for (int i = 0; i < B.length; i++) {
            W[i] = B[i];
        }

        for (int i = 16; i < 68; i++) {
            W[i] =
                    P1(W[i - 16] ^ W[i - 9] ^ bitCycleLeft(W[i - 3], 15))
                            ^ bitCycleLeft(W[i - 13], 7)
                            ^ W[i - 6];
        }

        for (int i = 0; i < 64; i++) {
            W1[i] = W[i] ^ W[i + 4];
        }

        int arr[][] = new int[][] {W, W1};
        return arr;
    }

    private static byte[] bigEndianIntToByte(int num) {
        return back(Util.intToBytes(num));
    }

    private static int bigEndianByteToInt(byte[] bytes) {
        return Util.byteToInt(back(bytes));
    }

    private static int FFj(int X, int Y, int Z, int j) {
        if (j >= 0 && j <= 15) {
            return FF1j(X, Y, Z);
        } else {
            return FF2j(X, Y, Z);
        }
    }

    private static int GGj(int X, int Y, int Z, int j) {
        if (j >= 0 && j <= 15) {
            return GG1j(X, Y, Z);
        } else {
            return GG2j(X, Y, Z);
        }
    }

    // 逻辑位运算函数
    private static int FF1j(int X, int Y, int Z) {
        int tmp = X ^ Y ^ Z;
        return tmp;
    }

    private static int FF2j(int X, int Y, int Z) {
        int tmp = ((X & Y) | (X & Z) | (Y & Z));
        return tmp;
    }

    private static int GG1j(int X, int Y, int Z) {
        int tmp = X ^ Y ^ Z;
        return tmp;
    }

    private static int GG2j(int X, int Y, int Z) {
        int tmp = (X & Y) | (~X & Z);
        return tmp;
    }

    private static int P0(int X) {
        int y = rotateLeft(X, 9);
        y = bitCycleLeft(X, 9);
        int z = rotateLeft(X, 17);
        z = bitCycleLeft(X, 17);
        int t = X ^ y ^ z;
        return t;
    }

    private static int P1(int X) {
        int t = X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23);
        return t;
    }

    /**
     * 对最后一个分组字节数据padding
     *
     * @param in
     * @param bLen 分组个数
     * @return
     */
    public static byte[] padding(byte[] in, int bLen) {
        int k = 448 - (8 * in.length + 1) % 512;
        if (k < 0) {
            k = 960 - (8 * in.length + 1) % 512;
        }
        k += 1;
        byte[] padd = new byte[k / 8];
        padd[0] = (byte) 0x80;
        long n = in.length * 8L + bLen * 512L;
        byte[] out = new byte[in.length + k / 8 + 64 / 8];
        int pos = 0;
        System.arraycopy(in, 0, out, 0, in.length);
        pos += in.length;
        System.arraycopy(padd, 0, out, pos, padd.length);
        pos += padd.length;
        byte[] tmp = back(Util.longToBytes(n));
        System.arraycopy(tmp, 0, out, pos, tmp.length);
        return out;
    }

    /**
     * 字节数组逆序
     *
     * @param in
     * @return
     */
    private static byte[] back(byte[] in) {
        byte[] out = new byte[in.length];
        for (int i = 0; i < out.length; i++) {
            out[i] = in[out.length - i - 1];
        }

        return out;
    }

    public static int rotateLeft(int x, int n) {
        return (x << n) | (x >> (32 - n));
    }

    private static int bitCycleLeft(int n, int bitLen) {
        bitLen %= 32;
        byte[] tmp = bigEndianIntToByte(n);
        int byteLen = bitLen / 8;
        int len = bitLen % 8;
        if (byteLen > 0) {
            tmp = byteCycleLeft(tmp, byteLen);
        }

        if (len > 0) {
            tmp = bitSmall8CycleLeft(tmp, len);
        }

        return bigEndianByteToInt(tmp);
    }

    private static byte[] bitSmall8CycleLeft(byte[] in, int len) {
        byte[] tmp = new byte[in.length];
        int t1, t2, t3;
        for (int i = 0; i < tmp.length; i++) {
            t1 = (byte) ((in[i] & 0x000000ff) << len);
            t2 = (byte) ((in[(i + 1) % tmp.length] & 0x000000ff) >> (8 - len));
            t3 = (byte) (t1 | t2);
            tmp[i] = (byte) t3;
        }

        return tmp;
    }

    private static byte[] byteCycleLeft(byte[] in, int byteLen) {
        byte[] tmp = new byte[in.length];
        System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen);
        System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen);
        return tmp;
    }

    /*private static void print(int[] arr)
    {
    	for (int i = 0; i < arr.length; i++)
    	{
    		System.out.print(Integer.toHexString(arr[i]) + " ");
    		if ((i + 1) % 16 == 0)
    		{
    			System.out.println();
    		}
    	}
    	System.out.println();
    }*/
}
