package com.zzwtec.wechat.util.security;

import java.io.UnsupportedEncodingException;

public class XTEA {

    private static final int BLOCK_ALIGN = 8;
    private static final int KEY_LENGTH = 16;
    private static final int DELTA = 0x9E3779B9;
    private static final int NUM_ROUNDS = 32;

    private static int[] getKey(byte[] b) {
        int[] key = new int[4];
        for (int i = 0; i < KEY_LENGTH; ) {
            key[i / 4] = (b[i++] << 24) + ((b[i++] & 255) << 16) + ((b[i++] & 255) << 8) + (b[i++] & 255);
        }
        int[] keys = new int[NUM_ROUNDS];
        for (int i = 0, sum = 0; i < NUM_ROUNDS; ) {
            keys[i++] = sum + key[sum & 3];
            sum += DELTA;
            keys[i++] = sum + key[(sum >>> 11) & 3];
        }
        return keys;
    }

    private static byte[] getIV(byte[] b) {
        byte[] ivtmp = new byte[BLOCK_ALIGN];
        System.arraycopy(b, 0, ivtmp, 0, BLOCK_ALIGN);
        return ivtmp;
    }

    private static byte[] padding(byte[] in, int len) {
        int lennew = len;
        int mod = len % BLOCK_ALIGN;
        if (mod != 0) {
            lennew = ((len + BLOCK_ALIGN) / BLOCK_ALIGN) * BLOCK_ALIGN;
        }
        byte[] innew = new byte[lennew];
        System.arraycopy(in, 0, innew, 0, len);

        return innew;
    }

    private static void encryptBlock(int[] keys, byte[] in, byte[] out, int off) {
        int y = (in[off] << 24) | ((in[off + 1] & 255) << 16) | ((in[off + 2] & 255) << 8) | (in[off + 3] & 255);
        int z = (in[off + 4] << 24) | ((in[off + 5] & 255) << 16) | ((in[off + 6] & 255) << 8) | (in[off + 7] & 255);
        for (int i = 0; i < NUM_ROUNDS; i += 2) {
            y += (((z << 4) ^ (z >>> 5)) + z) ^ keys[i];
            z += (((y >>> 5) ^ (y << 4)) + y) ^ keys[i + 1];
        }
        out[off] = (byte) (y >> 24);
        out[off + 1] = (byte) (y >> 16);
        out[off + 2] = (byte) (y >> 8);
        out[off + 3] = (byte) y;
        out[off + 4] = (byte) (z >> 24);
        out[off + 5] = (byte) (z >> 16);
        out[off + 6] = (byte) (z >> 8);
        out[off + 7] = (byte) z;
    }

    private static void decryptBlock(int[] keys, byte[] in, byte[] out, int off) {
        int y = (in[off] << 24) | ((in[off + 1] & 255) << 16) | ((in[off + 2] & 255) << 8) | (in[off + 3] & 255);
        int z = (in[off + 4] << 24) | ((in[off + 5] & 255) << 16) | ((in[off + 6] & 255) << 8) | (in[off + 7] & 255);
        for (int i = NUM_ROUNDS - 1; i >= 0; i -= 2) {
            z -= (((y >>> 5) ^ (y << 4)) + y) ^ keys[i];
            y -= (((z << 4) ^ (z >>> 5)) + z) ^ keys[i - 1];
        }
        out[off] = (byte) (y >> 24);
        out[off + 1] = (byte) (y >> 16);
        out[off + 2] = (byte) (y >> 8);
        out[off + 3] = (byte) y;
        out[off + 4] = (byte) (z >> 24);
        out[off + 5] = (byte) (z >> 16);
        out[off + 6] = (byte) (z >> 8);
        out[off + 7] = (byte) z;
    }

    public static byte[] encrypt(byte[] key, byte[] iv, byte[] in, int len) {
        if (key.length != KEY_LENGTH) {
            throw new RuntimeException("invalid key length, should be length of 16");
        }
        if (iv.length % BLOCK_ALIGN != 0) {
            throw new RuntimeException("invalid iv length, should be length of 8");
        }
        int[] keys = getKey(key);
        byte[] ivtmp = getIV(iv);

        byte[] plain = padding(in, len);

        for (int i = 0; i < plain.length; i += BLOCK_ALIGN) {
            for (int j = 0; j < BLOCK_ALIGN; j++) {
                plain[i + j] = (byte) (plain[i + j] ^ ivtmp[j]);
            }
            encryptBlock(keys, plain, plain, i);
            System.arraycopy(plain, i, ivtmp, 0, BLOCK_ALIGN);
        }
        return plain;
    }

    public static byte[] decrypt(byte[] key, byte[] iv, byte[] in, int len) {
        if (key.length != KEY_LENGTH) {
            throw new RuntimeException("invalid key length, should be length of 16");
        }
        if (iv.length % BLOCK_ALIGN != 0) {
            throw new RuntimeException("invalid iv length, should be length of 8");
        }
        if (len % BLOCK_ALIGN != 0) {
            throw new RuntimeException("invalid ciper length, should be multiple of 8");
        }
        int[] keys = getKey(key);
        byte[] ivtmp = getIV(iv);

        byte[] plain = new byte[len];
        byte[] tmp8 = new byte[BLOCK_ALIGN];
        for (int i = 0; i < len; i += BLOCK_ALIGN) {
            System.arraycopy(in, i, tmp8, 0, BLOCK_ALIGN);
            decryptBlock(keys, in, plain, i);
            for (int j = 0; j < BLOCK_ALIGN; j++) {
                plain[i + j] = (byte) (plain[i + j] ^ ivtmp[j]);
            }
            System.arraycopy(tmp8, 0, ivtmp, 0, BLOCK_ALIGN);
        }
        return plain;
    }

    public static String encrypt(String key, String iv, String in) {
        try {
            return encryptWithEx(key, iv, in);
        } catch (Exception e) {
            return null;
        }
    }

    public static String decrypt(String key, String iv, String inHexStr) {
        try {
            return decryptWithEx(key, iv, inHexStr);
        } catch (Exception e) {
            return null;
        }
    }

    public static String encryptWithEx(String key, String iv, String in) throws UnsupportedEncodingException {
        byte[] inBytes = in.getBytes("UTF8");
        byte[] cipher = encrypt(key.getBytes("UTF8"), iv.getBytes("UTF8"), inBytes, inBytes.length);
        return Hex.encodeHexStr(cipher);
    }

    public static String decryptWithEx(String key, String iv, String inHexStr) throws UnsupportedEncodingException {
        byte[] cipher = Hex.decodeHex(inHexStr.toCharArray());
        byte[] plain = decrypt(key.getBytes("UTF8"), iv.getBytes("UTF8"), cipher, cipher.length);
        int len = 0;
        while (len < plain.length && plain[len] != 0) {
            len++;
        }
        byte[] p2 = new byte[len];
        System.arraycopy(plain, 0, p2, 0, len);
        return new String(p2, "UTF-8");
    }

/*   public static void main(String[] args) {
        //		String key = "0123456789abcdef";
        //		String iv = "12345678";
        String key = "18c1326ae83347ab"; //长度128b，即16B
        String iv = "9afcd148"; //长度64b，即8B
        String text = "8148f67c9a261ff41a94be052a692801";

        String cipher = encrypt(key, iv, text);
        System.out.print("cipher string: " + cipher + "\n");

        String plain = decrypt(key, iv, cipher);
        System.out.print("plain string: " + plain + "\n");
    }*/
}
