using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

namespace EasyDevCore.Common.Security
{
    /// <summary>
    /// 
    /// </summary>
    public enum SymmetricAlgorithmMethod
    {
        /// <summary>
        /// The DES crypto service provider
        /// </summary>
        DESCryptoServiceProvider,
        /// <summary>
        /// The rijndael managed
        /// </summary>
        RijndaelManaged,
        /// <summary>
        /// The R c2 crypto service provider
        /// </summary>
        RC2CryptoServiceProvider,
        /// <summary>
        /// The triple DES crypto service provider
        /// </summary>
        TripleDESCryptoServiceProvider
    }

    /// <summary>
    /// 
    /// </summary>
    public enum AsymmetricAlgorithmMethod
    {
        /// <summary>
        /// The DSA
        /// </summary>
        DSA,
        /// <summary>
        /// The EC diffie hellman
        /// </summary>
        ECDiffieHellman,
        /// <summary>
        /// The EC DSA
        /// </summary>
        ECDsa,
        /// <summary>
        /// The RSA
        /// </summary>
        RSA
    }

    /// <summary>Security Helper</summary>
    public sealed class EncryptionHelper
    {
        #region Private Variables
        //Original key to ensure it complex
        private static string _KeyStringDump = "1AXDHKDASF932CXK";
        #endregion

        #region Public Methods
        /// <summary>
        /// Sets the dump key string - original key to ensure it enough complex.
        /// </summary>
        /// <param name="dumpKey">The dump key.</param>
        public static void SetDumpKeyString(string dumpKey)
        {
            _KeyStringDump = dumpKey;
        }

        private static SymmetricAlgorithm GetCryptorServiceProvider(SymmetricAlgorithmMethod algorithm)
        {
            switch (algorithm)
            {
                case SymmetricAlgorithmMethod.DESCryptoServiceProvider:
                    return System.Security.Cryptography.TripleDES.Create();
                case SymmetricAlgorithmMethod.RC2CryptoServiceProvider:
                    return System.Security.Cryptography.RC2.Create();
                case SymmetricAlgorithmMethod.RijndaelManaged:
                    return System.Security.Cryptography.Aes.Create();
                case SymmetricAlgorithmMethod.TripleDESCryptoServiceProvider:
                    return System.Security.Cryptography.TripleDES.Create();
                default:
                    throw new NotSupportedException(algorithm.ToString());
            }
        }

