using System;
using System.Globalization;
using System.Reflection;
using FSI.Lib.CompareNetObjects.IgnoreOrderTypes;

namespace FSI.Lib.CompareNetObjects.TypeComparers
{
    /// <summary>
    /// Logic to compare an integer indexer (Note, inherits from BaseComparer, not TypeComparer)
    /// </summary>
    public class IndexerComparer : BaseComparer
    {
        private readonly RootComparer _rootComparer;

        /// <summary>
        /// Constructor that takes a root comparer
        /// </summary>
        /// <param name="rootComparer"></param>
        public IndexerComparer(RootComparer rootComparer)
        {
            _rootComparer = rootComparer;
        }

        /// <summary>
        /// Compare an integer indexer
        /// </summary>
        public void CompareIndexer(CompareParms parms, PropertyEntity info, PropertyEntity secondObjectInfo)
        {
            if (info == null)
                throw new ArgumentNullException("info");
#if !NETSTANDARD
            var type = info.ReflectedType;
            var type2 = secondObjectInfo.ReflectedType;
#else
            var type = info.DeclaringType;
            var type2 = secondObjectInfo.DeclaringType;
#endif
            if (type == null || type2 == null)
                throw new ArgumentNullException("info");

            int indexerCount1 = (int)type.GetProperty("Count").GetGetMethod().Invoke(parms.Object1, new object[] { });
            int indexerCount2 = (int)type2.GetProperty("Count").GetGetMethod().Invoke(parms.Object2, new object[] { });

            bool differentCounts = IndexersHaveDifferentLength(parms, info, indexerCount1, indexerCount2);

            if (parms.Result.ExceededDifferences)
                return;

            if (parms.Config.IgnoreCollectionOrder)
            {
                var enumerable1 = new IndexerCollectionLooper(parms.Object1, info.PropertyInfo, indexerCount1);
                var enumerable2 = new IndexerCollectionLooper(parms.Object2, secondObjectInfo.PropertyInfo, indexerCount2);

                CompareParms childParms = new CompareParms
                {
                    Result = parms.Result,
                    Config = parms.Config,
                    ParentObject1 = parms.Object1,
                    ParentObject2 = parms.Object2,
                    Object1 = enumerable1,
                    Object2 = enumerable2,
                    BreadCrumb = parms.BreadCrumb
                };

                IgnoreOrderLogic logic = new IgnoreOrderLogic(_rootComparer);
                logic.CompareEnumeratorIgnoreOrder(childParms, differentCounts);
            }
            else
            {
                string currentCrumb;

                // Run on indexer
                for (int i = 0; i < indexerCount1; i++)
                {
                    currentCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name, string.Empty, i);
                    object objectValue1 = info.PropertyInfo.GetValue(parms.Object1, new object[] { i });
                    object objectValue2 = null;

                    if (i < indexerCount2)
                        objectValue2 = secondObjectInfo.PropertyInfo.GetValue(parms.Object2, new object[] { i });

                    CompareParms childParms = new CompareParms
                    {
                        Result = parms.Result,
                        Config = parms.Config,
                        ParentObject1 = parms.Object1,
                        ParentObject2 = parms.Object2,
                        Object1 = objectValue1,
                        Object2 = objectValue2,
                        BreadCrumb = currentCrumb
                    };

                    _rootComparer.Compare(childParms);

                    if (parms.Result.ExceededDifferences)
                        return;
                }

                if (indexerCount1 < indexerCount2)
                {
                    for (int j = indexerCount1; j < indexerCount2; j++)
                    {
                        currentCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name, string.Empty, j);
                        object objectValue2 =  secondObjectInfo.PropertyInfo.GetValue(parms.Object2, new object[] { j });

                        CompareParms childParms = new CompareParms
                        {
                            Result = parms.Result,
                            Config = parms.Config,
                            ParentObject1 = parms.Object1,
                            ParentObject2 = parms.Object2,
                            Object1 = null,
                            Object2 = objectValue2,
                            BreadCrumb = currentCrumb
                        };

                        _rootComparer.Compare(childParms);

                        if (parms.Result.ExceededDifferences)
                            return;
                    }
                }
            }
        }

        private bool IndexersHaveDifferentLength(CompareParms parms, PropertyEntity info, int indexerCount1, int indexerCount2)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            if (indexerCount1 != indexerCount2)
            {
                string currentCrumb = AddBreadCrumb(parms.Config, parms.BreadCrumb, info.Name);
                Difference difference = new Difference
                {
                    ParentObject1 = parms.ParentObject1,
                    ParentObject2 = parms.ParentObject2,
                    PropertyName = currentCrumb,
                    Object1Value = indexerCount1.ToString(CultureInfo.InvariantCulture),
                    Object2Value = indexerCount2.ToString(CultureInfo.InvariantCulture),
                    ChildPropertyName = "Count",
                    Object1 = parms.Object1,
                    Object2 = parms.Object2
                };

                AddDifference(parms.Result, difference);
                
                return true;
            }
            return false;
        }
    }
}
