public static SpanEquipment Apply(SpanEquipment existingSpanEquipment, SpanSegmentsCut spanSegmentsCutEvent)
        {
            // Cut them segments
            List <SpanStructure> newStructures = new List <SpanStructure>();

            // Create dictionary of cuts for fast lookup
            Dictionary <Guid, SpanSegmentCutInfo> spanSegmentCutInfoBySegmentId = spanSegmentsCutEvent.Cuts.ToDictionary(c => c.OldSpanSegmentId);

            // First create a new nodes of interest list with the cut node added
            Guid[] newNodeOfInterestIdList = CreateNewNodeOfInterestIdListWith(existingSpanEquipment, spanSegmentsCutEvent.CutNodeOfInterestId, spanSegmentsCutEvent.CutNodeOfInterestIndex);

            bool nodeOfInterestAlreadyExists = existingSpanEquipment.NodesOfInterestIds.Contains(spanSegmentsCutEvent.CutNodeOfInterestId);

            // Loop though all span structures
            for (UInt16 structureIndex = 0; structureIndex < existingSpanEquipment.SpanStructures.Length; structureIndex++)
            {
                var existingSpanStructure = existingSpanEquipment.SpanStructures[structureIndex];

                List <SpanSegment> newSegments = new List <SpanSegment>();

                // Loop throughh all span segments
                foreach (var existingSegment in existingSpanStructure.SpanSegments)
                {
                    UInt16 fromNodeOfInterestIndexToUse = existingSegment.FromNodeOfInterestIndex;
                    UInt16 toNodeOfInterestIndexToUse   = existingSegment.ToNodeOfInterestIndex;

                    if (!nodeOfInterestAlreadyExists)
                    {
                        if (fromNodeOfInterestIndexToUse >= spanSegmentsCutEvent.CutNodeOfInterestIndex)
                        {
                            fromNodeOfInterestIndexToUse++;
                        }

                        if (toNodeOfInterestIndexToUse >= spanSegmentsCutEvent.CutNodeOfInterestIndex)
                        {
                            toNodeOfInterestIndexToUse++;
                        }
                    }

                    // If cut info exists
                    if (spanSegmentCutInfoBySegmentId.TryGetValue(existingSegment.Id, out var spanSegmentCutInfo))
                    {
                        // Add the first segment
                        newSegments.Add(
                            new SpanSegment(
                                id: spanSegmentCutInfo.NewSpanSegmentId1,
                                fromNodeOfInterestIndex: fromNodeOfInterestIndexToUse,
                                toNodeOfInterestIndex: spanSegmentsCutEvent.CutNodeOfInterestIndex
                                )
                        {
                            FromTerminalId = existingSegment.FromTerminalId
                        }
                            );

                        // Add the second segment
                        newSegments.Add(
                            new SpanSegment(
                                id: spanSegmentCutInfo.NewSpanSegmentId2,
                                fromNodeOfInterestIndex: spanSegmentsCutEvent.CutNodeOfInterestIndex,
                                toNodeOfInterestIndex: toNodeOfInterestIndexToUse
                                )
                        {
                            ToTerminalId = existingSegment.ToTerminalId
                        }
                            );
                    }
                    // If no cut info exists
                    else
                    {
                        if (!nodeOfInterestAlreadyExists)
                        {
                            var newSegment = existingSegment with {
                                FromNodeOfInterestIndex = fromNodeOfInterestIndexToUse, ToNodeOfInterestIndex = toNodeOfInterestIndexToUse
                            };
                            newSegments.Add(newSegment);
                        }
                        else
                        {
                            newSegments.Add(existingSegment);
                        }
                    }
                }

                newStructures.Add(
                    existingSpanStructure with
                {
                    SpanSegments = newSegments.ToArray()
                });
            }

            return(existingSpanEquipment with
            {
                NodesOfInterestIds = newNodeOfInterestIdList,
                SpanStructures = newStructures.ToArray()
            });
        }
        public void TestCutTwoStructuresAtSameNode_ShouldSucceed()
        {
            // Setup
            var cutNodeOfInterestId = Guid.NewGuid();
            var cut1spanSegmentId   = Guid.NewGuid();
            var cut2spanSegmentId   = Guid.NewGuid();

            var existingSpanEquipment = new OpenFTTH.UtilityGraphService.API.Model.UtilityNetwork.SpanEquipment(
                id: Guid.NewGuid(),
                specificationId: Guid.NewGuid(),
                walkOfInterestId: Guid.NewGuid(),
                nodesOfInterestIds: new Guid[] { Guid.NewGuid(), Guid.NewGuid() },
                spanStructures:
                new SpanStructure[]
            {
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 1,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(cut1spanSegmentId, 0, 1)
                }
                    ),
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 2,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(cut2spanSegmentId, 0, 1)
                }
                    )
            }
                );

            // Cut first structure
            var cut1newSegmentId1 = Guid.NewGuid();
            var cut1newSegmentId2 = Guid.NewGuid();

            var cut1Event = new SpanSegmentsCut(
                spanEquipmentId: existingSpanEquipment.Id,
                cutNodeOfInterestId: cutNodeOfInterestId,
                cutNodeOfInterestIndex: 1,
                cuts:
                new SpanSegmentCutInfo[]
            {
                new SpanSegmentCutInfo(
                    oldSpanSegmentId: cut1spanSegmentId,
                    newSpanSegmentId1: cut1newSegmentId1,
                    newSpanSegmentId2: cut1newSegmentId2
                    )
            }
                );


            // Cut second structure
            var cut2newSegmentId1 = Guid.NewGuid();
            var cut2newSegmentId2 = Guid.NewGuid();

            var cut2Event = new SpanSegmentsCut(
                spanEquipmentId: existingSpanEquipment.Id,
                cutNodeOfInterestId: cutNodeOfInterestId,
                cutNodeOfInterestIndex: 1,
                cuts:
                new SpanSegmentCutInfo[]
            {
                new SpanSegmentCutInfo(
                    oldSpanSegmentId: cut2spanSegmentId,
                    newSpanSegmentId1: cut2newSegmentId1,
                    newSpanSegmentId2: cut2newSegmentId2
                    )
            }
                );

            // Act
            var newSpanEquipment = SpanEquipmentProjectionFunctions.Apply(existingSpanEquipment, cut1Event);

            newSpanEquipment = SpanEquipmentProjectionFunctions.Apply(newSpanEquipment, cut2Event);

            // Assert
            newSpanEquipment.NodesOfInterestIds.Length.Should().Be(3);
            newSpanEquipment.SpanStructures[0].SpanSegments.Length.Should().Be(2);

            // Structure 1
            newSpanEquipment.SpanStructures[0].SpanSegments[0].Id.Should().Be(cut1newSegmentId1);
            newSpanEquipment.SpanStructures[0].SpanSegments[0].FromNodeOfInterestIndex.Should().Be(0);
            newSpanEquipment.SpanStructures[0].SpanSegments[0].ToNodeOfInterestIndex.Should().Be(1);

            newSpanEquipment.SpanStructures[0].SpanSegments[1].Id.Should().Be(cut1newSegmentId2);
            newSpanEquipment.SpanStructures[0].SpanSegments[1].FromNodeOfInterestIndex.Should().Be(1);
            newSpanEquipment.SpanStructures[0].SpanSegments[1].ToNodeOfInterestIndex.Should().Be(2);

            // Structure 2
            newSpanEquipment.SpanStructures[1].SpanSegments[0].Id.Should().Be(cut2newSegmentId1);
            newSpanEquipment.SpanStructures[1].SpanSegments[0].FromNodeOfInterestIndex.Should().Be(0);
            newSpanEquipment.SpanStructures[1].SpanSegments[0].ToNodeOfInterestIndex.Should().Be(1);

            newSpanEquipment.SpanStructures[1].SpanSegments[1].Id.Should().Be(cut2newSegmentId2);
            newSpanEquipment.SpanStructures[1].SpanSegments[1].FromNodeOfInterestIndex.Should().Be(1);
            newSpanEquipment.SpanStructures[1].SpanSegments[1].ToNodeOfInterestIndex.Should().Be(2);
        }
        public void TestCutDifferentStructureMultipleTime_ShouldSucceed()
        {
            // Setup
            var cut1spanSegmentId = Guid.NewGuid();
            var cut2spanSegmentId = Guid.NewGuid();
            var cut3spanSegmentId = Guid.NewGuid();

            var existingSpanEquipment = new OpenFTTH.UtilityGraphService.API.Model.UtilityNetwork.SpanEquipment(
                id: Guid.NewGuid(),
                specificationId: Guid.NewGuid(),
                walkOfInterestId: Guid.NewGuid(),
                nodesOfInterestIds: new Guid[] { Guid.NewGuid(), Guid.NewGuid() },
                spanStructures:
                new SpanStructure[]
            {
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 1,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(cut1spanSegmentId, 0, 1)
                }
                    ),
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 2,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(cut2spanSegmentId, 0, 1)
                }
                    ),
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 3,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(cut3spanSegmentId, 0, 1)
                }
                    )
            }
                );

            // Cut first structure
            var cut1newSegmentId1 = Guid.NewGuid();
            var cut1newSegmentId2 = Guid.NewGuid();

            var cutEvent1 = new SpanSegmentsCut(
                spanEquipmentId: existingSpanEquipment.Id,
                cutNodeOfInterestId: Guid.NewGuid(),
                cutNodeOfInterestIndex: 1,
                cuts:
                new SpanSegmentCutInfo[]
            {
                new SpanSegmentCutInfo(
                    oldSpanSegmentId: cut1spanSegmentId,
                    newSpanSegmentId1: cut1newSegmentId1,
                    newSpanSegmentId2: cut1newSegmentId2
                    )
            }
                );

            var newSpanEquipment1 = SpanEquipmentProjectionFunctions.Apply(existingSpanEquipment, cutEvent1);

            // Cut second structure
            var cut2newSegmentId1 = Guid.NewGuid();
            var cut2newSegmentId2 = Guid.NewGuid();

            var cutEvent2 = new SpanSegmentsCut(
                spanEquipmentId: existingSpanEquipment.Id,
                cutNodeOfInterestId: Guid.NewGuid(),
                cutNodeOfInterestIndex: 2,
                cuts:
                new SpanSegmentCutInfo[]
            {
                new SpanSegmentCutInfo(
                    oldSpanSegmentId: cut1spanSegmentId,
                    newSpanSegmentId1: cut1newSegmentId1,
                    newSpanSegmentId2: cut1newSegmentId2
                    )
            }
                );

            var newSpanEquipment2 = SpanEquipmentProjectionFunctions.Apply(newSpanEquipment1, cutEvent2);



            newSpanEquipment1.NodesOfInterestIds.Length.Should().Be(3);

            // Check that structure 1 has been cut correctly
            newSpanEquipment1.SpanStructures[0].SpanSegments.Length.Should().Be(2);

            newSpanEquipment1.SpanStructures[0].SpanSegments[0].Id.Should().Be(cut1newSegmentId1);
            newSpanEquipment1.SpanStructures[0].SpanSegments[0].FromNodeOfInterestIndex.Should().Be(0);
            newSpanEquipment1.SpanStructures[0].SpanSegments[0].ToNodeOfInterestIndex.Should().Be(1);

            newSpanEquipment1.SpanStructures[0].SpanSegments[1].Id.Should().Be(cut1newSegmentId2);
            newSpanEquipment1.SpanStructures[0].SpanSegments[1].FromNodeOfInterestIndex.Should().Be(1);
            newSpanEquipment1.SpanStructures[0].SpanSegments[1].ToNodeOfInterestIndex.Should().Be(2);
        }
        public void TestCutStructureOneTime_ShouldSucceed()
        {
            // Setup
            var cutNodeOfInterestId = Guid.NewGuid();

            var spanSegmentId1ToCut = Guid.NewGuid();
            var spanSegmentId2      = Guid.NewGuid();

            var existingSpanEquipment = new OpenFTTH.UtilityGraphService.API.Model.UtilityNetwork.SpanEquipment(
                id: Guid.NewGuid(),
                specificationId: Guid.NewGuid(),
                walkOfInterestId: Guid.NewGuid(),
                nodesOfInterestIds: new Guid[] { Guid.NewGuid(), Guid.NewGuid() },
                spanStructures:
                new SpanStructure[]
            {
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 1,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(spanSegmentId1ToCut, 0, 1)
                }
                    ),
                new SpanStructure(
                    id: Guid.NewGuid(),
                    specificationId: Guid.NewGuid(),
                    level: 1,
                    position: 2,
                    parentPosition: 0,
                    spanSegments:
                    new SpanSegment[]
                {
                    new SpanSegment(spanSegmentId2, 0, 1)
                }
                    )
            }
                );

            // Cut first structure
            var newSegmentId1 = Guid.NewGuid();
            var newSegmentId2 = Guid.NewGuid();

            var cutEvent = new SpanSegmentsCut(
                spanEquipmentId: existingSpanEquipment.Id,
                cutNodeOfInterestId: cutNodeOfInterestId,
                cutNodeOfInterestIndex: 1,
                cuts:
                new SpanSegmentCutInfo[]
            {
                new SpanSegmentCutInfo(
                    oldSpanSegmentId: spanSegmentId1ToCut,
                    newSpanSegmentId1: newSegmentId1,
                    newSpanSegmentId2: newSegmentId2
                    )
            }
                );

            // Act
            var newSpanEquipment = SpanEquipmentProjectionFunctions.Apply(existingSpanEquipment, cutEvent);

            // Assert
            newSpanEquipment.NodesOfInterestIds.Length.Should().Be(3);

            // Check that the cut node is inserted at index 1
            newSpanEquipment.NodesOfInterestIds[1].Should().Be(cutNodeOfInterestId);

            // Check that structure 1 has been cut correctly
            newSpanEquipment.SpanStructures[0].SpanSegments.Length.Should().Be(2);

            newSpanEquipment.SpanStructures[0].SpanSegments[0].Id.Should().Be(newSegmentId1);
            newSpanEquipment.SpanStructures[0].SpanSegments[0].FromNodeOfInterestIndex.Should().Be(0);
            newSpanEquipment.SpanStructures[0].SpanSegments[0].ToNodeOfInterestIndex.Should().Be(1);

            newSpanEquipment.SpanStructures[0].SpanSegments[1].Id.Should().Be(newSegmentId2);
            newSpanEquipment.SpanStructures[0].SpanSegments[1].FromNodeOfInterestIndex.Should().Be(1);
            newSpanEquipment.SpanStructures[0].SpanSegments[1].ToNodeOfInterestIndex.Should().Be(2);

            // Check that structure 2 has got new node of interest index set
            newSpanEquipment.SpanStructures[1].SpanSegments.Length.Should().Be(1);
            newSpanEquipment.SpanStructures[1].SpanSegments[0].FromNodeOfInterestIndex.Should().Be(0);
            newSpanEquipment.SpanStructures[1].SpanSegments[0].ToNodeOfInterestIndex.Should().Be(2);
        }