public void Update()
        {
            Log._Trace($"TrafficLightSimulation.Update(): called for node {nodeId}");

            if (IsTimedLight())
            {
                timedLight.OnGeometryUpdate();
                timedLight.Housekeeping();
            }
        }
        public bool LoadData(List <Configuration.TimedTrafficLights> data)
        {
            bool success = true;

            Log.Info($"Loading {data.Count} timed traffic lights (new method)");

            var nodesWithSimulation = new HashSet <ushort>();

            foreach (Configuration.TimedTrafficLights cnfTimedLights in data)
            {
                nodesWithSimulation.Add(cnfTimedLights.nodeId);
            }

            var masterNodeIdBySlaveNodeId = new Dictionary <ushort, ushort>();
            var nodeGroupByMasterNodeId   = new Dictionary <ushort, List <ushort> >();

            foreach (Configuration.TimedTrafficLights cnfTimedLights in data)
            {
                try {
                    // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed
                    // enforce uniqueness of node ids
                    List <ushort> currentNodeGroup = cnfTimedLights.nodeGroup.Distinct().ToList();

                    if (!currentNodeGroup.Contains(cnfTimedLights.nodeId))
                    {
                        currentNodeGroup.Add(cnfTimedLights.nodeId);
                    }

                    // remove any nodes that are not configured to have a simulation
                    currentNodeGroup = new List <ushort>(
                        currentNodeGroup.Intersect(nodesWithSimulation));

                    // remove invalid nodes from the group; find if any of the nodes in the group is already a master node
                    ushort masterNodeId     = 0;
                    int    foundMasterNodes = 0;

                    for (int i = 0; i < currentNodeGroup.Count;)
                    {
                        ushort nodeId = currentNodeGroup[i];

                        if (!Services.NetService.IsNodeValid(currentNodeGroup[i]))
                        {
                            currentNodeGroup.RemoveAt(i);
                            continue;
                        }

                        if (nodeGroupByMasterNodeId.ContainsKey(nodeId))
                        {
                            // this is a known master node
                            if (foundMasterNodes > 0)
                            {
                                // we already found another master node. ignore this node.
                                currentNodeGroup.RemoveAt(i);
                                continue;
                            }

                            // we found the first master node
                            masterNodeId = nodeId;
                            ++foundMasterNodes;
                        }

                        ++i;
                    }

                    if (masterNodeId == 0)
                    {
                        // no master node defined yet, set the first node as a master node
                        masterNodeId = currentNodeGroup[0];
                    }

                    // ensure the master node is the first node in the list (TimedTrafficLights
                    // depends on this at the moment...)
                    currentNodeGroup.Remove(masterNodeId);
                    currentNodeGroup.Insert(0, masterNodeId);

                    // update the saved node group and master-slave info
                    nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup;

                    foreach (ushort nodeId in currentNodeGroup)
                    {
                        masterNodeIdBySlaveNodeId[nodeId] = masterNodeId;
                    }
                } catch (Exception e) {
                    Log.WarningFormat(
                        "Error building timed traffic light group for TimedNode {0} (NodeGroup: {1}): {2}",
                        cnfTimedLights.nodeId,
                        string.Join(", ", cnfTimedLights.nodeGroup.Select(x => x.ToString()).ToArray()),
                        e);
                    success = false;
                }
            }

            foreach (Configuration.TimedTrafficLights cnfTimedLights in data)
            {
                try {
                    if (!masterNodeIdBySlaveNodeId.ContainsKey(cnfTimedLights.nodeId))
                    {
                        continue;
                    }

                    ushort        masterNodeId = masterNodeIdBySlaveNodeId[cnfTimedLights.nodeId];
                    List <ushort> nodeGroup    = nodeGroupByMasterNodeId[masterNodeId];

#if DEBUGLOAD
                    Log._Debug($"Adding timed light at node {cnfTimedLights.nodeId}. NodeGroup: " +
                               $"{string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}");
#endif
                    SetUpTimedTrafficLight(cnfTimedLights.nodeId, nodeGroup);

                    int j = 0;
                    foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps)
                    {
#if DEBUGLOAD
                        Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}");
#endif
                        ITimedTrafficLightsStep step =
                            TrafficLightSimulations[cnfTimedLights.nodeId].timedLight.AddStep(
                                cnfTimedStep.minTime,
                                cnfTimedStep.maxTime,
                                (StepChangeMetric)cnfTimedStep.changeMetric,
                                cnfTimedStep.waitFlowBalance);

                        foreach (KeyValuePair <ushort, Configuration.CustomSegmentLights> e in
                                 cnfTimedStep.segmentLights)
                        {
                            if (!Services.NetService.IsSegmentValid(e.Key))
                            {
                                continue;
                            }

                            e.Value.nodeId = cnfTimedLights.nodeId;

#if DEBUGLOAD
                            Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}");
#endif
                            ICustomSegmentLights lights = null;
                            if (!step.CustomSegmentLights.TryGetValue(e.Key, out lights))
                            {
#if DEBUGLOAD
                                Log._Debug($"No segment lights found at timed step {j} for segment " +
                                           $"{e.Key}, node {cnfTimedLights.nodeId}");
#endif
                                continue;
                            }

                            Configuration.CustomSegmentLights cnfLights = e.Value;

#if DEBUGLOAD
                            Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: " +
                                       $"{cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}");
#endif
                            lights.ManualPedestrianMode = cnfLights.manualPedestrianMode;
                            lights.PedestrianLightState = cnfLights.pedestrianLightState;

                            bool first = true; // v1.10.2 transitional code
                            foreach (KeyValuePair <ExtVehicleType, Configuration.CustomSegmentLight> e2
                                     in cnfLights.customLights)
                            {
#if DEBUGLOAD
                                Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType " +
                                           $"{e2.Key} at node {cnfTimedLights.nodeId}");
#endif
                                if (!lights.CustomLights.TryGetValue(
                                        LegacyExtVehicleType.ToNew(e2.Key),
                                        out ICustomSegmentLight light))
                                {
#if DEBUGLOAD
                                    Log._Debug($"No segment light found for timed step {j}, segment " +
                                               $"{e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}");
#endif
                                    // v1.10.2 transitional code START
                                    if (first)
                                    {
                                        first = false;
                                        if (!lights.CustomLights.TryGetValue(
                                                CustomSegmentLights
                                                .DEFAULT_MAIN_VEHICLETYPE,
                                                out light))
                                        {
#if DEBUGLOAD
                                            Log._Debug($"No segment light found for timed step {j}, " +
                                                       $"segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} " +
                                                       $"at node {cnfTimedLights.nodeId}");
#endif
                                            continue;
                                        }
                                    }
                                    else
                                    {
                                        // v1.10.2 transitional code END
                                        continue;

                                        // v1.10.2 transitional code START
                                    }

                                    // v1.10.2 transitional code END
                                }

                                Configuration.CustomSegmentLight cnfLight = e2.Value;

                                light.InternalCurrentMode = (LightMode)cnfLight.currentMode; // TODO improve & remove
                                light.SetStates(
                                    cnfLight.mainLight,
                                    cnfLight.leftLight,
                                    cnfLight.rightLight,
                                    false);
                            }
                        }

                        ++j;
                    }
                } catch (Exception e) {
                    // ignore, as it's probably corrupt save data. it'll be culled on next save
                    Log.Warning($"Error loading data from TimedNode (new method): {e}");
                    success = false;
                }
            }

            foreach (Configuration.TimedTrafficLights cnfTimedLights in data)
            {
                try {
                    ITimedTrafficLights timedNode =
                        TrafficLightSimulations[cnfTimedLights.nodeId].timedLight;

                    timedNode.Housekeeping();
                    if (cnfTimedLights.started)
                    {
                        timedNode.Start(cnfTimedLights.currentStep);
                    }
                } catch (Exception e) {
                    Log.Warning($"Error starting timed light @ {cnfTimedLights.nodeId}: {e}");
                    success = false;
                }
            }

            return(success);
        }
 public void Housekeeping()
 {
     // TODO improve & remove
     timedLight?.Housekeeping(); // removes unused step lights
 }