import { hmacSha256 } from "./encrypt/hmacSha256"
import * as CryptoJS from "./encrypt/aes.js"

/**
 * 加密解密工具类
 * @author 邹勇
 */
export class EncryptUtil {

    /**
     * 随机生成多位字符串
     * @param   len    字符串长度
     * @returns 
     */
    public randomString(len = 32) {
        let $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnoprstuvwxyz123456789';
        let maxPos = $chars.length;
        let pwd = '';
        for (let i = 0; i < len; i++) {
            pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
        }
        return pwd;
    }

    //说明：
    //  1.如果加密解密涉及到前端和后端，则这里的key要保持和后端的key一致
    //  2.AES的算法模式有好几种（ECB,CBC,CFB,OFB），所以也要和后端保持一致
    //  3.AES的补码方式有两种（PKS5，PKS7），所以也要和后端保持一致
    //  4.AES的密钥长度有三种（128,192,256，默认是128），所以也要和后端保持一致
    //  5.AES的加密结果编码方式有两种（base64和十六进制），具体怎么选择由自己定，但是加密和解密的编码方式要统一
    //十六位十六进制数作为秘钥
    // private public key = fun_aes.CryptoJS.enc.Utf8.parse("3454345434543454");
    //十六位十六进制数作为秘钥偏移量
    // private public iv = fun_aes.CryptoJS.enc.Utf8.parse('6666666666666666');

    /**
     * 说明：
     * 1.如果加密解密涉及到前端和后端，则这里的key要保持和后端的key一致
     * 2.AES的算法模式有好几种（ECB,CBC,CFB,OFB），所以也要和后端保持一致
     * 3.AES的补码方式有两种（PKS5，PKS7），所以也要和后端保持一致
     * 4.AES的密钥长度有三种（128,192,256，默认是128），所以也要和后端保持一致
     * 5.AES的加密结果编码方式有两种（base64和十六进制），具体怎么选择由自己定，但是加密和解密的编码方式要统一
     * 十六位十六进制数作为秘钥
     * private public key = fun_aes.CryptoJS.enc.Utf8.parse("3454345434543454");
     * 十六位十六进制数作为秘钥偏移量
     * private public iv = fun_aes.CryptoJS.enc.Utf8.parse('6666666666666666');
     * 
     * aes加密
     * @param   word    需要加密的字符串
     * @param   tkey    加密key，一般为sessionkey
     * @param   appid   游戏的appid
     * @param   state   true 存包长 false /r/n
     * @returns 
    */
    public encrypt(word, tkey, appid, state = true) {
        let tiv = tkey.substr(0, 16);

        //明文拼接
        let newStr = '';
        if (state) {
            newStr = tiv + word + appid;
        }
        else {
            newStr = tiv + word + '\r\n' + appid;
        }

        //加密后
        let key = CryptoJS.enc.Utf8.parse(tkey);
        let iv = CryptoJS.enc.Utf8.parse(tiv);

        newStr = CryptoJS.enc.Utf8.parse(newStr);
        let encrypted = CryptoJS.AES.encrypt(newStr, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
        encrypted = encrypted.toString();
        let mac = hmacSha256.Instance.hex_hmac_sha256(tkey, encrypted);
        return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(tiv + encrypted + mac));
    }

