using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Aspose.Cells;
using Kreta.Core;

namespace Kreta.BusinessLogic.Classes
{
    public static class Extensions
    {
        private static readonly List<string> DateTimeStringFormatList = new List<string>
        {
            "yyyyMMdd",
            "yyyy.MM.dd",
            "yyyy.MM.dd.",
            "yyyy. MM. dd",
            "yyyy. MM. dd.",
            "yyyy-MM-dd",
            "yyyy/MM/dd",

            "yyyyMMdd H:mm:ss",
            "yyyy.MM.dd H:mm:ss",
            "yyyy.MM.dd. H:mm:ss",
            "yyyy. MM. dd H:mm:ss",
            "yyyy. MM. dd. H:mm:ss",
            "yyyy-MM-dd H:mm:ss",
            "yyyy/MM/dd H:mm:ss",

            "yyyyMMdd HH:mm:ss",
            "yyyy.MM.dd HH:mm:ss",
            "yyyy.MM.dd. HH:mm:ss",
            "yyyy. MM. dd HH:mm:ss",
            "yyyy. MM. dd. HH:mm:ss",
            "yyyy-MM-dd HH:mm:ss",
            "yyyy/MM/dd HH:mm:ss"
        };

        public static readonly List<string> TrueStringFormatList = new List<string>
        {
            "igen",
            "Igen"
        };

        public static readonly List<string> FalseStringFormatList = new List<string>
        {
            "nem",
            "Nem"
        };

        public static string Stringify(this XmlDocument doc)
        {
            var stringWriter = new StringWriter();
            var xmlTextWriter = new XmlTextWriter(stringWriter);
            doc.WriteTo(xmlTextWriter);
            return stringWriter.ToString();
        }

        public static string ToShortDateString(this DateTime? dateTime)
        {
            return dateTime?.ToShortDateString() ?? string.Empty;
        }

        public static string ToConvertableDateString(this DateTime? dateTime)
        {
            return dateTime != null ? dateTime.Value.ToConvertableDateString() : string.Empty;
        }

        public static string ToConvertableDateString(this DateTime dateTime)
        {
            return dateTime.ToString("yyyy-MM-dd");
        }

        public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek)
        {
            int diff = (7 + (dt.DayOfWeek - startOfWeek)) % 7;
            return dt.AddDays(-1 * diff).Date;
        }

        public static int Age(this DateTime birthDate, DateTime reference)
        {
            // Calculate the age.
            var age = reference.Year - birthDate.Year;
            // Go back to the year in which the person was born in case of a leap year
            if (reference < birthDate.AddYears(age))
            {
                age--;
            }
            return age;
        }

        public static string ToComparableString(this string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return "invalid_text";
            }

