public void StartClass([NotNull] CompetitionClassRequirements requirements)
        {
            Guard.NotNull(requirements, nameof(requirements));

            Log.Debug("Entering StartClass.");

            ExecuteExclusiveIfStateIn(CompetitionClassState.Offline, () =>
            {
                modelSnapshot = CacheManager.DefaultInstance.ActiveModel;
                currentCompetitorNumber = modelSnapshot.GetBestStartingCompetitorNumber();

                nextCompetitorNumberTyped = null;
                nextCompetitorNumberGenerated = modelSnapshot.GetBestNextCompetitorNumberOrNull(currentCompetitorNumber);

                classRequirements = requirements;
                runData.Reset(false);

                SetState(CompetitionClassState.SetupCompleted);

                using (var collector = new VisualizationUpdateCollector(visualizer))
                {
                    collector.Include(VisualizationChangeFactory.ClearAll());
                    collector.Include(new ClassInfoUpdate(modelSnapshot.ClassInfo));

                    UpdateCurrentCompetitorVisualization(collector);
                    UpdateNextCompetitorVisualization(collector);

                    CompetitionRunResult previousCompetitorOrNull = modelSnapshot.GetLastCompletedOrNull();
                    collector.Include(new PreviousCompetitorRunUpdate(previousCompetitorOrNull));

                    IReadOnlyCollection<CompetitionRunResult> rankings =
                        modelSnapshot.FilterCompletedAndSortedAscendingByPlacement().Results;
                    collector.Include(new RankingsUpdate(rankings));
                }
            });
        }
        public string TryUpdateRunResult([NotNull] CompetitionRunResult originalRunVersion,
            [NotNull] CompetitionRunResult newRunVersion)
        {
            Guard.NotNull(originalRunVersion, nameof(originalRunVersion));
            Guard.NotNull(newRunVersion, nameof(newRunVersion));

            string result = null;
            ExecuteExclusiveIfStateIn(AllStates, () =>
            {
                // Assumptions:
                // 1] When class is started, underlying cache can never be changed from the outside.
                // 2] When no run active (state Offline), we are not in control of underlying cache 
                //    (so snapshot field should not be read because it is likely outdated).

                CompetitionClassModel activeModelVersion = classState == CompetitionClassState.Offline
                    ? CacheManager.DefaultInstance.ActiveModel
                    : modelSnapshot;
                CompetitionRunResult activeRunVersion =
                    activeModelVersion.GetRunResultOrNull(originalRunVersion.Competitor.Number);

                if (activeRunVersion == null)
                {
                    result = "Competitor not found";
                    return;
                }

                if (!CompetitionRunResult.AreEquivalent(activeRunVersion, originalRunVersion))
                {
                    result = "Competitor changed in-between";
                    return;
                }

                if (classState != CompetitionClassState.Offline &&
                    currentCompetitorNumber == newRunVersion.Competitor.Number)
                {
                    result = "Competitor is running";
                    return;
                }

                CompetitionClassModel newSnapshot =
                    activeModelVersion.ChangeRunResult(newRunVersion).RecalculatePlacements();

                modelSnapshot = CacheManager.DefaultInstance.ReplaceModel(newSnapshot, activeModelVersion);
                AutoExportRunResults();

                if (classState != CompetitionClassState.Offline)
                {
                    using (var collector = new VisualizationUpdateCollector(visualizer))
                    {
                        IReadOnlyCollection<CompetitionRunResult> rankings =
                            modelSnapshot.FilterCompletedAndSortedAscendingByPlacement().Results;
                        collector.Include(new RankingsUpdate(rankings));

                        if (newRunVersion.Competitor.Number == modelSnapshot.LastCompletedCompetitorNumber)
                        {
                            CompetitionRunResult previousCompetitorOrNull = modelSnapshot.GetLastCompletedOrNull();
                            collector.Include(new PreviousCompetitorRunUpdate(previousCompetitorOrNull));
                        }
                    }
                }
            });
            return result;
        }