using System;
using System.Runtime.CompilerServices;

namespace Assets.Voronoi
{
    public enum RedBlackNodeType
    {
        Black,
        Red
    }
    
    public class RedBlackNode<T>
    {
        public T Value { get; internal set; }

        internal RedBlackNodeType Color { get; set; } = RedBlackNodeType.Red;

        internal RedBlackNode<T> _left;
        internal RedBlackNode<T> _right;


        public RedBlackNode<T> Next => GetSuccessor();
        public RedBlackNode<T> Previous => GetPredecessor();

        internal RedBlackNode<T> GrandParent => Parent?.Parent;

        internal RedBlackNode<T> Uncle => Parent?.Parent?.Left == Parent
            ? Parent?.Parent?.Right
            : Parent?.Parent?.Left;

        public RedBlackNode<T> Parent { get; internal set; }

        public RedBlackNode<T> Right
        {
            get => _right;
            internal set
            {
                _right = value;
                if (value != null) value.Parent = this;
            }
        }

        public RedBlackNode<T> Left
        {
            get => _left;
            internal set
            {
                _left = value;
                if (value != null) value.Parent = this;
            }
        }

        public RedBlackNode(T value = default)
        {
            Value = value;
        }

        private RedBlackNode<T> GetSuccessor()
        {
            RedBlackNode<T> successor, node = this;

            if (node.Right != null)
            {
                successor = node.Right;

                while (successor.Left != null)
                    successor = successor.Left;

                return successor;
            }

            successor = node.Parent;
            while (successor != null && successor.Right == node)
            {
                node = successor;
                successor = successor.Parent;
            }

            return successor;
        }

        private RedBlackNode<T> GetPredecessor()
        {
            RedBlackNode<T> predecessor, node = this;

            if (node.Left != null)
            {
                predecessor = node.Left;

                while (predecessor.Right != null)
                    predecessor = predecessor.Right;

                return predecessor;
            }

            predecessor = node.Parent;
            while (predecessor != null && predecessor.Left == node)
            {
                node = predecessor;
                predecessor = predecessor.Parent;
            }

            return predecessor;
        }
    }

    public class RedBlackTree<T>
    {
        private RedBlackNode<T> _root;

        public RedBlackNode<T> Root
        {
            get => _root;
            internal set
            {
                _root = value;
                _root.Color = RedBlackNodeType.Black;
            }
        }

        public bool IsEmpty => _root == null;

        public RedBlackNode<T> First
        {
            get
            {
                var current = _root;

                while (current?.Left != null) current = current.Left;

                return current;
            }
        }

        public RedBlackNode<T> Last
        {
            get
            {
                var current = _root;

                while (current?.Right != null) current = current.Right;

                return current;
            }
        }

        public void Rebalance(RedBlackNode<T> node)
        {
            while (node != Root && node.Parent.Color == RedBlackNodeType.Red)
            {
                if (node.Uncle != null && node.Uncle.Color == RedBlackNodeType.Red)
                {
                    node.Parent.Color = RedBlackNodeType.Black;
                    node.Uncle.Color = RedBlackNodeType.Black;

                    node = node.GrandParent;
                }
                else
                {
                    (node.Parent.Color, node.GrandParent.Color) = (node.GrandParent.Color, node.Parent.Color);

                    if (node == node.Parent.Left)
                    {
                        if (node.Parent == node.GrandParent.Right)
                        {
                            RotateLeft(node.Parent);
                        }

                        RotateRight(node.GrandParent);
                    }
                    else
                    {
                        if (node.Parent == node.GrandParent.Left)
                        {
                            RotateRight(node.Parent);
                        }

                        RotateRight(node.GrandParent);
                    }
                }
            }
        }

        public void InsertAfter(RedBlackNode<T> node, T value)
        {
            var current = node.Right;
            var inserted = new RedBlackNode<T>(value);

            if (current is null)
            {
                node.Right = inserted;
            }
            else
            {
                while (current.Left != null)
                    current = current.Left;

                current.Left = inserted;
            }

//            Rebalance(inserted);
        }

        public void InsertBefore(RedBlackNode<T> node, T value)
        {
            var current = node.Left;
            var inserted = new RedBlackNode<T>(value);

            if (current is null)
            {
                node.Left = inserted;
            }
            else
            {
                while (current.Right != null)
                    current = current.Right;

                current.Right = inserted;
            }

//            Rebalance(inserted);
        }

        private void RotateLeft(RedBlackNode<T> current)
        {
            var right = current.Right;

            current.Right = right.Left;
            right.Left = current;
            right.Parent = current.Parent;
            current.Parent = right;

            FixRoot(current, right);
        }

        private void RotateRight(RedBlackNode<T> current)
        {
            var left = current.Left;

            current.Left = left.Right;
            left.Right = current;
            left.Parent = current.Parent;
            current.Parent = left;

            FixRoot(current, left);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void FixRoot(RedBlackNode<T> old, RedBlackNode<T> current)
        {
            if (Root == old)
            {
                Root = current;
                Root.Color = RedBlackNodeType.Black;
                Root.Parent = null;
            }
        }

        public void Remove(RedBlackNode<T> node)
        {
            if (node.Left == null && node.Right == null)
                RemoveLeaf(node);
            else if (node.Left == null || node.Right == null)
                RemoveOneChild(node);
            else
                Substitute(node, node.Next);
        }

        private void RemoveOneChild(RedBlackNode<T> node)
        {
            var replacement = node.Left ?? node.Right;
            
            if (Root == node)
                Root = replacement;
            else if (node.Parent.Left == node)
                node.Parent.Left = replacement;
            else if (node.Parent.Right == node)
                node.Parent.Right = replacement;
            else
                throw new Exception("WTF?");
        }

        private void Swap(RedBlackNode<T> a, RedBlackNode<T> b)
        {
            if (b.Parent == a)
                (a, b) = (b, a);

            if (a.Parent == b)
            {
                if (b.Parent?.Left == b) b.Parent.Left = a;
                if (b.Parent?.Right == b) b.Parent.Right = a;
                if (b.Parent == null) a.Parent = null;

                if (b.Left == a)
                {
                    (b.Right, a.Right) = (a.Right, b.Right);
                    
                    b.Left = a.Left;
                    a.Left = b;
                }
                else
                {
                    (b.Left, a.Left) = (a.Left, b.Left);
                    
                    b.Right = a.Right;
                    a.Right = b;
                }
            }
            else
            {
                (a.Right, b.Right) = (b.Right, a.Right);
                (a.Left, b.Left) = (b.Left, a.Left);

                var bparent = b.Parent;
                
                if (a.Parent != null && a.Parent.Left == a) a.Parent.Left = b;
                else if (a.Parent != null && a.Parent.Right == a) a.Parent.Right = b;
                else b.Parent = null;

                if (bparent != null && bparent.Left == b) bparent.Left = a;
                else if (bparent != null && bparent.Right == b) bparent.Right = a;
                else a.Parent = null;
            }

            if (b.Parent == null) Root = b;
            if (a.Parent == null) Root = a;
        }
        
        private void Substitute(RedBlackNode<T> node, RedBlackNode<T> replacement)
        {
//            (node.Value, replacement.Value) = (replacement.Value, node.Value);
            Swap(node, replacement);            

            Remove(node);
        }

        private void RemoveLeaf(RedBlackNode<T> leaf)
        {
            if (leaf.Parent == null)
            {
                Root = null;
                return;
            }

            if (leaf.Parent.Left == leaf) leaf.Parent.Left = null;
            else leaf.Parent.Right = null;
        }
    }
}