using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Ato.CD.Inbound.Shared
{
    /// <summary>
    /// Class to contain all the ESR generated validator helper functions instead of imbedding them within each and every form validator.
    /// </summary>
    public static class ValidatorHelpers
    {

        public static IEnumerable<string> Union(IEnumerable<string> list1, IEnumerable<string> list2)
        {
            IEnumerable<string> response;

            if (list1 == null && list2 == null)
            {
                response = null;
            }
            else if (list1 == null)
            {
                response = list2.Distinct();
            }
            else if (list2 == null)
            {
                response = list1.Distinct();
            }
            else
            {
                response = list1.Union(list2);
            }

            return response;
        }

        public static bool IsMatch(int? field, string expression, RegexOptions options = RegexOptions.None)
        {
            if (field == null)
                return false;
            else
                return Regex.IsMatch(Convert.ToString(field.Value), expression, options);
        }

        public static bool IsMatch(string field, string expression, RegexOptions options = RegexOptions.None)
        {
            if (field == null)
            {
                return false;
            }
            else
            {
                field = field.Trim();
                return Regex.IsMatch(field, expression, options);
            }
        }

        // This is just context and tuple counts where they are integer values - easier that changing the parsing logic to just return the value
        public static int Count(int count)
        {
            return count;
        }


        public static int Count<T>(IEnumerable<T> values)
        {
            return values == null ? 0 : values.Where(f => f != null).Count();
        }


        public static int Count<T>(ICollection<T> values)
        {
            return values == null ? 0 : values.Where(f => f != null).Count();
        }

        public static bool exists(bool value)
        {
            return value;
        }

        public static string GetValueOrEmpty(bool? val)
        {
            return (val.HasValue) ? val.ToString().ToLower() : string.Empty;
        }

        public static string GetValueOrEmptyDate(DateTime? val)
        {
            return (val.HasValue) ? val.Value.ToString("yyyy-MM-dd") : string.Empty;
        }

        public static string GetValueOrEmptyDate(string val)
        {
            DateTime dateTimeOut;
            if (DateTime.TryParse(val, out dateTimeOut))
            {
                return dateTimeOut.ToString("yyyy-MM-dd");
            }
            return !string.IsNullOrWhiteSpace(val) ? val : string.Empty;
        }

        public static string GetValueOrEmptyDateTime(string val)
        {
            DateTime dateTimeOut;
            if (DateTime.TryParse(val, out dateTimeOut))
            {
                return dateTimeOut.ToString("yyyy-MM-ddTHH:mm:ssZ");
            }
            return !string.IsNullOrWhiteSpace(val) ? val : string.Empty;
        }

        public static string GetValueOrEmptyDateTime(DateTime? val)
        {
            return (val.HasValue) ? val.Value.ToString("yyyy-MM-ddTHH:mm:ssZ") : string.Empty;
        }

        public static string GetValueOrEmpty(string val)
        {
            return !string.IsNullOrWhiteSpace(val) ? val : string.Empty;
        }

        public static string GetValueOrEmpty(decimal? val)
        {
            return (val.HasValue) ? val.ToString() : string.Empty;
        }

        public static string GetValueOrEmpty(int? val)
        {
            return (val.HasValue) ? val.ToString() : string.Empty;
        }

        /// <summary>
        /// Get string value between [first] a and [last] b.
        /// </summary>
        public static string Between(string value, string a, string b)
        {
            int posA = value.IndexOf(a);
            int posB = value.LastIndexOf(b);
            if (posA == -1)
            {
                return "";
            }
            if (posB == -1)
            {
                return "";
            }
            int adjustedPosA = posA + a.Length;
            if (adjustedPosA >= posB)
            {
                return "";
            }
            return value.Substring(adjustedPosA, posB - adjustedPosA);
        }

        /// <summary>
        /// Get string value after [first] a.
        /// </summary>
        public static string Before(string value, string a)
        {
            int posA = value.IndexOf(a);
            if (posA == -1)
            {
                return "";
            }
            return value.Substring(0, posA);
        }

        /// <summary>
        /// Get string value after [last] a.
        /// </summary>
        public static string After(string value, string a)
        {
            int posA = value.LastIndexOf(a);
            if (posA == -1)
            {
                return "";
            }
            int adjustedPosA = posA + a.Length;
            if (adjustedPosA >= value.Length)
            {
                return "";
            }
            return value.Substring(adjustedPosA);
        }

        public static int Length(object field)
        {
            if (field == null)
                return 0;
            else
                return field.ToString().Trim().Length;
        }

        public static bool NotSameValues(IEnumerable<object> nodes)
        {
            if (nodes == null)
                return false;

            object[] nodesArray = nodes.Cast<object>().ToArray();
            return NotSameValues(nodesArray);
        }

        public static bool NotSameValues(params object[] nodes)
        {
            if (nodes == null)
                return false;

            return (from x in nodes select x).Distinct().Count() != 1;
        }

        public static bool HasDuplicateValues(IEnumerable<DateTime> nodes)
        {
            if (nodes == null)
                return false;

            object[] nodesArray = nodes.Cast<object>().ToArray();
            return HasDuplicateValues(nodesArray);
        }

        public static bool HasDuplicateValues(IEnumerable<DateTime?> nodes)
        {
            if (nodes == null)
                return false;

            object[] nodesArray = nodes.Cast<object>().ToArray();
            return HasDuplicateValues(nodesArray);
        }

        public static bool NotSameValues(IEnumerable<DateTime> nodes)
        {
            if (nodes == null)
                return false;

            object[] nodesArray = nodes.Cast<object>().ToArray();
            return NotSameValues(nodesArray);
        }

        public static bool NotSameValues(IEnumerable<DateTime?> nodes)
        {
            if (nodes == null)
                return false;

            object[] nodesArray = nodes.Cast<object>().ToArray();
            return NotSameValues(nodesArray);
        }

        public static bool HasDuplicateValues(IEnumerable<object> nodes)
        {
            if (nodes == null)
                return false;

            object[] nodesArray = nodes.Cast<object>().ToArray();
            return HasDuplicateValues(nodesArray);
        }

        public static bool HasDuplicateValues(params object[] nodes)
        {
            if (nodes == null)
                return false;

            nodes = nodes.Where(x => x != null).ToArray();
            return !((from x in nodes select x).Distinct().Count() == nodes.Count());

        }

        public static int DuplicateValueIndex(IEnumerable<object> values)
        {
            int response = 0;
            var hashset = new HashSet<object>();
            foreach (var value in values)
            {
                if (!hashset.Add(value))
                {
                    return response;
                }
                response++;
            }
            return response;
        }

        public static int DuplicateValueIndex<T>(IEnumerable<T?> values) where T : struct
        {
            int response = 0;
            var hashset = new HashSet<T?>();
            foreach (var value in values)
            {
                if (!hashset.Add(value))
                {
                    return response;
                }
                response++;
            }
            return response;
        }

        public static bool IsDate(object value)
        {
            DateTime dateValue;
            return (value != null && DateTime.TryParse(value.ToString(), out dateValue));
        }

        public static DateTime AsDate(string dateAsString)
        {
            DateTime response = DateTime.MinValue;
            DateTime date;

            if (DateTime.TryParse(dateAsString, out date))
            {
                response = date;
            }

            return response;
        }

        public static DateTime Date(DateTime? datetime)
        {
            return datetime.GetValueOrDefault().Date;
        }

        //The logic in After function expects "---" for day and "--" for month. 
        //Since hyphen was missing the date always returned null
        public static DateTime? ConvertToDate(int day, int month, int year)
        {
            return ConvertToDate(day == 0 ? null : "---" + day.ToString(), month == 0 ? null : "--" + month.ToString(), year == 0 ? null : year.ToString());
        }

        public static DateTime? ConvertToDate(string day, string month, string year)
        {
            DateTime? response;
            DateTime result;

            if (year == null || month == null || day == null)
            {
                return null;
            }
            string dateAsString = year + "-" + After(month, "--") + "-" + After(day, "---");
            if (DateTime.TryParse(dateAsString, out result))
            {
                response = result;
            }
            else
            {
                response = null;
            }
            return response;
        }


        public static DateTime? ConvertToDate(string day, string month, int year)
        {
            DateTime? response;
            DateTime result;

            if (year == 0 || month == null || day == null)
            {
                return null;
            }
            string dateAsString = year.ToString() + "-" + After(month, "--") + "-" + After(day, "---");
            if (DateTime.TryParse(dateAsString, out result))
            {
                response = result;
            }
            else
            {
                response = null;
            }
            return response;
        }


        public static int Day(string dateAsString)
        {
            int response = 0;
            DateTime date;

            if (DateTime.TryParse(dateAsString, out date))
            {
                response = date.Day;
            }

            return response;
        }


        public static int Day(DateTime? date)
        {
            if (date == null)
                return 0;
            else
                return date.Value.Day;

        }


        public static string Month(string dateAsString)
        {
            string response = null;
            DateTime date;

            if (DateTime.TryParse(dateAsString, out date))
            {
                response = date.ToString("MMMM");
            }
            else
            {
                return "NotAMonth";
            }

            return response;
        }

        public static string Month(DateTime? date)
        {
            if (date == null)
                return "NotAMonth";
            else
                return date.Value.ToString("MMMM");
        }


        public static int MonthAsInt(string dateAsString)
        {
            int response = 0;
            DateTime date;

            if (DateTime.TryParse(dateAsString, out date))
            {
                response = date.Month;
            }

            return response;
        }

        public static int MonthAsInt(DateTime? date)
        {
            if (date == null)
            {
                return 0;
            }
            return date.Value.Month;
        }


        public static int Year(string dateAsString)
        {
            int response = 0;
            DateTime date;

            if (DateTime.TryParse(dateAsString, out date))
            {
                response = date.Year;
            }

            return response;
        }

        public static int Year(DateTime? date)
        {
            if (date == null)
                return 0;
            else
                return date.Value.Year;
        }


        public static int CurrentFinancialYear()
        {
            return DateToFinancialYear(DateTime.Now, 7);
        }

        public static int FinancialYear(string dateAsString)
        {
            return DateToFinancialYear(dateAsString, 7);
        }

        public static int FinancialYear(DateTime? date)
        {
            return DateToFinancialYear(date, 7);
        }

        public static int DateToFinancialYear(string dateAsString, int startingMonth)
        {
            int response = 0;
            DateTime date;
            if (DateTime.TryParse(dateAsString, out date))
            {
                response = DateToFinancialYear(date, startingMonth);
            }

            return response;
        }
        public static int DateToFinancialYear(DateTime? date, int startingMonth)
        {
            int response;
            if (date == null)
            {
                response = 0;
            }
            else
            {
                int year = date.Value.Year;
                int month = date.Value.Month;

                if (startingMonth > month)
                    response = year;
                else
                    response = year + 1;
            }
            return response;
        }


        public static int FBTYear(string dateAsString)
        {
            int response = 0;
            DateTime date;

            if (DateTime.TryParse(dateAsString, out date))
                response = FBTYear(date);
            return response;
        }

        public static int FBTYear(DateTime? date)
        {
            if (date == null)
            {
                return 0;
            }
            else
            {
                if (date.Value.Month > 3)
                    return date.Value.Year + 1;
                else
                    return date.Value.Year;
            }
        }

        public static DateTime? AddMonthsToDate(DateTime? dateTime, int months)
        {
            return dateTime == null ? null : (DateTime?)dateTime.Value.AddMonths(months);
        }

        public static bool IsNumeric(object value)
        {
            decimal numbervalue;
            return (value != null && decimal.TryParse(value.ToString(), out numbervalue));
        }

        public static bool NotMonetary(decimal? field, string sign, int digits, int decimals)
        {
            if (field == null)
            {
                return false;
            }
            else
            {
                string signExpression;
                string decimalExpression;
                int digitsToUse = digits - decimals;

                if (sign == "U")
                    signExpression = "^";
                else
                    signExpression = "^-?";

                if (decimals > 0)
                    decimalExpression = @"(\.\d{1," + decimals + "})?$";
                else
                    decimalExpression = @"$";

                return !(Regex.IsMatch(field.Value.ToString("0.#########################"), signExpression + @"\d{1," + digitsToUse + "}" + decimalExpression));
            }
        }

        public static bool NotNumeric(decimal? field, string sign, int digits, int decimals)
        {
            if (field == null)
            {
                return false;
            }
            else
            {
                string signExpression;
                string decimalExpression;
                int digitsToUse = digits - decimals;

                if (sign == "U")
                    signExpression = "^";
                else
                    signExpression = "^-?";

                if (decimals > 0)
                    decimalExpression = @"(\.\d{1," + decimals + "})?$";
                else
                    decimalExpression = @"$";

                return !(Regex.IsMatch(field.Value.ToString("0.#########################"), signExpression + @"\d{1," + digitsToUse + "}" + decimalExpression));
            }
        }

        public static bool NotNumeric(int? field, string sign, int digits, int decimals = 0)
        {
            if (field == null)
            {
                return false;
            }
            else
            {
                string signExpression;

                if (sign == "U")
                    signExpression = "^";
                else
                    signExpression = "^-?";

                return !(Regex.IsMatch(field.Value.ToString(), signExpression + @"\d{1," + digits + "}$"));
            }
        }

        public static bool NotNumeric(long? field, string sign, int digits, int decimals = 0)
        {
            if (field == null)
            {
                return false;
            }
            else
            {
                string signExpression;

                if (sign == "U")
                    signExpression = "^";
                else
                    signExpression = "^-?";

                return !(Regex.IsMatch(field.Value.ToString(), signExpression + @"\d{1," + digits + "}$"));
            }
        }


        public static bool OutsideRange(decimal field, decimal expression, int range)
        {
            bool response;

            response = (field < (expression - range)) || (field > (expression + range));

            return response;
        }


        public static bool FailsUSIAlgorithm(string usi, string abn)
        {
            bool response = true;
            if (usi == null || abn == null)
            {
                response = false;
            }
            else
            {
                usi = usi.Trim();
                abn = abn.Trim();
                if (usi.Length == 14)
                {
                    int numeric;
                    if (usi.Substring(0, 11) == abn && int.TryParse(usi.Substring(11, 3), out numeric))
                        response = false;

                }
                else if (usi.Length == 9)
                {

                    if (Regex.IsMatch(usi, @"^([a-zA-Z]{3}\d{4}[a-zA-Z]{2})$"))
                        response = false;
                }
            }
            return response;
        }

        public static bool FailsTANAlgorithm(string tan)
        {
            bool response;
            decimal decimalTan;

            if (tan == null)
                return false;

            tan = tan.Trim();

            if (!decimal.TryParse(tan, out decimalTan))
                return true;

            if (tan.Length != 8)
                return true;

            decimal tanSum =
                7 * int.Parse(tan.Substring(0, 1)) +
                9 * int.Parse(tan.Substring(1, 1)) +
                8 * int.Parse(tan.Substring(2, 1)) +
                4 * int.Parse(tan.Substring(3, 1)) +
                6 * int.Parse(tan.Substring(4, 1)) +
                3 * int.Parse(tan.Substring(5, 1)) +
                5 * int.Parse(tan.Substring(6, 1)) +
                1 * int.Parse(tan.Substring(7, 1));

            if ((tanSum % 11) == 0)
                response = false;
            else
                response = true;

            return response;
        }


        public static bool FailsABNAlgorithm(string abn)
        {
            bool response;
            decimal decimalAbn;

            if (abn == null)
                return false;

            abn = abn.Trim();

            if (!decimal.TryParse(abn, out decimalAbn))
                return true;

            if (abn.Length != 11)
                return true;

            decimal abnSum =
                10 * (int.Parse(abn.Substring(0, 1)) - 1) +
                1 * int.Parse(abn.Substring(1, 1)) +
                3 * int.Parse(abn.Substring(2, 1)) +
                5 * int.Parse(abn.Substring(3, 1)) +
                7 * int.Parse(abn.Substring(4, 1)) +
                9 * int.Parse(abn.Substring(5, 1)) +
                11 * int.Parse(abn.Substring(6, 1)) +
                13 * int.Parse(abn.Substring(7, 1)) +
                15 * int.Parse(abn.Substring(8, 1)) +
                17 * int.Parse(abn.Substring(9, 1)) +
                19 * int.Parse(abn.Substring(10, 1));

            if ((abnSum % 89) == 0)
                response = false;
            else
                response = true;

            return response;
        }
        public static bool FailsACNAlgorithm(string acn)
        {
            bool response;
            decimal decimalAbn;
            if (acn == null)
                return false;

            acn = acn.Trim();

            if (!decimal.TryParse(acn, out decimalAbn))
                return true;

            if (acn.Length != 9)
                return true;

            decimal abnSum =
                8 * int.Parse(acn.Substring(0, 1)) +
                7 * int.Parse(acn.Substring(1, 1)) +
                6 * int.Parse(acn.Substring(2, 1)) +
                5 * int.Parse(acn.Substring(3, 1)) +
                4 * int.Parse(acn.Substring(4, 1)) +
                3 * int.Parse(acn.Substring(5, 1)) +
                2 * int.Parse(acn.Substring(6, 1)) +
                1 * int.Parse(acn.Substring(7, 1));

            decimal checkDigit = int.Parse(acn.Substring(8, 1));
            decimal acnRemainder = abnSum % 10;

            if (((10 - acnRemainder) % 10) == checkDigit)
                response = false;
            else
                response = true;

            return response;
        }


        public static bool FailsTFNAlgorithm(string tfn)
        {
            bool response;
            decimal decimalTfn;

            if (tfn == null)
                return false;

            tfn = tfn.Trim();
            tfn = Regex.Replace(tfn, "^0+", "");

            if (!decimal.TryParse(tfn, out decimalTfn))
                return true;

            if (tfn.Length < 8)
                return true;


            decimal tfn1To7Sum =
                1 * int.Parse(tfn.Substring(0, 1)) +
                4 * int.Parse(tfn.Substring(1, 1)) +
                3 * int.Parse(tfn.Substring(2, 1)) +
                7 * int.Parse(tfn.Substring(3, 1)) +
                5 * int.Parse(tfn.Substring(4, 1)) +
                8 * int.Parse(tfn.Substring(5, 1)) +
                6 * int.Parse(tfn.Substring(6, 1));

            decimal tfn8 = 9 * int.Parse(tfn.Substring(7, 1));

            if (tfn.Length == 8)
            {
                decimal tFNLg8WSum9 = 10 * int.Parse(tfn.Substring(7, 1));
                decimal tFNLg8WSum = tfn1To7Sum + tFNLg8WSum9;

                if ((tFNLg8WSum % 11) == 0)
                    response = false;
                else
                    response = true;
            }
            else if (tfn.Length == 9)
            {
                decimal tfn9 = 10 * int.Parse(tfn.Substring(8, 1));
                decimal tFNLg9WSum = tfn1To7Sum + tfn8 + tfn9;

                if ((tFNLg9WSum % 11) == 0)
                    response = false;
                else
                    response = true;
            }
            else
            {
                response = true;
            }

            return response;
        }


        public static decimal ConditionalValue(bool expression, decimal? trueVal, decimal? falseVal)
        {
            return expression ? trueVal.GetValueOrDefault() : falseVal.GetValueOrDefault();
        }

        public static decimal AsNumeric(string value)
        {
            decimal numberValue;
            decimal.TryParse(value, out numberValue);
            return numberValue;
        }

        public static bool RegexMatch(int? field, string expression, string flags = "")
        {
            return IsMatch(field, expression, GetRegexOption(flags));
        }

        public static bool RegexMatch(string field, string expression, string flags = "")
        {
            return IsMatch(field, expression, GetRegexOption(flags));
        }

        public static RegexOptions GetRegexOption(string flags)
        {
            RegexOptions options = RegexOptions.None;

            char[] characters = flags.ToCharArray();

            foreach (char character in characters)
            {
                switch (character)
                {
                    case 'i':
                        options = options | RegexOptions.IgnoreCase;
                        break;
                    case 'm':
                        options = options | RegexOptions.Multiline;
                        break;
                    case 's':
                        options = options | RegexOptions.Singleline;
                        break;
                    case 'n':
                        options = options | RegexOptions.ExplicitCapture;
                        break;
                    case 'x':
                        options = options | RegexOptions.IgnorePatternWhitespace;
                        break;
                }
            }

            return options;
        }

        public static string Substring(object field, int start, int length)
        {
            return field.ToString().Substring(start, length);
        }

        /// <summary>
        /// Returns an occurrence index as [occurrenceIndex] of occurrenceIndex > 0, otherwise the empty string
        /// </summary>
        /// <param name="occurrenceIndex">Index of the occurrence.</param>
        /// <returns>Occurrence in XPath [#] format</returns>
        public static string OccurrenceIndex(int occurrenceIndex)
        {
            return occurrenceIndex > 0 ? "[" + occurrenceIndex + "]" : "";
        }

    }
}
