private static bool ShouldBeMerged(SpanEquipmentWithConnectsHolder firstSpanEquipment, SpanEquipmentWithConnectsHolder secondSpanEquipment)
        {
            if (firstSpanEquipment.Connects.Count == 1 && firstSpanEquipment.SpanEquipment.TryGetSpanSegment(firstSpanEquipment.Connects[0].ConnectInfo.SegmentId, out var firstSpanSegmentWithIndexInfo))
            {
                // If we're dealing with structure index 0, then the client is connecting the outer span
                if (firstSpanSegmentWithIndexInfo.StructureIndex == 0)
                {
                    return(true);
                }
            }

            return(false);
        }
        private Result CheckIfConnectsAreAlligned(SpanEquipmentWithConnectsHolder firstSpanEquipment, SpanEquipmentWithConnectsHolder secondSpanEquipment)
        {
            // If more than 2 segments selected in each span equipment, check that they are alligned in terms of specifications
            if (firstSpanEquipment.Connects.Count > 1)
            {
                HashSet <Guid> firstSpanEquipmentStructureSpecIds  = new HashSet <Guid>();
                HashSet <Guid> secondSpanEquipmentStructureSpecIds = new HashSet <Guid>();

                foreach (var firstEqSpanSegmentConnect in firstSpanEquipment.Connects)
                {
                    firstSpanEquipment.SpanEquipment.TryGetSpanSegment(firstEqSpanSegmentConnect.ConnectInfo.SegmentId, out var spanSegmentWithIndexInfo);
                    var structureSpecId = firstSpanEquipment.SpanEquipment.SpanStructures[spanSegmentWithIndexInfo.StructureIndex].SpecificationId;
                    firstEqSpanSegmentConnect.StructureSpecificationId = structureSpecId;
                    firstSpanEquipmentStructureSpecIds.Add(structureSpecId);
                }

                foreach (var secondEqSpanSegmentConnect in secondSpanEquipment.Connects)
                {
                    secondSpanEquipment.SpanEquipment.TryGetSpanSegment(secondEqSpanSegmentConnect.ConnectInfo.SegmentId, out var spanSegmentWithIndexInfo);
                    var structureSpecId = secondSpanEquipment.SpanEquipment.SpanStructures[spanSegmentWithIndexInfo.StructureIndex].SpecificationId;
                    secondEqSpanSegmentConnect.StructureSpecificationId = structureSpecId;
                    secondSpanEquipmentStructureSpecIds.Add(structureSpecId);
                }

                foreach (var firstStructureId in firstSpanEquipmentStructureSpecIds)
                {
                    if (!secondSpanEquipmentStructureSpecIds.Contains(firstStructureId))
                    {
                        return(Result.Fail(new ConnectSpanSegmentsAtRouteNodeError(
                                               ConnectSpanSegmentsAtRouteNodeErrorCodes.EXPECTED_SAME_SPECIFICATIONS_OF_SPAN_SEGMENTS_BELONGING_TO_TWO_SPAN_EQUIPMENT,
                                               $"Cannot connect the span segments specified because specifications on the span structures don't allign. Make sure that you connect span segments beloning to structure with the same specs - i.e. a red and blue Ø10 from one span equipment to a red and blue Ø10 in the other span equipment.")
                                           ));
                    }
                }

                // Order connects by spec id
                firstSpanEquipment.Connects  = firstSpanEquipment.Connects.OrderBy(s => s.StructureSpecificationId).ToList();
                secondSpanEquipment.Connects = secondSpanEquipment.Connects.OrderBy(s => s.StructureSpecificationId).ToList();
            }

            return(Result.Ok());
        }
        private Result ConnectTwoSpanEquipment(CommandContext cmdContext, Guid routeNodeId, SpanEquipmentWithConnectsHolder firstSpanEquipment, SpanEquipmentWithConnectsHolder secondSpanEquipment)
        {
            // Create junction/terminal ids used to connect span segments
            for (int i = 0; i < firstSpanEquipment.Connects.Count; i++)
            {
                var junctionId = Guid.NewGuid();

                firstSpanEquipment.Connects[i].ConnectInfo.TerminalId          = junctionId;
                firstSpanEquipment.Connects[i].ConnectInfo.ConnectionDirection = SpanSegmentToTerminalConnectionDirection.FromSpanSegmentToTerminal;

                secondSpanEquipment.Connects[i].ConnectInfo.TerminalId          = junctionId;
                secondSpanEquipment.Connects[i].ConnectInfo.ConnectionDirection = SpanSegmentToTerminalConnectionDirection.FromTerminalToSpanSegment;
            }

            // Connect the first span equipment to terminals
            var firstSpanEquipmentAR = _eventStore.Aggregates.Load <SpanEquipmentAR>(firstSpanEquipment.SpanEquipment.Id);

            var firstSpanEquipmentConnectResult = firstSpanEquipmentAR.ConnectSpanSegmentsToSimpleTerminals(
                cmdContext: cmdContext,
                routeNodeId: routeNodeId,
                connects: firstSpanEquipment.Connects.Select(c => c.ConnectInfo).ToArray()
                );

            if (firstSpanEquipmentConnectResult.IsFailed)
            {
                return(firstSpanEquipmentConnectResult);
            }

            // Connect the second span equipment to terminals
            var secondSpanEquipmentAR = _eventStore.Aggregates.Load <SpanEquipmentAR>(secondSpanEquipment.SpanEquipment.Id);

            var secondSpanEquipmentConnectResult = secondSpanEquipmentAR.ConnectSpanSegmentsToSimpleTerminals(
                cmdContext: cmdContext,
                routeNodeId: routeNodeId,
                connects: secondSpanEquipment.Connects.Select(c => c.ConnectInfo).ToArray()
                );

            if (secondSpanEquipmentConnectResult.IsFailed)
            {
                return(secondSpanEquipmentConnectResult);
            }

            _eventStore.Aggregates.Store(firstSpanEquipmentAR);
            _eventStore.Aggregates.Store(secondSpanEquipmentAR);

            NotifyExternalServicesAboutConnectivityChange(firstSpanEquipment.SpanEquipment.Id, secondSpanEquipment.SpanEquipment.Id, routeNodeId);

            return(Result.Ok());
        }
        private Result ConnectSameEquipment(CommandContext cmdContext, Guid routeNodeId, SpanEquipmentWithConnectsHolder spanEquipmentToConnect)
        {
            if (spanEquipmentToConnect.Connects.Count != 2)
            {
                return
                    (Result.Fail(new ConnectSpanSegmentsAtRouteNodeError(
                                     ConnectSpanSegmentsAtRouteNodeErrorCodes.SAME_SPAN_EQUIPMENT_CONNECTIONS_MUST_BE_DONE_TWO_SPAN_SEGMENTS_AT_THE_TIME,
                                     $"Cannot connect the span segments specified because {spanEquipmentToConnect.Connects.Count} segments are selected. Two span segments were expected.")
                                 ));
            }

            // Get the two segments from graph
            if (!_utilityNetwork.Graph.TryGetGraphElement <IUtilityGraphSegmentRef>(spanEquipmentToConnect.Connects[0].ConnectInfo.SegmentId, out var utilityGraphSegmentRefToConnect1))
            {
                throw new ApplicationException($"Failed to lookup span segment with id: {spanEquipmentToConnect.Connects[0].ConnectInfo.SegmentId}");
            }

            if (!_utilityNetwork.Graph.TryGetGraphElement <IUtilityGraphSegmentRef>(spanEquipmentToConnect.Connects[1].ConnectInfo.SegmentId, out var utilityGraphSegmentRefToConnect2))
            {
                throw new ApplicationException($"Failed to lookup span segment with id: {spanEquipmentToConnect.Connects[1].ConnectInfo.SegmentId}");
            }


            // If segment 1 -> (node) -> segment 2
            if (utilityGraphSegmentRefToConnect1.SpanSegment(_utilityNetwork).ToNodeOfInterestIndex == utilityGraphSegmentRefToConnect2.SpanSegment(_utilityNetwork).FromNodeOfInterestIndex)
            {
                spanEquipmentToConnect.Connects[0].ConnectInfo.ConnectionDirection = SpanSegmentToTerminalConnectionDirection.FromSpanSegmentToTerminal;
                spanEquipmentToConnect.Connects[1].ConnectInfo.ConnectionDirection = SpanSegmentToTerminalConnectionDirection.FromTerminalToSpanSegment;
            }
            // If segment 2-> (node)-> segment 1
            else if (utilityGraphSegmentRefToConnect1.SpanSegment(_utilityNetwork).FromNodeOfInterestIndex == utilityGraphSegmentRefToConnect2.SpanSegment(_utilityNetwork).ToNodeOfInterestIndex)
            {
                spanEquipmentToConnect.Connects[0].ConnectInfo.ConnectionDirection = SpanSegmentToTerminalConnectionDirection.FromTerminalToSpanSegment;
                spanEquipmentToConnect.Connects[1].ConnectInfo.ConnectionDirection = SpanSegmentToTerminalConnectionDirection.FromSpanSegmentToTerminal;
            }
            else
            {
                return
                    (Result.Fail(new ConnectSpanSegmentsAtRouteNodeError(
                                     ConnectSpanSegmentsAtRouteNodeErrorCodes.SAME_SPAN_EQUIPMENT_U_TURN_CONNECTIONS_NOT_ALLOWED,
                                     $"Cannot connect the span segments specified because the two span segments selected will create an u-turn.")
                                 ));
            }

            // Create junction/terminal ids used to connect span segments
            var junctionId = Guid.NewGuid();

            foreach (var connect in spanEquipmentToConnect.Connects)
            {
                connect.ConnectInfo.TerminalId = junctionId;
            }

            // Connect span equipment to terminals
            var spanEquipmentAR = _eventStore.Aggregates.Load <SpanEquipmentAR>(spanEquipmentToConnect.SpanEquipment.Id);

            var spanEquipmentConnectResult = spanEquipmentAR.ConnectSpanSegmentsToSimpleTerminals(
                cmdContext: cmdContext,
                routeNodeId: routeNodeId,
                connects: spanEquipmentToConnect.Connects.Select(c => c.ConnectInfo).ToArray()
                );

            if (spanEquipmentConnectResult.IsFailed)
            {
                return(spanEquipmentConnectResult);
            }

            _eventStore.Aggregates.Store(spanEquipmentAR);

            NotifyExternalServicesAboutConnectivityChange(spanEquipmentToConnect.SpanEquipment.Id, routeNodeId);

            return(Result.Ok());
        }
        private Result MergeSpanEquipment(CommandContext cmdContext, Guid routeNodeId, SpanEquipmentWithConnectsHolder firstSpanEquipment, SpanEquipmentWithConnectsHolder secondSpanEquipment)
        {
            // Merge the first span equipment with the second one
            var firstSpanEquipmentAR = _eventStore.Aggregates.Load <SpanEquipmentAR>(firstSpanEquipment.SpanEquipment.Id);

            var firstSpanEquipmentConnectResult = firstSpanEquipmentAR.Merge(
                cmdContext: cmdContext,
                routeNodeId: routeNodeId,
                secondSpanEquipment.SpanEquipment
                );

            if (firstSpanEquipmentConnectResult.IsFailed)
            {
                return(firstSpanEquipmentConnectResult);
            }

            var firstSpanEquipmentWalk  = GetInterestInformation(firstSpanEquipment.SpanEquipment);
            var secondSpanEquipmentWalk = GetInterestInformation(secondSpanEquipment.SpanEquipment);

            // Update interest of the first span equipment to cover both span equipments
            var newSegmentIds = MergeWalks(firstSpanEquipmentWalk, secondSpanEquipmentWalk);

            var updateWalkOfInterestCommand       = new UpdateWalkOfInterest(firstSpanEquipment.SpanEquipment.WalkOfInterestId, newSegmentIds);
            var updateWalkOfInterestCommandResult = _commandDispatcher.HandleAsync <UpdateWalkOfInterest, Result <RouteNetworkInterest> >(updateWalkOfInterestCommand).Result;

            if (updateWalkOfInterestCommandResult.IsFailed)
            {
                return(updateWalkOfInterestCommandResult);
            }

            // Remove the second span equipment
            var secondSpanEquipmentAR = _eventStore.Aggregates.Load <SpanEquipmentAR>(secondSpanEquipment.SpanEquipment.Id);
            var removeSpanEquipment   = secondSpanEquipmentAR.Remove(cmdContext);

            if (removeSpanEquipment.IsSuccess)
            {
                // Remember to remove the walk of interest as well
                var unregisterInterestCmd       = new UnregisterInterest(secondSpanEquipment.SpanEquipment.WalkOfInterestId);
                var unregisterInterestCmdResult = _commandDispatcher.HandleAsync <UnregisterInterest, Result>(unregisterInterestCmd).Result;

                if (unregisterInterestCmdResult.IsFailed)
                {
                    throw new ApplicationException($"Failed to unregister interest: {secondSpanEquipment.SpanEquipment.WalkOfInterestId} of span equipment: {secondSpanEquipment.SpanEquipment.Id} in RemoveSpanStructureFromSpanEquipmentCommandHandler Error: {unregisterInterestCmdResult.Errors.First().Message}");
                }
            }

            _eventStore.Aggregates.Store(firstSpanEquipmentAR);
            _eventStore.Aggregates.Store(secondSpanEquipmentAR);

            NotifyExternalServicesAboutMerge(firstSpanEquipment.SpanEquipment.Id, updateWalkOfInterestCommandResult.Value.RouteNetworkElementRefs.ToArray());

            return(Result.Ok());
        }