using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Diagnostics.CodeAnalysis;
using System.Data.SqlTypes;

namespace EasyDevCore.Common
{    
    /// <summary>
    /// Common Helper class
    /// </summary>
    public static class ConditionHelper
    {
        /// <summary>
        /// Tests the value in.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">The input.</param>
        /// <param name="args">The arguments.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        private static bool TestValueIn<T>(T input, IEnumerable args, IComparer comparer = null)
        {
            if (input == null) return false;
            foreach (var value in args)
            {
                if (value is IEnumerable list)
                {
                    if(TestValueIn(input, list, comparer))
                    {
                        return true;
                    }    
                }
                if (comparer is not null)
                {
                    return comparer.Compare(input, value) == 0;
                }
                else if (input.Equals(value))
                {
                    return true;
                }
            }
            return false;
        }
        

        /// <summary>
        /// Check value in list.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="args">The args.</param>
        /// <returns></returns>
        public static bool In<T>(this T input, params object[] args)
        {
            return TestValueIn(input, args);
        }

        /// <summary>
        /// Ins the specified comparer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input">The input.</param>
        /// <param name="comparer">The comparer.</param>
        /// <param name="args">The arguments.</param>
        /// <returns></returns>
        public static bool In<T>(this T input, IComparer comparer, params object[] args)
        {
            return TestValueIn(input, args);
        }

        /// <summary>
        /// Betweens the specified value from.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="valueFrom">The value from.</param>
        /// <param name="valueTo">The value to.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        public static bool Between<T>(this T value, T valueFrom, T valueTo, IComparer<T> comparer = null)
        {
            if (value == null || valueFrom == null || valueTo == null) return false;
            if(comparer is not null)
            {
                return comparer.Compare(value, valueFrom) >= 0 && comparer.Compare(value, valueTo) <= 0;
            }
            else if ((valueFrom is IComparable) && (valueTo is IComparable))
            {
                return Comparer<T>.Default.Compare(value, valueFrom) >= 0 && Comparer<T>.Default.Compare(value, valueTo) <= 0;
            }
            else
            {
                return value.CompareValue(valueFrom) >= 0 && value.CompareValue(valueTo) <= 0;
            }
        }


        /// <summary>
        /// Values if exception.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="func">The function.</param>
        /// <param name="valueIfException">The value if exception.</param>
        /// <returns></returns>
        public static T ValueIfException<T>(this Func<T> func, T valueIfException)
        {
            try
            {
                return func();
            }
            catch
            {
                return valueIfException;
            }
        }

        /// <summary>
        /// Return the index of answer value in the question values.
        /// (-1 means no correct)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="answer">The answer.</param>
        /// <param name="questValues">The quest values.</param>
        /// <returns></returns>
        public static int ChoiceIndex<T>(this T answer, params T[] questValues)
        {
            if (answer == null) return -1;
            for (int i = 0; i < questValues.Length; i++)
            {
                if (answer.Equals(questValues[i])) return i;
            }
            return -1;
        }

        /// <summary>
        /// Switches the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T1">The type of the 1.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="compareValues">The compare values.</param>
        /// <param name="returnValues">The return values.</param>
        /// <param name="failedValue">The failed value.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Compare values and the same length with return values</exception>
        public static T Switch<T, T1>(this T1 value, T1[] compareValues, T[] returnValues, T failedValue, IComparer<T1> comparer = null)
        {
            if (compareValues.Length != returnValues.Length)
            {
                throw new ArgumentOutOfRangeException("Compare values must be the same length with return values");
            }
            for (int i = 0; i < compareValues.Length; i++)
            {
                T1 compareValue = compareValues[i];
                if (value != null && compareValue != null)
                {
                    if (comparer is null)
                    {
                        if (value.Equals(compareValues[i]))
                            return returnValues[i];
                    }
                    else
                    {
                        if(comparer.Compare(value, compareValue) == 0)
                        {
                            return returnValues[i];
                        }    
                    }    
                }    
            }
            return failedValue;
        }

        /// <summary>
        /// Switches the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T1">The type of the 1.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="compareValues">The compare values.</param>
        /// <param name="returnValues">The return values.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns></returns>
        public static T Switch<T, T1>(this T1 value, T1[] compareValues, T[] returnValues, IComparer<T1> comparer = null)
        {
            return Switch(value, compareValues, returnValues, default, comparer);
        }

