public static void Single([NotNull] ICompetitionRunVisualizer visualizer, [NotNull] VisualizationChange change)
        {
            Guard.NotNull(visualizer, nameof(visualizer));
            Guard.NotNull(change, nameof(change));

            using (var collector = new VisualizationUpdateCollector(visualizer))
            {
                collector.Include(change);
            }
        }
 private void HandlePlaySoundA()
 {
     ExecuteExclusiveIfStateIn(
         StatesInRange(CompetitionClassState.SetupCompleted, CompetitionClassState.RunCompleted), () =>
         {
             if (modelSnapshot.Alerts.CustomItemA.Sound.EffectivePath != null)
             {
                 using (var collector = new VisualizationUpdateCollector(visualizer))
                 {
                     collector.Include(new PlaySound(modelSnapshot.Alerts.CustomItemA.Sound.EffectivePath));
                 }
             }
         });
 }
 private void HandleMuteSound()
 {
     ExecuteExclusiveIfStateIn(
         StatesInRange(CompetitionClassState.SetupCompleted, CompetitionClassState.RunCompleted), () =>
         {
             using (var collector = new VisualizationUpdateCollector(visualizer))
             {
                 collector.Include(PlaySound.Mute);
             }
         });
 }
        private void ClockSynchronizationMonitorOnSyncCompleted([CanBeNull] object sender,
            [NotNull] ClockSynchronizationCompletedEventArgs e)
        {
            ExecuteExclusiveIfStateIn(CompetitionClassState.WaitingForSync, () =>
            {
                using (var collector = new VisualizationUpdateCollector(visualizer))
                {
                    if (e.Result == ClockSynchronizationResult.Succeeded)
                    {
                        collector.Include(new ClockSynchronizationUpdate(ClockSynchronizationMode.Normal));

                        if (modelSnapshot.Alerts.ReadyToStart.Sound.EffectivePath != null)
                        {
                            collector.Include(new PlaySound(modelSnapshot.Alerts.ReadyToStart.Sound.EffectivePath));
                        }

                        ClearCurrentRunOrShowExistingRun(collector);
                    }
                    else
                    {
                        GoOffline(collector);
                    }
                }
            });
        }
        private void HandleChangeFaults(bool isIncrement)
        {
            ExecuteExclusiveIfStateIn(
                StatesInRange(CompetitionClassState.ReadyToStart, CompetitionClassState.FinishPassed), () =>
                {
                    if ((isIncrement &&
                        runData.FaultCount <= CompetitionRunResult.MaxFaultValue - CompetitionRunResult.FaultStepSize) ||
                        (!isIncrement && runData.FaultCount > 0))
                    {
                        int stepSize = isIncrement
                            ? CompetitionRunResult.FaultStepSize
                            : -CompetitionRunResult.FaultStepSize;
                        int faultCount = runData.FaultCount + stepSize;
                        runData.FaultCount = faultCount;
                    }

                    using (var collector = new VisualizationUpdateCollector(visualizer))
                    {
                        runData.HideExistingRunResultIfAny(collector);

                        collector.Include(new FaultCountUpdate(runData.FaultCount));
                    }
                });
        }
        private void HandlePassStart([NotNull] RecordedTime passageTime)
        {
            ExecuteExclusiveIfStateIn(CompetitionClassState.ReadyToStart, () =>
            {
                runData.Timings = new CompetitionRunTimings(passageTime);
                SetState(CompetitionClassState.StartPassed);

                runData.EliminationTracker.StartMonitorCourseTime(modelSnapshot.ClassInfo.MaximumCourseTime);

                using (var collector = new VisualizationUpdateCollector(visualizer))
                {
                    runData.HideExistingRunResultIfAny(collector);

                    collector.Include(new StartPrimaryTimer());
                }
            });
        }
        private void HandlePassFinish([NotNull] RecordedTime passageTime)
        {
            ExecuteExclusiveIfStateIn(
                StatesInRange(CompetitionClassState.StartPassed, CompetitionClassState.Intermediate3Passed), () =>
                {
                    using (var collector = new VisualizationUpdateCollector(visualizer))
                    {
                        CompetitionRunTimings timingsNotNull = AssertRunDataTimingsNotNull();

                        runData.Timings = timingsNotNull.ChangeFinishTime(passageTime);
                        SetState(CompetitionClassState.FinishPassed);

                        TimeSpanWithAccuracy elapsed = passageTime.ElapsedSince(runData.Timings.StartTime);
                        Log.Info($"Passed Finish at {elapsed}.");

                        runData.EliminationTracker.StopMonitorCourseTime();
                        collector.Include(PrimaryTimeStopAndSet.FromTimeSpanWithAccuracy(elapsed));
                    }
                });
        }
        public void CompleteCompetitorSelection(bool isCurrentCompetitor, [CanBeNull] int? competitorNumber)
        {
            Log.Debug(
                $"Entering CompleteCompetitorSelection with number {competitorNumber} for {CurrentOrNext(isCurrentCompetitor)} competitor.");

            ICollection<CompetitionClassState> statesAllowed = isCurrentCompetitor
                ? StatesInRange(CompetitionClassState.SetupCompleted, CompetitionClassState.FinishPassed)
                : StatesInRange(CompetitionClassState.SetupCompleted, CompetitionClassState.RunCompleted);

            ExecuteExclusiveIfStateIn(statesAllowed, () =>
            {
                using (var collector = new VisualizationUpdateCollector(visualizer))
                {
                    if (competitorNumber != null)
                    {
                        if (isCurrentCompetitor)
                        {
                            if (modelSnapshot.Results.Any(r => r.Competitor.Number == competitorNumber.Value))
                            {
                                currentCompetitorNumber = competitorNumber.Value;

                                if (nextCompetitorNumberTyped == null ||
                                    nextCompetitorNumberTyped.Value == currentCompetitorNumber.Value)
                                {
                                    nextCompetitorNumberTyped = null;
                                    nextCompetitorNumberGenerated =
                                        modelSnapshot.GetBestNextCompetitorNumberOrNull(currentCompetitorNumber);

                                    UpdateNextCompetitorVisualization(collector);
                                }
                            }

                            UpdateCurrentCompetitorVisualization(collector);

                            if (classState == CompetitionClassState.ReadyToStart)
                            {
                                ClearCurrentRunOrShowExistingRun(collector);
                            }
                        }
                        else if (
                            modelSnapshot.Results.Any(
                                r =>
                                    r.Competitor.Number == competitorNumber.Value &&
                                        (currentCompetitorNumber == null ||
                                            r.Competitor.Number != currentCompetitorNumber.Value)))
                        {
                            nextCompetitorNumberTyped = competitorNumber.Value;
                        }

                        UpdateNextCompetitorVisualization(collector);
                    }

                    collector.Include(VisualizationChangeFactory.CompetitorNumberBlinkOff(isCurrentCompetitor));
                }
            });
        }
        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;
        }
        private void EliminationTrackerOnRefusalCountChanged([CanBeNull] object sender, [NotNull] EventArgs<int> e)
        {
            ExecuteExclusiveIfStateIn(AllStates, () =>
            {
                using (var collector = new VisualizationUpdateCollector(visualizer))
                {
                    runData.HideExistingRunResultIfAny(collector);

                    collector.Include(new RefusalCountUpdate(e.Argument));
                }
            });
        }
        private void EliminationTrackerOnEliminationChanged([CanBeNull] object sender, [NotNull] EliminationEventArgs e)
        {
            ExecuteExclusiveIfStateIn(AllStates, () =>
            {
                using (var collector = new VisualizationUpdateCollector(visualizer))
                {
                    runData.HideExistingRunResultIfAny(collector);

                    collector.Include(new EliminationUpdate(e.IsEliminated));

                    if (e.IsEliminated)
                    {
                        if (modelSnapshot.Alerts.Eliminated.Picture.EffectiveItem != null)
                        {
                            collector.Include(new StartAnimation(modelSnapshot.Alerts.Eliminated.Picture.EffectiveItem));
                        }
                        if (modelSnapshot.Alerts.Eliminated.Sound.EffectivePath != null)
                        {
                            collector.Include(new PlaySound(modelSnapshot.Alerts.Eliminated.Sound.EffectivePath));
                        }
                    }
                }
            });
        }