using System.Text;

namespace XGame.Framework.Utils
{
    /// <summary>
    /// 安全性工具类
    /// </summary>
    public static class SecurityUtils
    {
        /// <summary>
        /// rc4 密钥字节数组长度
        /// </summary>
        private const short RC4_KEY_LENGTH = 256;
        /// <summary>
        /// 用于密钥索引计算
        /// 位操作( index & 255 )比余数快一点( index % 256 ) 
        /// </summary>
        private const byte RC4_KEY_LIMIT = 0xFF;//255
        /// <summary>
        /// 状态向量S
        /// </summary>
        private static readonly byte[] RC4_KEY_INDEXS = new byte[RC4_KEY_LENGTH];

        /// <summary>
        /// RC4算法的特点是算法简单，运行速度快，而且密钥长度是可变的，可变范围为1-256字节(8-2048比特)，
        /// RC4加密算法中的几个关键变量：
        /// 1、密钥流：RC4算法的关键是根据明文和密钥生成相应的密钥流，密钥流的长度和明文的长度是对应的，也就是说明文的长度是500字节，那么密钥流也是500字节。当然，加密生成的密文也是500字节，因为密文第i字节=明文第i字节^密钥流第i字节；
        /// 2、状态向量S：长度为256，S[0],S[1].....S[255]。每个单元都是一个字节，算法运行的任何时候，S都包括0-255的8比特数的排列组合，只不过值的位置发生了变换；
        /// 3、临时向量T：长度也为256，每个单元也是一个字节。如果密钥的长度是256字节，就直接把密钥的值赋给T，否则，轮转地将密钥的每个字节赋给T；
        /// 4、密钥K：长度为1-256字节，注意密钥的长度keylen与明文长度、密钥流的长度没有必然关系，通常密钥的长度趣味16字节（128比特）。
        /// RC4步骤：1、初始化S和T 2、初始排列S 3、产生密钥流
        /// 本质上是打乱密钥顺序的异或加密
        /// </summary>
        /// <param name="pwd">密钥</param>
        /// <param name="input">输入数组</param>
        /// <param name="offset">数据偏移值</param>
        /// <param name="length">加密的数据长度</param>
        /// <param name="output">输出，长度和input一致</param>
        public static void RC4(byte[] pwd, byte[] input, int offset, int length, byte[] output)
        {
            Assert.IsFalse(pwd == null || pwd.Length == 0, "RC4密钥不能为空.");
            Assert.IsFalse(input == null || input.Length == 0, "RC4输入数据内容不能为空.");
            Assert.IsFalse(output == null || output.Length == 0, "RC4输出数据不能为空.");
            Assert.IsTrue(input.Length == output.Length, $"RC4输入输出数据长度必须一致.Input:{input.Length} Output:{output.Length}");
            Assert.IsFalse(offset < 0 || length <= 0 || offset + length > input.Length, $"RC4数据偏移值错误.offset:{offset} length:{length} inputL:{input.Length}");
            int a, i, j, k;
            var pwdLength = pwd.Length;
            //打乱密码,密码,密码箱长度
            for (i = 0; i < RC4_KEY_LENGTH; i++)
            { // 先重置索引
                RC4_KEY_INDEXS[i] = (byte)i;
            }
            for (j = i = 0; i < RC4_KEY_LENGTH; i++)
            {
                j = (j + RC4_KEY_INDEXS[i] + pwd[i % pwdLength]) & RC4_KEY_LIMIT; // 位操作( &255 )比余数快一点( %256 ) 
                Swap(RC4_KEY_INDEXS, i, j);
            }
            for (j = i = 0; i < length; i++)
            {
                a = (i + 1) & RC4_KEY_LIMIT;
                j = (j + RC4_KEY_INDEXS[a]) & RC4_KEY_LIMIT;
                Swap(RC4_KEY_INDEXS, a, j);
                k = RC4_KEY_INDEXS[(RC4_KEY_INDEXS[a] + RC4_KEY_INDEXS[j]) & RC4_KEY_LIMIT];
                output[i + offset] = (byte)(input[i + offset] ^ k);
            }
        }
        private static void Swap(byte[] s, int i, int j)
        {
            var c = s[i];
            s[i] = s[j];
            s[j] = c;
        }
        /// <summary>
        /// 加密字节数组
        /// 加密直接替换原数组内容
        /// </summary>
        /// <param name="pwd">密钥</param>
        /// <param name="bytes">加密源&输出</param>
        public static void RC4(byte[] pwd, byte[] bytes)
        {
            RC4(pwd, bytes, 0, bytes.Length, bytes);
        }
        /// <summary>
        /// 十六进制字符串加密
        /// 返回十六进制字符串
        /// </summary>
        /// <param name="pwd">密钥，不强制使用十六进制</param>
        /// <param name="hexString">必须是十六进制的字符串</param>
        /// <returns></returns>
        public static string RC4(string pwd, string hexString)
        {
            byte[] key = Encoding.UTF8.GetBytes(pwd);
            byte[] bytes = hexString.ToHexBytes();
            RC4(key, bytes, 0, bytes.Length, bytes);
            return bytes.ToHexString();
        }
    }
}
