IExpressionTreeNode InjectInto(PredicateNode targetPredicate, Func<IPredicateConnectiveNode> createConnectiveNode)
        {
            if (targetPredicate == null)
                throw new ArgumentNullException("targetPredicate");

            var sourceParent = Parent;

            var targetParent = targetPredicate.Parent;

            if(Parent != null && targetParent != null && object.ReferenceEquals(Parent, targetParent))
            {
                Parent.SwapChildren();
                return FindRoot();

            }

            if(targetParent == null)
            {
                // create a parent for a target
                var newParent = createConnectiveNode();
                newParent.AssignChild(targetPredicate, ChildNodePosition.Right);
                targetPredicate.AssignParent(newParent);

                targetParent = newParent;
            }

            //# get target sibling
            //  injection will be handled differently depending on type of the sibling
            var targetPosition = targetParent.GetChildPosition(targetPredicate);

            var targetSibling = (IExpressionTreeNode)null;

            if (targetPosition == ChildNodePosition.Left)
                targetSibling = targetParent.Right;
            else
                targetSibling = targetParent.Left;

            if (targetSibling == null || targetSibling is PredicateNode)
            {
                // target sibling is null or predicate
                // we can simply replace target with a Connective Node
                // (if sibling was a connective node itself then injecting another connective node would create bracket in logic, which we don't want)
                // (node with two connective node children is treated as a bracket, e.g: AND.l = [a OR b], AND.l = [c OR d] => ((a OR b) AND (c OR d))

                return InjectInto(this, targetPredicate, createConnectiveNode);
            }
            else if (targetSibling is PredicateConnectiveNode)
            {
                // target sibling is a Predicate Connective Node
                // predicate will be injected in a new Predicate Connective Node above target parent

                return InjectInto(this, targetParent as PredicateConnectiveNode, createConnectiveNode);
            }
            else
            {
                throw new NotSupportedException();
            }
        }
        void RemoveNodeInternal(PredicateNode node)
        {
            if (node == Root)
            {
                ReplaceRootAndSubtree(null);
                return;
            }

            var nodeParent = node.Parent;

            var position = nodeParent.GetChildPosition(node);

            nodeParent.AssignChild(null, position);
        }
        IExpressionTreeNode InjectInto(PredicateNode target, Func<IPredicateConnectiveNode> createConnectiveNode)
        {
            var source = this;

            var targetParent = target.Parent;

            //# target is child of source, do nothing
            if (object.Equals(target.Parent, source))
                return FindRoot();

            //# target and this are children of same parent -> swap places
            if(targetParent != null && object.Equals(targetParent, source.Parent))
            {
                targetParent.SwapChildren();

                return FindRoot();
            }

            var originalTargetPosition = default(ChildNodePosition);

            if(targetParent != null)
            {
                originalTargetPosition = targetParent.GetChildPosition(target);
            }

            bool targetIsDescendantOfSource = target.IsDescendantOf(source);

            if(targetIsDescendantOfSource)
            {
                // target is a descendant of source
                // before source is injected into a parent of a target (because target is a Predicate Node, source cannot be injected into it directly)
                // attach target parent to parent of source

                var targetGrandParent = targetParent.Parent;

                targetGrandParent.ClearChildAssignment(targetParent);
                targetParent.AssignParent(source.Parent);

                if(source.Parent != null)
                    source.Parent.ReplaceChildNode(source, targetParent);
            }
            else
            {
                if (source.Parent != null)
                {
                    source.Parent.ClearChildAssignment(source);
                    source.AssignParent(null);
                }
            }

            //# check if source has an empty child slot
            if(source.Left == null || source.Right == null)
            {
                // connect target to source
                // connect source to target parent

                source.AssignParent(targetParent);
                targetParent.ReplaceChildNode(target, source);

                target.AssignParent(source);

                if (source.Left == null)
                    source.AssignChild(target, ChildNodePosition.Left);
                else
                    source.AssignChild(target, ChildNodePosition.Right);

                return FindRoot();
            }

            //# source has both children, create a new connective mode to link target parent, target and source

            var newConnective = createConnectiveNode();

            newConnective.CopyFrom(this);

            newConnective.AssignChild(target, ChildNodePosition.Left);
            newConnective.AssignChild(this, ChildNodePosition.Right);

            target.AssignParent(newConnective);

            targetParent.AssignChild(newConnective, originalTargetPosition);
            newConnective.AssignParent(targetParent);

            //# update this parent
            if (this.Parent != null)
            {
                this.Parent.ClearChildAssignment(this);
            }

            this.AssignParent(newConnective);

            return FindRoot();
        }