// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Cci.Mappings;

namespace Microsoft.Cci.Differs
{
    public static class ListMerger
    {
        public static IEnumerable<ElementMapping<T>> MergeLists<T>(IEnumerable<T> list0, IEnumerable<T> list1) where T : class
        {
            T[] array0 = list0 == null ? new T[0] : list0.ToArray();
            T[] array1 = list1 == null ? new T[0] : list1.ToArray();

            return ListMerger.Merge<T>(array0, array1).Select(t => new SimpleElementMapping<T>(t.Item1, t.Item2));
        }

        public static List<Tuple<DifferenceType, T>> Merge<T>(T[] list0, T[] list1) where T : class
        {
            return Merge<T>(list0, 0, list0.Length, list1, 0, list1.Length);
        }

        public static List<Tuple<DifferenceType, T>> Merge<T>(T[] list0, int list0Start, int list0End, T[] list1, int list1Start, int list1End) where T : class
        {
            List<Tuple<DifferenceType, T>> list = new List<Tuple<DifferenceType, T>>();

            int list1Index = list1Start;
            for (int list0Index = list0Start; list0Index < list0End;)
            {
                // No more in list1 so consume list0 items
                if (list1Index >= list1End)
                {
                    list.Add(Tuple.Create(DifferenceType.Removed, list0[list0Index]));
                    list0Index++;
                    continue;
                }

                // Items are equal consume.
                if (list0[list0Index].Equals(list1[list1Index]))
                {
                    list.Add(Tuple.Create(DifferenceType.Unchanged, list0[list0Index]));
                    list0Index++;
                    list1Index++;
                    continue;
                }

                // Find the next matching item in the list0 and consume to that point
                int findIndex0 = Array.FindIndex(list0, list0Index, list0End - list0Index, t => list1[list1Index].Equals(t));
                if (findIndex0 >= list0Index)
                {
                    while (findIndex0 > list0Index)
                    {
                        list.Add(Tuple.Create(DifferenceType.Removed, list0[list0Index]));
                        list0Index++;
                    }
                    continue;
                }

                // Find the next matching item in list1 and consume to that point
                int findIndex1 = Array.FindIndex(list1, list1Index, list1End - list1Index, t => list0[list0Index].Equals(t));
                if (findIndex1 >= list1Index)
                {
                    while (findIndex1 > list1Index)
                    {
                        list.Add(Tuple.Create(DifferenceType.Added, list1[list1Index]));
                        list1Index++;
                    }
                    continue;
                }

                // Either item is found in the other list so just consume both single items
                Contract.Assert(findIndex0 == -1 && findIndex1 == -1);
                Contract.Assert(!list0[list0Index].Equals(list1[list1Index]));
                list.Add(Tuple.Create(DifferenceType.Removed, list0[list0Index]));
                list0Index++;

                list.Add(Tuple.Create(DifferenceType.Added, list1[list1Index]));
                list1Index++;
            }

            // Consume any remaining in list1
            while (list1Index < list1End)
            {
                list.Add(Tuple.Create(DifferenceType.Added, list1[list1Index]));
                list1Index++;
            }

            return list;
        }
    }
}
