using System.Collections.Generic;

using PlasticGui;
using PlasticGui.WorkspaceWindow.Merge;

namespace Unity.PlasticSCM.Editor.Views.Merge.Developer
{
    internal class UnityMergeTree
    {
        internal static UnityMergeTree BuildMergeCategories(
            MergeChangesTree tree)
        {
            return new UnityMergeTree(tree);
        }

        UnityMergeTree(
            MergeChangesTree tree)
        {
            mInnerTree = tree;

            mMetaCache.Build(mInnerTree.GetNodes());
        }

        internal List<MergeChangesCategory> GetNodes()
        {
            return mInnerTree.GetNodes();
        }

        internal bool HasMeta(MergeChangeInfo changeInfo)
        {
            return mMetaCache.ContainsMeta(changeInfo);
        }

        internal MergeChangeInfo GetMetaChange(MergeChangeInfo change)
        {
            return mMetaCache.GetExistingMeta(change);
        }

        internal void FillWithMeta(List<MergeChangeInfo> changes)
        {
            changes.AddRange(
                mMetaCache.GetExistingMeta(changes));
        }

        internal void Filter(Filter filter, List<string> columnNames)
        {
            mInnerTree.Filter(filter, columnNames);
        }

        internal void Sort(string key, bool isAscending)
        {
            mInnerTree.Sort(key, isAscending);
        }

        internal void ResolveUserNames(
            MergeChangesTree.ResolveUserName resolveUserName)
        {
            mInnerTree.ResolveUserNames(resolveUserName);
        }

        MetaCache mMetaCache = new MetaCache();
        MergeChangesTree mInnerTree;

        class MetaCache
        {
            internal bool ContainsMeta(MergeChangeInfo changeInfo)
            {
                string key = BuildKey.ForMetaChange(changeInfo);

                return mCache.ContainsKey(key);
            }

            internal MergeChangeInfo GetExistingMeta(MergeChangeInfo change)
            {
                MergeChangeInfo result;

                if (!mCache.TryGetValue(BuildKey.ForMetaChange(change), out result))
                    return null;

                return result;
            }

            internal List<MergeChangeInfo> GetExistingMeta(
                List<MergeChangeInfo> changes)
            {
                List<MergeChangeInfo> result = new List<MergeChangeInfo>();

                foreach (MergeChangeInfo change in changes)
                {
                    string key = BuildKey.ForMetaChange(change);

                    MergeChangeInfo metaChange;
                    if (!mCache.TryGetValue(key, out metaChange))
                        continue;

                    result.Add(metaChange);
                }

                return result;
            }

            internal void Build(List<MergeChangesCategory> mergeChangesCategories)
            {
                mCache.Clear();

                foreach (MergeChangesCategory category in mergeChangesCategories)
                {
                    ExtractMetaToCache(category, mCache);
                }
            }

            static void ExtractMetaToCache(
                MergeChangesCategory category,
                Dictionary<string, MergeChangeInfo> cache)
            {
                List<MergeChangeInfo> changes = category.GetChanges();

                HashSet<string> indexedKeys = BuildIndexedKeys(
                    changes);

                for (int i = changes.Count - 1; i >= 0; i--)
                {
                    MergeChangeInfo currentChange = changes[i];

                    string path = currentChange.GetPath();

                    if (!MetaPath.IsMetaPath(path))
                        continue;

                    string realPath = MetaPath.GetPathFromMetaPath(path);

                    if (!indexedKeys.Contains(BuildKey.BuildCacheKey(
                        currentChange.CategoryType, realPath)))
                        continue;

                    // found foo.c and foo.c.meta - move .meta to cache
                    cache.Add(BuildKey.ForChange(currentChange), currentChange);
                    changes.RemoveAt(i);
                }
            }

            static HashSet<string> BuildIndexedKeys(
                List<MergeChangeInfo> changes)
            {
                HashSet<string> result = new HashSet<string>();

                foreach (MergeChangeInfo change in changes)
                {
                    if (MetaPath.IsMetaPath(change.GetPath()))
                        continue;

                    result.Add(BuildKey.ForChange(change));
                }

                return result;
            }

            Dictionary<string, MergeChangeInfo> mCache =
                new Dictionary<string, MergeChangeInfo>();

            static class BuildKey
            {
                internal static string ForChange(
                    MergeChangeInfo change)
                {
                    return BuildCacheKey(
                        change.CategoryType,
                        change.GetPath());
                }

                internal static string ForMetaChange(
                    MergeChangeInfo change)
                {
                    return BuildCacheKey(
                        change.CategoryType,
                        MetaPath.GetMetaPath(change.GetPath()));
                }

                internal static string BuildCacheKey(
                    MergeChangesCategory.Type type,
                    string path)
                {
                    return string.Concat(type, ":", path);
                }
            }
        }
    }
}