/**
 * 国密摘要算法（SM3）
 * @param str:raw string
 * @return the 256-bit hex string produced by SM3 from a raw string
 */
function sm3Digest(str) {
    //1. 转换为二进制数组
    var binArr = str2bin(str2rstr_utf8(str));
    //2. 填充
    var groupNum = alignSM3(binArr, str.length);
    //3. 迭代压缩
    var v = new Array(8);//初始值
    v[0] = 0x7380166f;
    v[1] = 0x4914b2b9;
    v[2] = 0x172442d7;
    v[3] = 0xda8a0600;
    v[4] = 0xa96f30bc;
    v[5] = 0x163138aa;
    v[6] = 0xe38dee4d;
    v[7] = 0xb0fb0e4e;
    //按 512bit 分组进行压缩
    for (var i = 0; i < groupNum; i++) {
        v = compress(v, binArr, i);
    }
    return word2str(v, '');
}

/**
 * 将数组转换为字符串。数组长度不定，每个元素为 32bit 的数字。
 * @param words:数组，每个元素为 32bit 的数字
 * @param seperator:在每个数组元素转换得到的字符串之间的分隔符
 */
function word2str(words, seperator) {
    var prefix = Array(8).join('0');
    for (var i = 0; i < words.length; i++) {
        //若 hex 不足 8 位，则高位补 0
        words[i] = (prefix + (words[i] >>> 0).toString(16)).slice(-8);
    }

    return words.join(seperator);
}

/**
 * 将字符串转换为二进制数组，默认字符串编码为 UTF-8，且范围在 0x00~0xFF 内。
 * 若某些字符的编码超过此范围，则会只保留最低字节。加密可正常进行，但加密结果有误。
 * 每个数组元素包含 4 个字符，即 32 bit。
 * @param 字符串
 * @return 数组，长度为（字符串长度 / 4），每个元素为 32bit 的数字
 */
