using Assets.Common;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Assets.Common
{
    [Serializable]
    public class Graph<T> : ICloneable
    {
        public List<T> Vertices { get; internal set; } = new List<T>();
        private Dictionary<int, List<int>> _edges = new Dictionary<int, List<int>>();

        public T this[int i] => Vertices[i];
        
        public IEnumerable<(int, int)> Edges
        {
            get
            {
                return _edges.SelectMany(pair => pair.Value.Where(value => value > pair.Key).Select(value => (pair.Key, value)));
            }

            set
            {
                _edges = new Dictionary<int, List<int>>();
                
                foreach (var (a, b) in value)
                    AddEdge(a, b);
            }
        }

        public void AddEdge(int a, int b)
        {
            if (!_edges.ContainsKey(a))
                _edges[a] = new List<int>();
            
            if (!_edges.ContainsKey(b))
                _edges[b] = new List<int>();
            
            _edges[a].Add(b);
            _edges[b].Add(a);
        }

        public void DeleteEdge(int a, int b)
        {
            if (!_edges.ContainsKey(a))
                return;
            
            if (!_edges.ContainsKey(b))
                return;

            _edges[a].Remove(b);
            _edges[b].Remove(a);
        }

        public static bool HasCycle(Graph<T> graph)
        {
            int Find(int[] parent, int i)
            {
                if (parent[i] == -1)
                    return i;

                return Find(parent, parent[i]);
            }

            void Union(int[] parent, int x, int y)
            {
                int xset = Find(parent, x);
                int yset = Find(parent, y);
                parent[xset] = yset;
            }

            int[] trueParent = new int[graph.Vertices.Count()];

            for (int i = 0; i < graph.Vertices.Count(); ++i)
                trueParent[i] = -1;

            for (int i = 0; i < graph.Edges.Count(); ++i)
            {
                int x = Find(trueParent, graph.Edges.ElementAt(i).Item1);
                int y = Find(trueParent, graph.Edges.ElementAt(i).Item2);

                if (x == y)
                    return true;

                Union(trueParent, x, y);
            }

            return false;
        }

        public Graph<U> Morph<U>(Func<T, U> vertices)
        {
            var result = new Graph<U>() { Edges = Edges };

            result.Vertices.AddRange(Vertices.Select(vertices));

            return result;
        }

        public Graph<U, E> Morph<U, E>(Func<T, U> vertices, Func<(int, int), E> edges)
        {
            var result = new Graph<U, E>() { Edges = Edges };

            result._edgeData = Edges.ToDictionary(k => k, v => edges(v));
            result.Vertices.AddRange(Vertices.Select(vertices));

            return result;
        }

        public IEnumerable<int> Neighbours(int vertex)
        {
            if (!_edges.ContainsKey(vertex)) return Enumerable.Empty<int>();

            return _edges[vertex].Distinct();
        }

        public virtual object Clone()
        {
            return Morph(l => l);
        }
    }

    public class Graph<T, E> : Graph<T>
    {
        internal Dictionary<(int, int), E> _edgeData = new Dictionary<(int, int), E>();

        public void AddEdge(int a, int b, E data)
        {
            AddEdge(a, b);
            SetEdgeData((a, b), data);
        }

        public void SetEdgeData((int, int) edge, E data)
        {
            var (a, b) = edge;

            if (a > b) (a, b) = (b, a);

            _edgeData[(a, b)] = data;
        }

        public E GetEdgeData((int, int) edge)
        {
            var (a, b) = edge;

            if (a > b) (a, b) = (b, a);

            return _edgeData[(a, b)];
        }

        public Graph<U, E2> Morph<U, E2>(Func<T, U> vertices, Func<E, (int, int), E2> edges)
        {
            var result = new Graph<U, E2>() { Edges = Edges };

            result._edgeData = _edgeData.ToDictionary(e => e.Key, e => edges(e.Value, e.Key));
            result.Vertices.AddRange(Vertices.Select(vertices));

            return result;
        }

        public override object Clone()
        {
            return Morph(l => l, l => l);
        }
    }
}