    /**
     * aes解密
     * @param   word    需要加密的字符串
     * @param   tkey    加密key，一般为sessionkey
     * @param   appid   游戏的appid
     * @param   state   true 存包长 false /r/n
     * @returns 
     */
    public decrypt(word, tkey, appid, state = true) {
        let en = CryptoJS.enc.Base64.parse(word).toString(CryptoJS.enc.Utf8);
        let newStrOne = en.substr(16);
        let temp = newStrOne.substr(0, newStrOne.length - 64);
        let de = temp;

        let key = CryptoJS.enc.Utf8.parse(tkey);
        let iv = CryptoJS.enc.Utf8.parse(tkey.substr(0, 16));

        let decrypt = CryptoJS.AES.decrypt(de, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
        let decryptedStr = CryptoJS.enc.Utf8.stringify(decrypt);
        decryptedStr = decryptedStr.toString();

        let newStrThree = '';
        if (state) {
            newStrThree = decryptedStr.slice(16).replace(new RegExp("(.*)" + appid), '$1');
        }
        else {
            newStrThree = decryptedStr.slice(16).split('\r\n')[0];
        }
        return newStrThree;
    }

    private options = {
        default_key_size: 2048,
        default_public_exponent: new Uint8Array([0x01, 0x00, 0x01])
    }
    /**
     * rsa加密
     * @param   word        需要加密的字符串
     * @param   isLong      字符串加长版
     * @returns 
     */
    public rsaEncrypt(word, isLong: boolean = false) {
        let jsb = new window['JSEncrypt'](this.options);

        //公钥
        let publicString = '-----BEGIN PUBLIC KEY-----\n' +
            'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwz5V8wt525dMS+A9wOd\n' +
            'NAjvKgxdyoXYuZSlAJEXLhvJlaXwFCKEsUg/qBBeo4nrRnwaFAjG7ItqbCWR9Ozv\n' +
            '6oY5tMYQnFFK82XkMGbKm1gG0z9vYKhcNYgHZHi+QKAw4tLLWXWO/QBTGelEXptD\n' +
            'azlNmdM+8wHOknTcMuIyjc9Yo5qCoNsCJpA465vnREiBezBbKB3rSw+LmrRcZcFk\n' +
            's8hmGtjpabcQ8sCxco7mRzVh6/DomNWfNm7hUP6shrrJirwjQtaS51grlScj8KhN\n' +
            'h+kBoiI4Ad0Xz0GHasK5hAMf3sXDsN61r0An8KTfa/nsYSJDwogPIKkeKAsmw9Df\n' +
            'EQIDAQAB\n' +
            '-----END PUBLIC KEY-----'
        jsb.setPublicKey(publicString);
        // 加密内容
        let encrypted = isLong ? jsb.encryptLong(word) : jsb.encrypt(word);
        return encrypted;
    }

    /**
     * for php pack( N , length )
     * @param   len    
     * @returns 
     */
    public pack(len: number) {
        let buf: ArrayBuffer = new ArrayBuffer(4);
        let dataView = new DataView(buf);
        dataView.setUint32(0, len);
        let str2 = this.ab2str(buf);
        return str2;
    }

    /**
     * 
     * @param array 
     * @returns 
     */
    public utf8ArrayToStr(array) {
        let out, i, len, c;
        let char2, char3;

        out = "";
        len = array.length;
        i = 0;
        while (i < len) {
            c = array[i++];
            switch (c >> 4) {
                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                    // 0xxxxxxx
                    out += String.fromCharCode(c);
                    break;
                case 12: case 13:
                    // 110x xxxx   10xx xxxx
                    char2 = array[i++];
                    out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
                    break;
                case 14:
                    // 1110 xxxx  10xx xxxx  10xx xxxx
                    char2 = array[i++];
                    char3 = array[i++];
                    out += String.fromCharCode(((c & 0x0F) << 12) |
                        ((char2 & 0x3F) << 6) |
                        ((char3 & 0x3F) << 0));
                    break;
            }
        }

        return out;
    }

    /**
     * UTF-16转UTF-8
     * @param   s 
     * @returns 
     */
    public utf16ToUtf8(s) {
        if (!s) {
            return;
        }

        let i, code, ret = [], len = s.length;
        for (i = 0; i < len; i++) {
            code = s.charCodeAt(i);
            if (code > 0x0 && code <= 0x7f) {
                //单字节
                //UTF-16 0000 - 007F
                //UTF-8  0xxxxxxx
                ret.push(s.charAt(i));
            } else if (code >= 0x80 && code <= 0x7ff) {
                //双字节
                //UTF-16 0080 - 07FF
                //UTF-8  110xxxxx 10xxxxxx
                ret.push(
                    //110xxxxx
                    String.fromCharCode(0xc0 | ((code >> 6) & 0x1f)),
                    //10xxxxxx
                    String.fromCharCode(0x80 | (code & 0x3f))
                );
            } else if (code >= 0x800 && code <= 0xffff) {
                //三字节
                //UTF-16 0800 - FFFF
                //UTF-8  1110xxxx 10xxxxxx 10xxxxxx
                ret.push(
                    //1110xxxx
                    String.fromCharCode(0xe0 | ((code >> 12) & 0xf)),
                    //10xxxxxx
                    String.fromCharCode(0x80 | ((code >> 6) & 0x3f)),
                    //10xxxxxx
                    String.fromCharCode(0x80 | (code & 0x3f))
                );
            }
        }

        return ret.join('');
    }

    /**
     * UTF-8转UTF-16
     * @param s 
     * @returns 
     */
    public utf8ToUtf16(s) {
        if (!s) {
            return;
        }

        let i, codes, bytes, ret = [], len = s.length;
        for (i = 0; i < len; i++) {
            codes = [];
            codes.push(s.charCodeAt(i));
            if (((codes[0] >> 7) & 0xff) == 0x0) {
                //单字节  0xxxxxxx
                ret.push(s.charAt(i));
            } else if (((codes[0] >> 5) & 0xff) == 0x6) {
                //双字节  110xxxxx 10xxxxxx
                codes.push(s.charCodeAt(++i));
                bytes = [];
                bytes.push(codes[0] & 0x1f);
                bytes.push(codes[1] & 0x3f);
                ret.push(String.fromCharCode((bytes[0] << 6) | bytes[1]));
            } else if (((codes[0] >> 4) & 0xff) == 0xe) {
                //三字节  1110xxxx 10xxxxxx 10xxxxxx
                codes.push(s.charCodeAt(++i));
                codes.push(s.charCodeAt(++i));
                bytes = [];
                bytes.push((codes[0] << 4) | ((codes[1] >> 2) & 0xf));
                bytes.push(((codes[1] & 0x3) << 6) | (codes[2] & 0x3f));
                ret.push(String.fromCharCode((bytes[0] << 8) | bytes[1]));
            }
        }
        return ret.join('');
    }

    /**
     * ArrayBuffer转为字符串，参数为ArrayBuffer对象
     * @param buf 
     * @returns 
     */
    public ab2str(buf: ArrayBuffer) {
        return String.fromCharCode.apply(null, new Uint8Array(buf));
    }

    /**
     * 
     * @param fileData 
     * @returns 
     */
    public uint8ArrayToString(fileData) {
        let dataString = "";
        for (let i = 0; i < fileData.length; i++) {
            dataString += String.fromCharCode(fileData[i]);
        }

        return dataString
    }

    /**
     * 
     * @param str 
     * @returns 
     */
    public stringToUint8Array(str) {
        let arr = [];
        for (let i = 0, j = str.length; i < j; ++i) {
            arr.push(str.charCodeAt(i));
        }

        let tmpUint8Array = new Uint8Array(arr);
        return tmpUint8Array
    }

    /**
     * 字符串转为ArrayBuffer对象，参数为字符串
     * @param str 
     * @returns 
     */
    public str2ab(str) {
        let buf = new ArrayBuffer(str.length); // 每个字符占用2个字节
        let bufView = new Uint8Array(buf);
        for (let i = 0, strLen = str.length; i < strLen; i++) {
            bufView[i] = str.charCodeAt(i);
        }
        return buf;
    }

}