        /// <summary>
        /// Switches all the specified value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T1">The type of the 1.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="compareValues">The compare values.</param>
        /// <param name="returnValues">The return values.</param>
        /// <param name="comparer">The comparer.</param>
        /// <returns>
        /// All the value where value equal compare value
        /// </returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Compare values and the same length with return values</exception>
        public static T[] SwitchAll<T, T1>(this T1 value, T1[] compareValues, T[] returnValues, IComparer<T1> comparer = null)
        {
            if (compareValues.Length != returnValues.Length)
            {
                throw new ArgumentOutOfRangeException("Compare values must be the same length with return values");
            }
            List<T> list = new List<T>();
            if (value != null)
            {
                for (int i = 0; i < compareValues.Length; i++)
                {
                    T1 compareValue = compareValues[i];
                    if (value != null && compareValue != null)
                    {
                        if (comparer is null)
                        {
                            if (value.Equals(compareValues[i]))
                                list.Add(returnValues[i]);
                        }
                        else
                        {
                            if (comparer.Compare(value, compareValue) == 0)
                            {
                                list.Add(returnValues[i]);
                            }
                        }
                    }
                }
            }
            return list.ToArray();
        }

        /// <summary>
        /// Mupliple If statement (if not any conditions meet, it will return last returnValues)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="conditions">Multiple conditions.</param>
        /// <param name="returnValues">The return values.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Conditions must be equals return values length - 1</exception>
        public static T IIFs<T>(this bool[] conditions, T[] returnValues)
        {
            if (conditions.Length != returnValues.Length)
            {
                throw new ArgumentOutOfRangeException("Conditions must be equals return values length - 1");
            }
            for (int i = 0; i < conditions.Length; i++)
            {
                if (conditions[i])
                {
                    return returnValues[i];
                }
            }
            return returnValues[returnValues.Length - 1];
        }

        /// <summary>
        /// Iifs all condition true.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="conditions">The conditions.</param>
        /// <param name="returnValues">The return values.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Conditions must be equals return values length - 1</exception>
        public static T[] IIFAll<T>(this bool[] conditions, T[] returnValues)
        {
            if (conditions.Length != returnValues.Length)
            {
                throw new ArgumentOutOfRangeException("Conditions must be equals return values length - 1");
            }
            var list = new List<T>();
            for (int i = 0; i < conditions.Length; i++)
            {
                if (conditions[i])
                {
                    list.Add(returnValues[i]);
                }
            }
            return list.ToArray();
        }

        /// <summary>
        /// Compares any value type (0: equal, 1: lower, 2: greater, 3: not equal)
        /// </summary>
        /// <param name="value1">The value 1.</param>
        /// <param name="value2">The value 2.</param>
        /// <returns></returns>
        public static byte CompareValue(this object value1, object value2)
        {
            if (value1.Equals(value2)) return 0;
            TypeCode value1TypeCode = Type.GetTypeCode(value1.GetType());
            TypeCode value2TypeCode = Type.GetTypeCode(value2.GetType());
            if ((value1TypeCode == value2TypeCode) && ((value1TypeCode == TypeCode.Empty) || (value1TypeCode == TypeCode.Object)) && !value1.Equals(value2)) return 3;
            if (((int)value1TypeCode >= 3) && ((int)value1TypeCode <= 15) && ((int)value2TypeCode >= 3) && ((int)value2TypeCode <= 15))
            {
                return (Convert.ToDecimal(value1) > Convert.ToDecimal(value2) ? (byte)2 : (byte)1);
            }
            if ((value1TypeCode == TypeCode.String) && (value2TypeCode == TypeCode.String))
            {
                return (string.Compare((string)value1, (string)value2) > 0 ? (byte)2 : (byte)1);
            }
            if ((value1TypeCode == TypeCode.DateTime) && (value2TypeCode == TypeCode.DateTime))
            {
                return (DateTime)value1 > (DateTime)value2 ? (byte)2 : (byte)1;
            }
            return 3;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public static class ComparerExtensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        private class FuncComparer<T> : IComparer<T>
        {
            private readonly Comparison<T> comparison;
            /// <summary>
            /// Initializes a new instance of the <see cref="FuncComparer{T}"/> class.
            /// </summary>
            /// <param name="comparison">The comparison.</param>
            public FuncComparer(Comparison<T> comparison)
            {
                this.comparison = comparison;
            }
            /// <summary>
            /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
            /// </summary>
            /// <param name="x">The first object to compare.</param>
            /// <param name="y">The second object to compare.</param>
            /// <returns>
            /// A signed integer that indicates the relative values of <paramref name="x" /> and <paramref name="y" />, as shown in the following table.
            /// <list type="table"><listheader><term> Value</term><description> Meaning</description></listheader><item><term> Less than zero</term><description><paramref name="x" /> is less than <paramref name="y" />.</description></item><item><term> Zero</term><description><paramref name="x" /> equals <paramref name="y" />.</description></item><item><term> Greater than zero</term><description><paramref name="x" /> is greater than <paramref name="y" />.</description></item></list>
            /// </returns>
#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
            public int Compare(T x, T y)
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
            {
                return comparison(x, y);
            }
        }

        /// <summary>
        /// Functions the compare.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="comparison">The comparison.</param>
        /// <returns></returns>
        public static IComparer<T> FuncCompare<T>(Comparison<T> comparison)
        {
            return new FuncComparer<T>(comparison);
        }       
    }
}