        /// <summary>
        /// RSAs the generate keys.
        /// </summary>
        /// <param name="xmlPublicKey">The XML public key.</param>
        /// <param name="xmlPrivateKey">The XML private key.</param>
        /// <param name="keySize">Size of the key.</param>
        public static void RSAGenerateKeys(ref string xmlPublicKey, ref string xmlPrivateKey, int keySize)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(keySize))
            {
                xmlPublicKey = rsa.ToXmlString(false);
                xmlPrivateKey = rsa.ToXmlString(true);
            }
        }

        /// <summary>
        /// RSAs the encrypt data.
        /// </summary>
        /// <param name="xmlPublicKey">The XML public key.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        public static byte[] RSAEncryptData(string xmlPublicKey, byte[] data)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPublicKey);
                return rsa.Encrypt(data, false);
            }
        }

        /// <summary>
        /// RSAs the encrypt string.
        /// </summary>
        /// <param name="xmlPublicKey">The XML public key.</param>
        /// <param name="dataString">The data string.</param>
        /// <returns></returns>
        public static string RSAEncryptString(string xmlPublicKey, string dataString)
        {
            string valueEncodeString = string.Empty;
            //convert data to byte array
            byte[] byteData = Encoding.UTF8.GetBytes(dataString);
            byte[] encryptedData = RSAEncryptData(xmlPublicKey, byteData);
            //convert to string and Base64 encode
            valueEncodeString = Convert.ToBase64String(encryptedData);
            return valueEncodeString;
        }


        /// <summary>
        /// RSAs the decrypt data.
        /// </summary>
        /// <param name="xmlPrivateKey">The XML private key.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        public static byte[] RSADecryptData(string xmlPrivateKey, byte[] data)
        {
            using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
            {
                rsa.FromXmlString(xmlPrivateKey);
                return rsa.Decrypt(data, false);
            }
        }

        /// <summary>
        /// RSAs the decrypt string.
        /// </summary>
        /// <param name="xmlPPrivateKey">The XML P private key.</param>
        /// <param name="encryptedDataString">The encrypted data string.</param>
        /// <returns></returns>
        public static string RSADecryptString(string xmlPPrivateKey, string encryptedDataString)
        {
            //convert data to byte array
            byte[] encryptedData = Convert.FromBase64String(encryptedDataString);
            byte[] byteData = RSADecryptData(xmlPPrivateKey, encryptedData);
            // convert to string
            System.Text.Encoding objEncoding = System.Text.Encoding.UTF8;
            return objEncoding.GetString(byteData);
        }

        /// <summary>
        /// Encrypts the data.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="algorithm">The algorithm.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        public static byte[] EncryptData(string keyString, SymmetricAlgorithmMethod algorithm, byte[] data)
        {
            string valueEncodeString = string.Empty;
            using (MemoryStream objMemoryStream = new MemoryStream())
            {
                if ((data.Length > 0) && keyString != string.Empty)
                {
                    //convert key to atleast KeyString.Length characters for complex
                    if (keyString.Length < _KeyStringDump.Length)
                    {
                        keyString += _KeyStringDump.Substring(0, _KeyStringDump.Length - keyString.Length);
                    }
                    //create encryption keys
                    byte[] byteKey = Encoding.UTF8.GetBytes(keyString.Substring(0, keyString.Length / 2));
                    byte[] byteVector = Encoding.UTF8.GetBytes(keyString.Substring(keyString.Length / 2));

                    //encrypt 
                    using (var objCryptor = GetCryptorServiceProvider(algorithm))
                    {
                        CryptoStream objCryptoStream = new CryptoStream(objMemoryStream, objCryptor.CreateEncryptor(byteKey, byteVector), CryptoStreamMode.Write);
                        objCryptoStream.Write(data, 0, data.Length);
                        objCryptoStream.FlushFinalBlock();
                    }
                }
                else
                {
                    objMemoryStream.Write(data, 0, data.Length);
                }
                return objMemoryStream.ToArray();
            }
        }

        /// <summary>
        /// Decrypts the data.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        public static byte[] DecryptData(string keyString, byte[] data)
        {
            return DecryptData(keyString, SymmetricAlgorithmMethod.DESCryptoServiceProvider, data);
        }

        /// <summary>
        /// Decrypts the data.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="algorithm">The algorithm.</param>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        public static byte[] DecryptData(string keyString, SymmetricAlgorithmMethod algorithm, byte[] data)
        {
            using (MemoryStream objMemoryStream = new MemoryStream())
            {
                if ((data.Length > 0) && keyString != string.Empty)
                {
                    //convert key to atleast KeyString.Length characters for complex
                    if (keyString.Length < _KeyStringDump.Length)
                    {
                        keyString += _KeyStringDump.Substring(0, _KeyStringDump.Length - keyString.Length);
                    }

                    //create encryption keys
                    byte[] byteKey = Encoding.UTF8.GetBytes(keyString.Substring(0, keyString.Length / 2));
                    byte[] byteVector = Encoding.UTF8.GetBytes(keyString.Substring(keyString.Length / 2));
                    
                    // decrypt
                    using (var objCryptor = GetCryptorServiceProvider(algorithm))
                    {
                        CryptoStream objCryptoStream = new CryptoStream(objMemoryStream, objCryptor.CreateDecryptor(byteKey, byteVector), CryptoStreamMode.Write);
                        objCryptoStream.Write(data, 0, data.Length);
                        objCryptoStream.FlushFinalBlock();
                    }
                }
                else
                {
                    objMemoryStream.Write(data, 0, data.Length);
                }
                return objMemoryStream.ToArray();
            }
        }

        /// <summary>
        /// Encrypts the string.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="dataString">The data string.</param>
        /// <returns></returns>
        public static string EncryptString(string keyString, string dataString)
        {
            string valueEncodeString = string.Empty;
            if (keyString != string.Empty && dataString != null)
            {
                //convert key to atleast KeyString.Length characters for complex
                if (keyString.Length < _KeyStringDump.Length)
                {
                    keyString += _KeyStringDump.Substring(0, _KeyStringDump.Length - keyString.Length);
                }

                //create encryption keys
                byte[] byteKey = Encoding.UTF8.GetBytes(keyString.Substring(0, keyString.Length / 2));
                byte[] byteVector = Encoding.UTF8.GetBytes(keyString.Substring(keyString.Length / 2));

                //convert data to byte array
                byte[] byteData = Encoding.UTF8.GetBytes(dataString);
                //encrypt 
                using(var objCryptor = GetCryptorServiceProvider(SymmetricAlgorithmMethod.DESCryptoServiceProvider))
                {
                    MemoryStream objMemoryStream = new MemoryStream();
                    CryptoStream objCryptoStream = new CryptoStream(objMemoryStream, objCryptor.CreateEncryptor(byteKey, byteVector), CryptoStreamMode.Write);
                    objCryptoStream.Write(byteData, 0, byteData.Length);
                    objCryptoStream.FlushFinalBlock();
                    //convert to string and Base64 encode
                    valueEncodeString = Convert.ToBase64String(objMemoryStream.ToArray());
                }
            }
            else
            {
                valueEncodeString = dataString;
            }
            return valueEncodeString;
        }

        /// <summary>
        /// Decrypts the string.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="dataString">The data string.</param>
        /// <returns></returns>
        public static string DecryptString(string keyString, string dataString)
        {
            if (string.IsNullOrWhiteSpace(dataString))
            {
                return dataString;
            }
            string valueEncodeString = string.Empty;
            if (keyString != string.Empty)
            {
                //convert key to atleast KeyString.Length characters for complex
                if (keyString.Length < _KeyStringDump.Length)
                {
                    keyString += _KeyStringDump.Substring(0, _KeyStringDump.Length - keyString.Length);
                }
                //create encryption keys
                byte[] byteKey = Encoding.UTF8.GetBytes(keyString.Substring(0, keyString.Length / 2));
                byte[] byteVector = Encoding.UTF8.GetBytes(keyString.Substring(keyString.Length / 2));
                //convert data to byte array and Base64 decode
                byte[] byteData = new byte[dataString.Length];
                try
                {
                    byteData = Convert.FromBase64String(dataString);
                }
                catch    // invalid length
                {
                    valueEncodeString = dataString;
                }

                if (valueEncodeString == string.Empty)
                {
                    try
                    {
                        // decrypt
                        using(var objCryptor = GetCryptorServiceProvider(SymmetricAlgorithmMethod.DESCryptoServiceProvider))
                        {
                            MemoryStream objMemoryStream = new MemoryStream();
                            CryptoStream objCryptoStream = new CryptoStream(objMemoryStream, objCryptor.CreateDecryptor(byteKey, byteVector), CryptoStreamMode.Write);
                            objCryptoStream.Write(byteData, 0, byteData.Length);
                            objCryptoStream.FlushFinalBlock();
                            // convert to string
                            System.Text.Encoding objEncoding = System.Text.Encoding.UTF8;
                            valueEncodeString = objEncoding.GetString(objMemoryStream.ToArray());
                        }
                    }
                    catch       // decryption error
                    {
                        return string.Empty;
                    }
                }
                return valueEncodeString;
            }
            else
            {
                return dataString;
            }
        }

        /// <summary>
        /// Complex Encrypts the string.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="dataString">The data string.</param>
        /// <returns></returns>
        public static string ComplexEncryptString(string keyString, string dataString)
        {
            if (string.IsNullOrEmpty(dataString))
            {
                return string.Empty;
            }
            if(dataString.IndexOf((char)0) > -1)
            {
                throw new NotSupportedException($"{nameof(dataString)} contains invalid characters !");
            }
            Random rand = new Random(DateTime.Now.Millisecond);
            int dump = rand.Next(1, 30 - (dataString.Length % 20));
            if (dump > 0)
            {
                string dumpString = string.Empty;
                for (int i = 0; i < dump; i++)
                {
                    dumpString += (char)(rand.Next(254) + 1);
                }
                if ((dumpString.Length + dataString.Length) % 2 == 0)
                {
                    dataString += (char)0 + dumpString;
                }
                else
                {
                    dataString = dumpString + (char)0 + dataString;
                }
            }
            return EncryptString(keyString, dataString);
        }

        /// <summary>
        /// Complex Decrypts the string.
        /// </summary>
        /// <param name="keyString">The key string.</param>
        /// <param name="dataString">The data string.</param>
        /// <returns></returns>
        public static string ComplexDecryptString(string keyString, string dataString)
        {
            if (string.IsNullOrEmpty(dataString))
            {
                return string.Empty;
            }
            string decryptString = DecryptString(keyString, dataString);
            if (dataString.Length > 0 && decryptString.Length == 0) decryptString = dataString;
            int pos = decryptString.IndexOf((char)0);
            if (pos > 0)
            {
                if ((decryptString.Length - 1) % 2 == 0)
                {
                    decryptString = decryptString.Substring(0, pos);
                }
                else
                {
                    decryptString = decryptString.Substring(pos + 1);
                }
            }
            return decryptString;
        }
        #endregion
    }
}