function str2bin(str) {
    var binary = new Array(str.length >> 2);
    for (var i = 0; i < str.length * 8; i += 8) {
        binary[i >> 5] |= (str.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
    }
    return binary;
}

/**
 * 对明文的二进制串进行填充
 * <pre>
 * |  满足 mod 512 = 448 |           固定 64 位         |
 * | 明文二进制 |填充部分|明文二进制串的长度的二进制表示|
 *  xxxxxxxxxxxx 10.....0 0...........................xx
 * </pre>
 * @param arr:数组，每个元素为 32bit 的数字
 * @param strLen：明文字符串长度
 * @return 数组，每个元素为 32bit 的数字，数组长度为 16 的倍数（包括 16）
 */
function alignSM3(arr, strLen) {
    //在明文二进制串后面拼接 1000 0000
    arr[strLen >> 2] |= 0x80 << (24 - strLen % 4 * 8);
    var groupNum = ((strLen + 8) >> 6) + 1;//以 512bit 为一组，总的组数
    var wordNum = groupNum * 16;//一个 word 32bit，总的 word 数

    for (var i = (strLen >> 2) + 1; i < wordNum; i++) {
        arr[i] = 0;
    }
    arr[wordNum - 1] = strLen * 8;//在末尾填上明文的二进制长度

    return groupNum;
}

/**
 * 压缩函数中的置换函数
 */
function p0(x) {
    return x ^ bitRol(x, 9) ^ bitRol(x, 17);
}

/**
 * 压缩函数中的置换函数
 */
function p1(x) {
    return x ^ bitRol(x, 15) ^ bitRol(x, 23);
}

/**
 * 循环左移
 */
function bitRol(input, n) {
    return (input << n) | (input >>> (32 - n));
}

/**
 * 压缩函数
 */
function compress(v, binArr, i) {
    //将消息分组扩展成 132 个字
    var w1 = new Array(68);
    var w2 = new Array(64);
    for (var j = 0; j < 68; j++) {
        if (j < 16) {
            w1[j] = binArr[i * 16 + j];
        } else {
            w1[j] = p1(w1[j-16] ^ w1[j-9] ^ bitRol(w1[j-3], 15)) ^ bitRol(w1[j-13], 7) ^ w1[j-6];
        }
    }
    for (var j = 0; j < 64; j++) {
        w2[j] = w1[j] ^ w1[j+4];
    }

    //压缩
    var a = v[0];
    var b = v[1];
    var c = v[2];
    var d = v[3];
    var e = v[4];
    var f = v[5];
    var g = v[6];
    var h = v[7];
    var ss1;
    var ss2;
    var tt1;
    var tt2;
    for (var j = 0; j < 64; j++) {
        ss1 = bitRol(addAll(bitRol(a, 12) , e , bitRol(t(j), j)), 7);
        ss2 = ss1 ^ bitRol(a, 12);
        tt1 = addAll(ff(a, b, c, j) , d , ss2 , w2[j]);
        tt2 = addAll(gg(e, f, g, j) , h , ss1 , w1[j]);
        d = c;
        c = bitRol(b, 9);
        b = a;
        a = tt1;
        h = g;
        g = bitRol(f, 19);
        f = e;
        e = p0(tt2);
    }
    v[0] ^= a;
    v[1] ^= b;
    v[2] ^= c;
    v[3] ^= d;
    v[4] ^= e;
    v[5] ^= f;
    v[6] ^= g;
    v[7] ^= h;
    return v;
}

/**
 * 常量 T 随 j 的不同而不同
 */
function t(j) {
    if (0 <= j && j < 16) {
        return 0x79CC4519;
    } else if (j < 64) {
        return 0x7A879D8A;
    }
}

/**
 * 布尔函数，随 j 的变化取不同的表达式
 */
function ff(x, y, z, j) {
    if (0 <= j && j < 16) {
        return x ^ y ^ z;
    } else if (j < 64) {
        return (x & y) | (x & z) | (y & z);
    }
}

/**
 * 布尔函数，随 j 的变化取不同的表达式
 */
function gg(x, y, z, j) {
    if (0 <= j && j < 16) {
        return x ^ y ^ z;
    } else if (j < 64) {
        return (x & y) | (~x & z);
    }
}

/**
 * 两数相加
 * 避免某些 js 引擎的 32 位加法的 bug
 */
function safe_add(x, y) {
    var lsw = ( x & 0xFFFF ) + (y & 0xFFFF);
    var msw = ( x >> 16 ) + (y >> 16) + (lsw >> 16);
    return (msw << 16) | ( lsw & 0xFFFF );
}

/**
 * 将所有参数相加
 */
function addAll() {
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
        sum = safe_add(sum, arguments[i]);
    }
    return sum;
}

/**
 * UTF-16 --> UTF-8
 */
function str2rstr_utf8(input) {
    var output = "" ;
    var i = -1 ;
    var x, y ;

    while(++ i < input.length) {
        //按 UTF-16 解码
        x = input.charCodeAt(i);
        y = i + 1 < input.length ? input .charCodeAt (i + 1) : 0 ;
        if( 0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF ) {
            x = 0x10000 + ((x & 0x03FF) << 10 ) + (y & 0x03FF);
            i++;
        }

        //按 UTF-8 编码
        if( x <= 0x7F ) {
            output += String.fromCharCode(x);
        }
        else if(x <= 0x7FF) {
            output += String.fromCharCode(
                0xC0 | ((x >>> 6 ) & 0x1F),
                0x80 | ( x         & 0x3F ));
        } else if(x <= 0xFFFF) {
            output += String.fromCharCode(
                0xE0 | ((x >>> 12) & 0x0F ),
                0x80 | ((x >>> 6 ) & 0x3F),
                0x80 | ( x         & 0x3F ));
        } else if(x <= 0x1FFFFF) {
            output += String.fromCharCode(
                0xF0 | ((x >>> 18) & 0x07 ),
                0x80 | ((x >>> 12) & 0x3F),
                0x80 | ((x >>> 6 ) & 0x3F),
                0x80 | ( x         & 0x3F ));
        }
    }
    return output;
}
export {
    sm3Digest
  }

