internal void Initialize(string intersectionId)
        {
            // Check unicity
            var ids = new List <List <TLCObjectBase> >
            {
                new List <TLCObjectBase>(InternalSignalGroups),
                new List <TLCObjectBase>(InternalDetectors),
                new List <TLCObjectBase>(InternalInputs),
                new List <TLCObjectBase>(InternalOutputs),
                new List <TLCObjectBase>(InternalVariables)
            };

            if (InternalIntersections.Count == 0)
            {
                throw new TLCFISessionException("No intersections are present in the collected data; cannot initialize StateManager.", true);
            }
            foreach (var l in ids)
            {
                foreach (var item1 in l)
                {
                    if (l.Any(item2 => item1 != item2 && item1.Id == item2.Id))
                    {
                        throw new DuplicateNameException($"Found duplicate IDs: type {item1.ObjectType}, id {item1.Id}. " +
                                                         "All Ids in TLC config must be unique per type.");
                    }
                }
            }

            // Build complete list
            if (Facilities != null)
            {
                _staticObjects.Add("_f_" + Facilities.Id, Facilities);
            }
            if (SpvhGenerator != null)
            {
                _staticObjects.Add("_sp_" + SpvhGenerator.Id, SpvhGenerator);
            }
            foreach (var sg in InternalSignalGroups)
            {
                sg.ChangedState += (o, e) =>
                {
                    if (RequestedStates.TryGetValue("sg" + sg.Id, out ulong ticks))
                    {
                        // note: wrapping around uint.MaxValue goes by itself in C#
                        _requestedStatesTimings.Enqueue((int)(TicksGenerator.Default.GetCurrentTicks() - ticks));
                        if (_requestedStatesTimings.Count > 50)
                        {
                            _requestedStatesTimings.Dequeue();
                        }
                        AvgResponseToRequestsTime = _requestedStatesTimings.Sum() / (double)_requestedStatesTimings.Count;
                        RequestedStates.Remove("sg" + sg.Id);
                    }
                    SignalGroupStateChanged?.Invoke(this, sg);
                };
                sg.ChangedPredictions += (o, e) =>
                {
                    if (RequestedStates.TryGetValue("pr" + sg.Id, out ulong ticks))
                    {
                        // note: wrapping around uint.MaxValue goes by itself in C#
                        _requestedStatesTimings.Enqueue((int)(TicksGenerator.Default.GetCurrentTicks() - ticks));
                        if (_requestedStatesTimings.Count > 50)
                        {
                            _requestedStatesTimings.Dequeue();
                        }
                        AvgResponseToRequestsTime = _requestedStatesTimings.Sum() / (double)_requestedStatesTimings.Count;
                        RequestedStates.Remove("pr" + sg.Id);
                    }
                    SignalGroupPredictionsChanged?.Invoke(this, sg);
                };
                _staticObjects.Add("_sg_" + sg.Id, sg);
            }
            foreach (var d in InternalDetectors)
            {
                d.ChangedState += (o, e) => { DetectorStateChanged?.Invoke(this, d); };
                _staticObjects.Add("_d_" + d.Id, d);
            }
            foreach (var i in InternalInputs)
            {
                i.ChangedState += (o, e) => { InputStateChanged?.Invoke(this, i); };
                _staticObjects.Add("_i_" + i.Id, i);
            }
            foreach (var o in InternalOutputs)
            {
                o.ChangedState += (o2, e) =>
                {
                    if (RequestedStates.TryGetValue("os" + o.Id, out ulong ticks))
                    {
                        // note: wrapping around uint.MaxValue goes by itself in C#
                        _requestedStatesTimings.Enqueue((int)(TicksGenerator.Default.GetCurrentTicks() - ticks));
                        if (_requestedStatesTimings.Count > 50)
                        {
                            _requestedStatesTimings.Dequeue();
                        }
                        AvgResponseToRequestsTime = _requestedStatesTimings.Sum() / (double)_requestedStatesTimings.Count;
                        RequestedStates.Remove("os" + o.Id);
                    }
                    OutputStateChanged?.Invoke(this, o);
                };
                _staticObjects.Add("_o_" + o.Id, o);

                // Set exclusive: if an output belongs to an intersection, it is exclusive
                if (InternalIntersections.SelectMany(x => x.Outputs).Any(x => x == o.Id))
                {
                    o.Exclusive = true;
                }
            }
            foreach (var v in InternalVariables)
            {
                v.ChangedState += (o, e) => { VariableChanged?.Invoke(this, v); };
                v.ChangedState += (o2, e) =>
                {
                    if (RequestedStates.TryGetValue("va" + v.Id, out ulong ticks))
                    {
                        // note: wrapping around uint.MaxValue goes by itself in C#
                        _requestedStatesTimings.Enqueue((int)(TicksGenerator.Default.GetCurrentTicks() - ticks));
                        if (_requestedStatesTimings.Count > 50)
                        {
                            _requestedStatesTimings.Dequeue();
                        }
                        AvgResponseToRequestsTime = _requestedStatesTimings.Sum() / (double)_requestedStatesTimings.Count;
                        RequestedStates.Remove("va" + v.Id);
                    }
                    VariableChanged?.Invoke(this, v);
                };
                _staticObjects.Add("_v_" + v.Id, v);
            }
            foreach (var i in InternalIntersections)
            {
                _staticObjects.Add("_int_" + i.Id, i);
                i.ChangedState += (o, e) => { IntersectionStateChanged?.Invoke(this, i); };
                if (i.Id == intersectionId)
                {
                    Intersection = i;
                }
            }

            // Initialize properties
            StaticObjects  = new ReadOnlyDictionary <string, object>(_staticObjects);
            DynamicObjects = new Dictionary <string, object>();
            Intersections  = new ReadOnlyCollection <Intersection>(InternalIntersections);
            SignalGroups   = new ReadOnlyCollection <SignalGroup>(InternalSignalGroups);
            Detectors      = new ReadOnlyCollection <Detector>(InternalDetectors);
            Inputs         = new ReadOnlyCollection <Input>(InternalInputs);
            Outputs        = new ReadOnlyCollection <Output>(InternalOutputs);
            Variables      = new ReadOnlyCollection <Variable>(InternalVariables);

            _logger.Info("Initializing data from remote TLC completed. TLC has:");
            _logger.Info("  - {0} intersections", InternalIntersections.Count);
            _logger.Info("  - {0} signalgroups", InternalSignalGroups.Count);
            _logger.Info("  - {0} detectors", InternalDetectors.Count);
            _logger.Info("  - {0} outputs", InternalOutputs.Count);
            _logger.Info("  - {0} inputs", InternalInputs.Count);
            _logger.Info("  - {0} variables", InternalVariables.Count);
            _logger.Info("  - {0} spvehiclegens", SpvhGenerator == null ? 0 : 1);
            foreach (var i in InternalIntersections)
            {
                _logger.Info("  Intersection {0} has:", i.Id);
                _logger.Info("    - {0} signalgroups", i.Signalgroups.Length);
                _logger.Info("    - {0} detectors", i.Detectors.Length);
                _logger.Info("    - {0} outputs", i.Outputs.Length);
                _logger.Info("    - {0} inputs", i.Inputs.Length);
                _logger.Info("    - {0} spvehiclegens", i.Spvehgenerator == null ? 0 : 1);
            }
        }
 internal void OnSignalGroupStateChanged(object sender, EventArgs e)
 {
     SignalGroupStateChanged?.Invoke(this, sender as SignalGroupModel);
 }