            string result = text
                .ReplaceMultipleSpacesAndTrim()?
                .ToLowerInvariant()
                .Replace("á", "a")
                .Replace("é", "e")
                .Replace("í", "i")
                .Replace("ó", "o")
                .Replace("ő", "ö")
                .Replace("ô", "ö")
                .Replace("õ", "ö")
                .Replace("ú", "u")
                .Replace("ű", "ü")
                .Replace("û", "ü")
                .Replace("ũ", "ü");
            return result;
        }

        public static string ToLetterComparableString(this string text)
        {
            char[] arr = text.ToComparableString().ToCharArray();

            arr = Array.FindAll(arr, c => char.IsLetter(c));

            var result = new string(arr);
            return result;
        }

        public static TEnum ToEnum<TEnum>(this string value, TEnum defaultValue)
        {
            if (!Enum.IsDefined(typeof(TEnum), value))
            {
                return defaultValue;
            }

            var result = (TEnum)Enum.Parse(typeof(TEnum), value);
            return result;
        }

        public static bool HasValueAndPositive(this int? value) => 0 < value;

        public static char ToChar(this bool value)
        {
            return value ? 'T' : 'F';
        }

        public static TResult Using<TClass, TResult>(this TClass helper, Func<TClass, TResult> func) where TClass : IDisposable
        {
            using (helper)
            {
                return func(helper);
            }
        }

        public static void Using<TClass>(this TClass helper, Action<TClass> action) where TClass : IDisposable
        {
            using (helper)
            {
                action(helper);
            }
        }

        public static DataSet DataSetSort(this DataSet dataSet, string columnName, bool isAsc = true)
        {
            DataSet result = new DataSet();
            dataSet.Tables[0].DefaultView.Sort = $"{columnName} {(isAsc ? "ASC" : "DESC")}";
            result.Tables.Add(dataSet.Tables[0].DefaultView.ToTable());
            return result;
        }

        public static bool IsEmpty(this DataSet dataSet)
        {
            if (dataSet != null
                && dataSet.Tables.Count > 0
                && dataSet.Tables[0].Rows.Count > 0)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Connect DictionaryItemIdColumns with NameColumns. Precondition: colName - colName_DNAME fields!
        /// </summary>
        /// <param name="origin">The origin.</param>
        /// <returns></returns>
        public static DataTable ConnectDictionaryColumns(this DataTable origin)
        {
            StringBuilder colNameString = new StringBuilder();
            foreach (DataColumn colItem in origin.Columns)
            {
                colNameString.Append(colItem.ColumnName + ";");
            }

            foreach (Match match in new Regex("(([^;]*)_DNAME);").Matches(colNameString.ToString()))
            {
                string colNameWDNAME = match.Groups[1].Value;
                string colOriginalName = match.Groups[2].Value;
                int colOriginalIndex = origin.Columns[colOriginalName].Ordinal;

                origin.Columns.Remove(colOriginalName);
                origin.Columns[colNameWDNAME].SetOrdinal(colOriginalIndex);
                origin.Columns[colNameWDNAME].ColumnName = colOriginalName;
            }

            return origin;
        }

        public static void RemoveEmptyRows(this DataTable dataTable)
        {
            for (int i = dataTable.Rows.Count - 1; i >= 0; i--)
            {
                if (dataTable.Rows[i][1] == DBNull.Value)
                {
                    dataTable.Rows[i].Delete();
                }
            }

            dataTable.AcceptChanges();
        }

        public static DateTime? GetDateTimeFromString(this string data)
        {
            if (DateTime.TryParseExact(data, DateTimeStringFormatList.ToArray(), CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
            {
                return result;
            }

            return null;
        }

        public static double? GetDoubleFromString(this string data)
        {
            var formattedDoubleString = data.Replace(" ", string.Empty).Replace(",", ".");
            if (double.TryParse(formattedDoubleString, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
            {
                return result;
            }

            return null;
        }

        public static bool? GetBooleanValueFromString(this string data)
        {
            if (TrueStringFormatList.Select(x => x.ToComparableString()).Contains(data.ToComparableString()))
            {
                return true;
            }

            if (FalseStringFormatList.Select(x => x.ToComparableString()).Contains(data.ToComparableString()))
            {
                return false;
            }

            return null;
        }

        public static double? WeightedAverage<T>(this IEnumerable<T> recordEnumeration, Func<T, double> value, Func<T, double> weight, int? round = null)
        {
            if (!recordEnumeration.Any())
            {
                return null;
            }

            var recordList = recordEnumeration.ToList();
            double weightedValueSum = recordList.Sum(x => value(x) * weight(x));
            double weightSum = recordList.Sum(weight);

            if (Math.Abs(weightSum) > 0)
            {
                double result;
                if (round.IsNotNullAndPositive())
                {
                    result = Math.Round(weightedValueSum / weightSum, round.Value);
                }
                else
                {
                    result = weightedValueSum / weightSum;
                }

                return result;
            }

            throw new DivideByZeroException();
        }

        public static TKey GetKeyByUniqueValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TValue value)
        {
            var result = dictionary.Single(x => x.Value.Equals(value));
            return result.Key;
        }

        public static void ChangeColumnDataType(this DataTable table, string columnname, Type newtype)
        {
            DataColumn column = table.Columns[columnname];
            if (column.DataType == newtype)
            {
                return;
            }

            try
            {
                var newcolumn = new DataColumn("temporary", newtype);
                table.Columns.Add(newcolumn);

                foreach (DataRow row in table.Rows)
                {
                    try
                    {
                        row["temporary"] = Convert.ChangeType(row[columnname], newtype);
                    }
                    catch
                    {
                    }
                }

                newcolumn.SetOrdinal(column.Ordinal);
                table.Columns.Remove(columnname);
                newcolumn.ColumnName = columnname;
            }
            catch (Exception e)
            {
                throw new Exception(e.Message, e);
            }
        }

        public static void SetTextWrap(this Cell errorCell, bool isTextWrapped = true)
        {
            Style style = errorCell.GetStyle();
            style.IsTextWrapped = isTextWrapped;
            errorCell.SetStyle(style);
        }

        public static Stream StripNonValidXmlCharacters(this Stream inputStream)
        {
            var inputStreamString = inputStream.StreamToString();
            StringBuilder outputStreamStringBuilder = new StringBuilder(); // Used to hold the output.

            if (string.IsNullOrWhiteSpace(inputStreamString))
            {
                return string.Empty.StringToStream(); // Vacancy test.
            }

            foreach (var current in inputStreamString)
            {
                if (current == 0x9 || // == '\t' == 9
                    current == 0xA || // == '\n' == 10
                    current == 0xD || // == '\r' == 13
                    current >= 0x20 && current <= 0xD7FF ||
                    current >= 0xE000 && current <= 0xFFFD ||
                    current >= 0x10000 && current <= 0x10FFFF)
                {
                    outputStreamStringBuilder.Append(current);
                }
            }

            var result = outputStreamStringBuilder.ToString().StringToStream();
            return result;
        }

        public static string StreamToString(this Stream stream)
        {
            stream.Position = 0;
            using (StreamReader reader = new StreamReader(stream, Encoding.GetEncoding("iso-8859-1")))
            {
                return reader.ReadToEnd();
            }
        }

        public static Stream StringToStream(this string streamString)
        {
            byte[] byteArray = Encoding.GetEncoding("iso-8859-1").GetBytes(streamString);
            return new MemoryStream(byteArray);
        }

        public static DataTable RemoveDuplicateRows(this DataTable dataTable, string columnName)
        {
            Hashtable hashTable = new Hashtable();
            ArrayList duplicateArrayList = new ArrayList();

            foreach (DataRow dataRow in dataTable.Rows)
            {
                if (hashTable.Contains(dataRow[columnName]))
                {
                    duplicateArrayList.Add(dataRow);
                }
                else
                {
                    hashTable.Add(dataRow[columnName], string.Empty);
                }
            }

            foreach (DataRow dataRow in duplicateArrayList)
            {
                dataTable.Rows.Remove(dataRow);
            }

            return dataTable;
        }
    }
}