using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

namespace Kit.Helpers
{
    public static partial class SystemObjectExtensionMethods
    {
        public static string ToJSON(this object obj, bool enableCyrillic = false)
        {
            return obj.JsonSerialize(enableCyrillic);
        }


        public static TObject ToObjectFromJSON<TObject>(this string json)
        {
            return json.JsonDeserialize<TObject>();
        }



        public static void SetProperties(this object obj, NameValueCollection properties)
        {
            Type objectType = obj.GetType();
            foreach (string name in properties.Keys)
            {
                var property = objectType.GetProperty(name);
                object propertyValue = property.PropertyType.IsEnum
                                       ? Enum.Parse(property.PropertyType, properties[name])
                                       : System.Convert.ChangeType(properties[name], property.PropertyType);
                property.SetValue(obj, propertyValue, null);
            }
        }

        public static void SetProperties(this object obj, Dictionary<string, object> properties)
        {
            Type objectType = obj.GetType();
            foreach (string name in properties.Keys)
            {
                var property = objectType.GetProperty(name);
                object propertyValue = properties[name];
                property.SetValue(obj, propertyValue, null);
            }
        }

        public static object GetPropertyValue(this object thisObject, string propertyPath)
        {
            if (thisObject == null) return null;

            string[] propertyNames = propertyPath.Split('.');
            foreach (var propertyName in propertyNames)
            {
                var propertyInfo = thisObject.GetType().GetProperty(propertyName);
                if (propertyInfo == null) throw new ArgumentOutOfRangeException("PropertyName", propertyName, "Property not found.");
                thisObject = propertyInfo.GetValue(thisObject, null);
            }
            return thisObject;
        }

        public static T Normolize<T>(this T currentValue, T minValue, T defaultValue)
            where T : IComparable<T>
        {
            return currentValue.CompareTo(minValue) > 0 ? currentValue : defaultValue;
        }

        public static T CreateClone<T>(this T thisObject)
            where T : ICloneable
        {
            return (T)thisObject.Clone();
        }

        public static IEnumerable<T> AscendantsOrSelf<T>(this T obj, Func<T, T> GetParent) where T : class
        {
            if (obj == null) throw new ArgumentNullException("obj");
            if (GetParent == null) throw new ArgumentNullException("GetParent");

            yield return obj;
            T parent = GetParent(obj);
            while (parent != null)
            {
                yield return parent;
                parent = GetParent(parent);
            }
        }

        public static string ToStringOrEmpty(this object obj)
        {
            if (obj == null) return String.Empty;
            return obj.ToString();
        }

        /// <summary>
        /// Шифрует исходное сообщение AES ключом (добавляет соль)
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        public static byte[] ToAes256(this string src, byte[] aeskey)
        {
            //Объявляем объект класса AES
            Aes aes = Aes.Create();
            //Генерируем соль
            aes.GenerateIV();
            //Присваиваем ключ. aeskey - переменная (массив байт), сгенерированная методом GenerateKey() класса AES
            aes.Key = aeskey;
            byte[] encrypted;
            ICryptoTransform crypt = aes.CreateEncryptor(aes.Key, aes.IV);
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write))
                {
                    using (StreamWriter sw = new StreamWriter(cs))
                    {
                        sw.Write(src);
                    }
                }
                //Записываем в переменную encrypted зашиврованный поток байтов
                encrypted = ms.ToArray();
            }
            //Возвращаем поток байт + крепим соль
            return encrypted.Concat(aes.IV).ToArray();
        }

        public static string FromAes256(this byte[] shifr, byte[] aeskey)
        {
            byte[] bytesIv = new byte[16];
            byte[] mess = new byte[shifr.Length - 16];
            //Списываем соль
            for (int i = shifr.Length - 16, j = 0; i < shifr.Length; i++, j++)
                bytesIv[j] = shifr[i];
            //Списываем оставшуюся часть сообщения
            for (int i = 0; i < shifr.Length - 16; i++)
                mess[i] = shifr[i];
            //Объект класса Aes
            Aes aes = Aes.Create();
            //Задаем тот же ключ, что и для шифрования
            aes.Key = aeskey;
            //Задаем соль
            aes.IV = bytesIv;
            //Строковая переменная для результата
            string text = "";
            byte[] data = mess;
            ICryptoTransform crypt = aes.CreateDecryptor(aes.Key, aes.IV);
            using (MemoryStream ms = new MemoryStream(data))
            {
                using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read))
                {
                    using (StreamReader sr = new StreamReader(cs))
                    {
                        //Результат записываем в переменную text в вие исходной строки
                        text = sr.ReadToEnd();
                    }
                }
            }
            return text;

        }

        public static byte[] ReadAllBytes(this Stream instream)
        {
            if (instream is MemoryStream)
                return ((MemoryStream)instream).ToArray();

            using (var memoryStream = new MemoryStream())
            {
                instream.CopyTo(memoryStream);
                
                return memoryStream.ToArray();
            }
        }

        public static string ReadFileAes256(this Stream stream,byte[] aeskey)
        {
            return ReadAllBytes(stream).FromAes256(aeskey);
        }

        public static void WriteFileAes256(this Stream stream, string data,  byte[] aeskey)
        {
            byte[] dataBytes = data.ToAes256(aeskey);
            stream.Write(dataBytes, 0, dataBytes.Length);
        }
    }
}