public static Parser <Exercise> GetParser(
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                (from name in HeadingParser.GetParser(2)
                 from _ in VerticalSeparationParser.Parser
                 from setAndRepetitionCount in setAndRepetitionCountParser
                 from __ in VerticalSeparationParser.Parser
                 from matchersWithActions in GetMatchersWithActionsParser(audioService, delayService, loggerService, speechService).Optional()
                 select new Exercise(
                     loggerService,
                     speechService,
                     name,
                     setAndRepetitionCount.Item1,
                     setAndRepetitionCount.Item2,
                     matchersWithActions.GetOrElse(Enumerable.Empty <MatcherWithAction>())));
        }
        public static Parser<DoNotAwaitAction> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                from _ in Parse.IgnoreCase("don't")
                from __ in HorizontalWhitespaceParser.Parser.AtLeastOnce()
                from ___ in Parse.IgnoreCase("wait:")
                from ____ in VerticalSeparationParser.Parser.AtLeastOnce()
                from actions in ActionListParser.GetParser(indentLevel + 1, audioService, delayService, loggerService, speechService)
                let child = new SequenceAction(actions)
                select new DoNotAwaitAction(
                    loggerService,
                    child);
        }
        public Exercise(ILoggerService loggerService, ISpeechService speechService, string name, int setCount, int repetitionCount, IEnumerable<MatcherWithAction> matchersWithActions)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));
            name.AssertNotNull(nameof(name));
            matchersWithActions.AssertNotNull(nameof(matchersWithActions));

            if (setCount < 0)
            {
                throw new ArgumentException("setCount cannot be less than zero.", "setCount");
            }

            if (repetitionCount < 0)
            {
                throw new ArgumentException("repetitionCount cannot be less than zero.", "repetitionCount");
            }

            this.logger = loggerService.GetLogger(this.GetType());
            this.speechService = speechService;
            this.name = name;
            this.setCount = setCount;
            this.repetitionCount = repetitionCount;
            this.matchersWithActions = matchersWithActions.ToImmutableList();

            using (var dummyExecutionContext = new ExecutionContext())
            {
                this.duration = this
                    .GetEventsWithActions(dummyExecutionContext)
                    .SelectMany(x => x.Actions)
                    .Select(x => x.Duration)
                    .DefaultIfEmpty()
                    .Aggregate((running, next) => running + next);
            }
        }
Example #4
0
        public Exercise(ILoggerService loggerService, ISpeechService speechService, string name, int setCount, int repetitionCount, IEnumerable <MatcherWithAction> matchersWithActions)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));
            name.AssertNotNull(nameof(name));
            matchersWithActions.AssertNotNull(nameof(matchersWithActions));

            if (setCount < 0)
            {
                throw new ArgumentException("setCount cannot be less than zero.", "setCount");
            }

            if (repetitionCount < 0)
            {
                throw new ArgumentException("repetitionCount cannot be less than zero.", "repetitionCount");
            }

            this.logger              = loggerService.GetLogger(this.GetType());
            this.speechService       = speechService;
            this.name                = name;
            this.setCount            = setCount;
            this.repetitionCount     = repetitionCount;
            this.matchersWithActions = matchersWithActions.ToImmutableList();

            using (var dummyExecutionContext = new ExecutionContext())
            {
                this.duration = this
                                .GetEventsWithActions(dummyExecutionContext)
                                .SelectMany(x => x.Actions)
                                .Select(x => x.Duration)
                                .DefaultIfEmpty()
                                .Aggregate((running, next) => running + next);
            }
        }
Example #5
0
        public static Parser <DoNotAwaitAction> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                (from _ in Parse.IgnoreCase("don't")
                 from __ in HorizontalWhitespaceParser.Parser.AtLeastOnce()
                 from ___ in Parse.IgnoreCase("wait:")
                 from ____ in VerticalSeparationParser.Parser.AtLeastOnce()
                 from actions in ActionListParser.GetParser(indentLevel + 1, audioService, delayService, loggerService, speechService)
                 let child = new SequenceAction(actions)
                             select new DoNotAwaitAction(
                     loggerService,
                     child));
        }
        public static Parser <IAction> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return(BreakActionParser.GetParser(delayService, speechService)
                   .Or <IAction>(MetronomeActionParser.GetParser(audioService, delayService, loggerService))
                   .Or <IAction>(PrepareActionParser.GetParser(delayService, speechService))
                   .Or <IAction>(SayActionParser.GetParser(speechService))
                   .Or <IAction>(WaitActionParser.GetParser(delayService))
                   .Or <IAction>(DoNotAwaitActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService))
                   .Or <IAction>(ParallelActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService))
                   .Or <IAction>(SequenceActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService)));
        }
        public static Parser<IAction> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return BreakActionParser.GetParser(delayService, speechService)
                .Or<IAction>(MetronomeActionParser.GetParser(audioService, delayService, loggerService))
                .Or<IAction>(PrepareActionParser.GetParser(delayService, speechService))
                .Or<IAction>(SayActionParser.GetParser(speechService))
                .Or<IAction>(WaitActionParser.GetParser(delayService))
                .Or<IAction>(DoNotAwaitActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService))
                .Or<IAction>(ParallelActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService))
                .Or<IAction>(SequenceActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService));
        }
        public iCloudExerciseDocumentService(ILoggerService loggerService)
        {
            loggerService.AssertNotNull(nameof(loggerService));

            this.logger = loggerService.GetLogger(this.GetType());
            this.exerciseDocument = new BehaviorSubject<string>(null);
            this.sync = new object();
        }
Example #9
0
        public iCloudExerciseDocumentService(ILoggerService loggerService)
        {
            loggerService.AssertNotNull(nameof(loggerService));

            this.logger           = loggerService.GetLogger(this.GetType());
            this.exerciseDocument = new BehaviorSubject <string>(null);
            this.sync             = new object();
        }
Example #10
0
        public DoNotAwaitAction(ILoggerService loggerService, IAction innerAction)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            innerAction.AssertNotNull(nameof(innerAction));

            this.logger      = loggerService.GetLogger(this.GetType());
            this.innerAction = innerAction;
        }
        public DoNotAwaitAction(ILoggerService loggerService, IAction innerAction)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            innerAction.AssertNotNull(nameof(innerAction));

            this.logger = loggerService.GetLogger(this.GetType());
            this.innerAction = innerAction;
        }
Example #12
0
        public MetronomeAction(IAudioService audioService, IDelayService delayService, ILoggerService loggerService, IEnumerable <MetronomeTick> ticks)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            ticks.AssertNotNull(nameof(ticks));

            this.ticks       = ticks.ToImmutableList();
            this.innerAction = new SequenceAction(GetInnerActions(audioService, delayService, loggerService, this.ticks));
        }
Example #13
0
        public StateService(IBlobCache blobCache, ILoggerService loggerSerive)
        {
            blobCache.AssertNotNull(nameof(blobCache));
            loggerSerive.AssertNotNull(nameof(loggerSerive));

            _blobCache     = blobCache;
            _logger        = loggerSerive.GetLogger(this.GetType());
            _saveCallbacks = new List <Func <IStateService, Task> >();
            _sync          = new object();
        }
Example #14
0
        public StateService(IBlobCache blobCache, ILoggerService loggerService)
        {
            blobCache.AssertNotNull(nameof(blobCache));
            loggerService.AssertNotNull(nameof(loggerService));

            this.blobCache     = blobCache;
            this.logger        = loggerService.GetLogger(this.GetType());
            this.saveCallbacks = ImmutableList <SaveCallback> .Empty;
            this.sync          = new object();
        }
        public MetronomeAction(IAudioService audioService, IDelayService delayService, ILoggerService loggerService, IEnumerable<MetronomeTick> ticks)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            ticks.AssertNotNull(nameof(ticks));

            this.ticks = ticks.ToImmutableList();
            this.innerAction = new SequenceAction(GetInnerActions(audioService, delayService, loggerService, this.ticks));
        }
        public GoogleDriveExerciseDocumentService(
            ILoggerService loggerService,
            IConnectionResultHandler connectionResultHandler)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            connectionResultHandler.AssertNotNull(nameof(connectionResultHandler));

            this.logger = loggerService.GetLogger(this.GetType());
            this.connectionResultHandler = connectionResultHandler;
            this.exerciseDocument = new BehaviorSubject<string>(null);
            this.sync = new object();
            this.connectedDisposable = new SerialDisposable();
        }
        public GoogleDriveExerciseDocumentService(
            ILoggerService loggerService,
            IConnectionResultHandler connectionResultHandler)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            connectionResultHandler.AssertNotNull(nameof(connectionResultHandler));

            this.logger = loggerService.GetLogger(this.GetType());
            this.connectionResultHandler = connectionResultHandler;
            this.exerciseDocument        = new BehaviorSubject <string>(null);
            this.sync = new object();
            this.connectedDisposable = new SerialDisposable();
        }
        public ExerciseProgram(ILoggerService loggerService, string name, IEnumerable <Exercise> exercises)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            name.AssertNotNull(nameof(name));
            exercises.AssertNotNull(nameof(exercises), assertContentsNotNull: true);

            this.logger    = loggerService.GetLogger(this.GetType());
            this.name      = name;
            this.exercises = exercises.ToImmutableList();
            this.duration  = this
                             .exercises
                             .Select(x => x.Duration)
                             .DefaultIfEmpty()
                             .Aggregate((running, next) => running + next);
        }
        public ExerciseProgram(ILoggerService loggerService, string name, IEnumerable<Exercise> exercises)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            name.AssertNotNull(nameof(name));
            exercises.AssertNotNull(nameof(exercises), assertContentsNotNull: true);

            this.logger = loggerService.GetLogger(this.GetType());
            this.name = name;
            this.exercises = exercises.ToImmutableList();
            this.duration = this
                .exercises
                .Select(x => x.Duration)
                .DefaultIfEmpty()
                .Aggregate((running, next) => running + next);
        }
        public static IResult<ExercisePrograms> TryParse(
            string input,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            input.AssertNotNull(nameof(input));
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return ExerciseProgramsParser.GetParser(audioService, delayService, loggerService, speechService).TryParse(input);
        }
        public static Parser <ExerciseProgram> GetParser(
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                (from name in HeadingParser.GetParser(1)
                 from _ in Parse.WhiteSpace.Many()
                 from exercises in ExerciseParser.GetParser(audioService, delayService, loggerService, speechService).DelimitedBy(Parse.WhiteSpace.Many()).Optional()
                 select new ExerciseProgram(loggerService, name, exercises.GetOrElse(Enumerable.Empty <Exercise>())));
        }
        public static Parser <ExercisePrograms> GetParser(
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                (from _ in VerticalSeparationParser.Parser
                 from exercisePrograms in ExerciseProgramParser.GetParser(audioService, delayService, loggerService, speechService).DelimitedBy(Parse.WhiteSpace.Many()).Optional()
                 from __ in Parse.WhiteSpace.Many().End()
                 select new ExercisePrograms(exercisePrograms.GetOrElse(Enumerable.Empty <ExerciseProgram>())));
        }
        public static Parser<ExerciseProgram> GetParser(
                IAudioService audioService,
                IDelayService delayService,
                ILoggerService loggerService,
                ISpeechService speechService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                from name in HeadingParser.GetParser(1)
                from _ in Parse.WhiteSpace.Many()
                from exercises in ExerciseParser.GetParser(audioService, delayService, loggerService, speechService).DelimitedBy(Parse.WhiteSpace.Many()).Optional()
                select new ExerciseProgram(loggerService, name, exercises.GetOrElse(Enumerable.Empty<Exercise>()));
        }
        public static Parser<ExercisePrograms> GetParser(
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                from _ in VerticalSeparationParser.Parser
                from exercisePrograms in ExerciseProgramParser.GetParser(audioService, delayService, loggerService, speechService).DelimitedBy(Parse.WhiteSpace.Many()).Optional()
                from __ in Parse.WhiteSpace.Many().End()
                select new ExercisePrograms(exercisePrograms.GetOrElse(Enumerable.Empty<ExerciseProgram>()));
        }
Example #25
0
        public static Parser <MetronomeAction> GetParser(
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));

            return
                (from _ in Parse.IgnoreCase("metronome")
                 from __ in HorizontalWhitespaceParser.Parser.AtLeastOnce()
                 from ___ in Parse.IgnoreCase("at")
                 from ____ in HorizontalWhitespaceParser.Parser.AtLeastOnce()
                 from ticks in metronomeTickParser.DelimitedBy(Parse.Char(',').Token(HorizontalWhitespaceParser.Parser))
                 select new MetronomeAction(
                     audioService,
                     delayService,
                     loggerService,
                     ticks));
        }
        public static Parser<MetronomeAction> GetParser(
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));

            return
                from _ in Parse.IgnoreCase("metronome")
                from __ in HorizontalWhitespaceParser.Parser.AtLeastOnce()
                from ___ in Parse.IgnoreCase("at")
                from ____ in HorizontalWhitespaceParser.Parser.AtLeastOnce()
                from ticks in metronomeTickParser.DelimitedBy(Parse.Char(',').Token(HorizontalWhitespaceParser.Parser))
                select new MetronomeAction(
                    audioService,
                    delayService,
                    loggerService,
                    ticks);
        }
        public static Parser <ParallelAction> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                (from _ in Parse.IgnoreCase("parallel:")
                 from __ in VerticalSeparationParser.Parser.AtLeastOnce()
                 from actions in ActionListParser.GetParser(indentLevel + 1, audioService, delayService, loggerService, speechService)
                 select new ParallelAction(actions));
        }
        public static Parser<IEnumerable<IAction>> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                (from _ in Parse.String("  ").Or(Parse.String("\t")).Repeat(indentLevel)
                 from __ in Parse.String("* ")
                 from action in ActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService).Token(HorizontalWhitespaceParser.Parser)
                 select action).DelimitedBy(NewLineParser.Parser);
        }
        public static Parser<ParallelAction> GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                from _ in Parse.IgnoreCase("parallel:")
                from __ in VerticalSeparationParser.Parser.AtLeastOnce()
                from actions in ActionListParser.GetParser(indentLevel + 1, audioService, delayService, loggerService, speechService)
                select new ParallelAction(actions);
        }
Example #30
0
        public static Parser <IEnumerable <IAction> > GetParser(
            int indentLevel,
            IAudioService audioService,
            IDelayService delayService,
            ILoggerService loggerService,
            ISpeechService speechService)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel must be greater than or equal to 0.", "indentLevel");
            }

            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            loggerService.AssertNotNull(nameof(loggerService));
            speechService.AssertNotNull(nameof(speechService));

            return
                ((from _ in Parse.String("  ").Or(Parse.String("\t")).Repeat(indentLevel)
                  from __ in Parse.String("* ")
                  from action in ActionParser.GetParser(indentLevel, audioService, delayService, loggerService, speechService).Token(HorizontalWhitespaceParser.Parser)
                  select action).DelimitedBy(NewLineParser.Parser));
        }
Example #31
0
        public ExerciseProgramViewModel(
            ILoggerService loggerService,
            ISchedulerService schedulerService,
            IScreen hostScreen,
            ExerciseProgram model)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            schedulerService.AssertNotNull(nameof(schedulerService));
            hostScreen.AssertNotNull(nameof(hostScreen));
            model.AssertNotNull(nameof(model));

            this.logger           = loggerService.GetLogger(this.GetType());
            this.schedulerService = schedulerService;
            this.model            = model;
            this.hostScreen       = hostScreen;
            this.disposables      = new CompositeDisposable();
            this.exercises        = this.model.Exercises.CreateDerivedCollection(x => new ExerciseViewModel(schedulerService, x, this.WhenAnyValue(y => y.ExecutionContext)));

            this
            .WhenAnyValue(
                x => x.ExecutionContext,
                x => x.ExecutionContext.IsCancelled,
                (ec, isCancelled) => ec != null && !isCancelled)
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.IsStarted = x)
            .AddTo(this.disposables);

            this
            .WhenAnyValue(x => x.ExecutionContext)
            .Select(x => x == null ? Observable.Return(false) : x.WhenAnyValue(y => y.IsPaused))
            .Switch()
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.IsPaused = x)
            .AddTo(this.disposables);

            this
            .WhenAnyValue(x => x.ExecutionContext)
            .Select(x => x == null ? Observable.Return(TimeSpan.Zero) : x.WhenAnyValue(y => y.Progress))
            .Switch()
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.ProgressTimeSpan = x)
            .AddTo(this.disposables);

            this
            .WhenAnyValue(x => x.ProgressTimeSpan)
            .Select(x => x.TotalMilliseconds / this.model.Duration.TotalMilliseconds)
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.Progress = x)
            .AddTo(this.disposables);

            this
            .WhenAnyValue(
                x => x.ExecutionContext,
                x => x.ExecutionContext.CurrentExercise,
                (ec, currentExercise) => ec == null ? null : currentExercise)
            .Select(x => this.Exercises.SingleOrDefault(y => y.Model == x))
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.CurrentExercise = x)
            .AddTo(this.disposables);

            var canStart = this
                           .WhenAnyValue(x => x.IsStarted)
                           .Select(x => !x)
                           .ObserveOn(schedulerService.MainScheduler)
                           .Do(x => System.Diagnostics.Debug.WriteLine("CanStart changing to " + x));

            this.startCommand = ReactiveCommand
                                .CreateAsyncObservable(canStart, this.OnStartAsync, schedulerService.MainScheduler)
                                .AddTo(this.disposables);

            var canPause = this
                           .WhenAnyValue(x => x.IsStarted)
                           .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && !isPaused)
                           .ObserveOn(schedulerService.MainScheduler);

            this.pauseCommand = ReactiveCommand
                                .CreateAsyncObservable(canPause, this.OnPauseAsync, schedulerService.MainScheduler)
                                .AddTo(this.disposables);

            var canResume = this
                            .WhenAnyValue(x => x.IsStarted)
                            .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && isPaused)
                            .ObserveOn(schedulerService.MainScheduler);

            this.resumeCommand = ReactiveCommand
                                 .CreateAsyncObservable(canResume, this.OnResumeAsync, schedulerService.MainScheduler)
                                 .AddTo(this.disposables);

            var canSkipBackwards = this
                                   .WhenAnyValue(
                x => x.ExecutionContext,
                x => x.ProgressTimeSpan,
                (ec, progress) => new { ExecutionContext = ec, Progress = progress })
                                   .Select(x => x.ExecutionContext != null && x.Progress >= skipBackwardsThreshold)
                                   .ObserveOn(schedulerService.MainScheduler);

            this.skipBackwardsCommand = ReactiveCommand
                                        .CreateAsyncObservable(canSkipBackwards, this.OnSkipBackwardsAsync, schedulerService.MainScheduler)
                                        .AddTo(this.disposables);

            var canSkipForwards = this
                                  .WhenAnyValue(
                x => x.ExecutionContext,
                x => x.CurrentExercise,
                (ec, currentExercise) => new { ExecutionContext = ec, CurrentExercise = currentExercise })
                                  .Select(x => x.ExecutionContext != null && x.CurrentExercise != null && x.CurrentExercise != this.exercises.LastOrDefault())
                                  .ObserveOn(schedulerService.MainScheduler);

            this.skipForwardsCommand = ReactiveCommand
                                       .CreateAsyncObservable(canSkipForwards, this.OnSkipForwardsAsync, schedulerService.MainScheduler)
                                       .AddTo(this.disposables);

            this.startCommand
            .CanExecuteObservable
            .Subscribe(x => this.IsStartVisible = x)
            .AddTo(this.disposables);

            this.pauseCommand
            .CanExecuteObservable
            .Subscribe(x => this.IsPauseVisible = x)
            .AddTo(this.disposables);

            this.resumeCommand
            .CanExecuteObservable
            .Subscribe(x => this.IsResumeVisible = x)
            .AddTo(this.disposables);

            this.startCommand
            .ThrownExceptions
            .Subscribe(ex => this.OnThrownException("start", ex))
            .AddTo(this.disposables);

            this.pauseCommand
            .ThrownExceptions
            .Subscribe(ex => this.OnThrownException("pause", ex))
            .AddTo(this.disposables);

            this.resumeCommand
            .ThrownExceptions
            .Subscribe(ex => this.OnThrownException("resume", ex))
            .AddTo(this.disposables);

            this.skipBackwardsCommand
            .ThrownExceptions
            .Subscribe(ex => this.OnThrownException("skip backwards", ex))
            .AddTo(this.disposables);

            this.skipForwardsCommand
            .ThrownExceptions
            .Subscribe(ex => this.OnThrownException("skip forwards", ex))
            .AddTo(this.disposables);

            // we don't use a reactive command here because switching in different commands causes it to get confused and
            // command binding leaves the target button disabled. We could also have not used command binding to get around
            // this problem
            this.playbackCommand = new PlaybackCommandImpl(this);

            // cancel the exercise program if the user navigates away
            this
            .hostScreen
            .Router
            .NavigationStack
            .ItemsRemoved
            .OfType <ExerciseProgramViewModel>()
            .SelectMany(x => x.StopAsync())
            .Subscribe()
            .AddTo(this.disposables);
        }
        public ExerciseProgramViewModel(
            ILoggerService loggerService,
            ISchedulerService schedulerService,
            IScreen hostScreen,
            ExerciseProgram model)
        {
            loggerService.AssertNotNull(nameof(loggerService));
            schedulerService.AssertNotNull(nameof(schedulerService));
            hostScreen.AssertNotNull(nameof(hostScreen));
            model.AssertNotNull(nameof(model));

            this.logger = loggerService.GetLogger(this.GetType());
            this.schedulerService = schedulerService;
            this.model = model;
            this.hostScreen = hostScreen;
            this.disposables = new CompositeDisposable();
            this.exercises = this.model.Exercises.CreateDerivedCollection(x => new ExerciseViewModel(schedulerService, x, this.WhenAnyValue(y => y.ExecutionContext)));

            this
                .WhenAnyValue(
                    x => x.ExecutionContext,
                    x => x.ExecutionContext.IsCancelled,
                    (ec, isCancelled) => ec != null && !isCancelled)
                .ObserveOn(schedulerService.MainScheduler)
                .Subscribe(x => this.IsStarted = x)
                .AddTo(this.disposables);

            this
                .WhenAnyValue(x => x.ExecutionContext)
                .Select(x => x == null ? Observable.Return(false) : x.WhenAnyValue(y => y.IsPaused))
                .Switch()
                .ObserveOn(schedulerService.MainScheduler)
                .Subscribe(x => this.IsPaused = x)
                .AddTo(this.disposables);

            this
                .WhenAnyValue(x => x.ExecutionContext)
                .Select(x => x == null ? Observable.Return(TimeSpan.Zero) : x.WhenAnyValue(y => y.Progress))
                .Switch()
                .ObserveOn(schedulerService.MainScheduler)
                .Subscribe(x => this.ProgressTimeSpan = x)
                .AddTo(this.disposables);

            this
                .WhenAnyValue(x => x.ProgressTimeSpan)
                .Select(x => x.TotalMilliseconds / this.model.Duration.TotalMilliseconds)
                .ObserveOn(schedulerService.MainScheduler)
                .Subscribe(x => this.Progress = x)
                .AddTo(this.disposables);

            this
                .WhenAnyValue(
                    x => x.ExecutionContext,
                    x => x.ExecutionContext.CurrentExercise,
                    (ec, currentExercise) => ec == null ? null : currentExercise)
                .Select(x => this.Exercises.SingleOrDefault(y => y.Model == x))
                .ObserveOn(schedulerService.MainScheduler)
                .Subscribe(x => this.CurrentExercise = x)
                .AddTo(this.disposables);

            var canStart = this
                .WhenAnyValue(x => x.IsStarted)
                .Select(x => !x)
                .ObserveOn(schedulerService.MainScheduler)
                .Do(x => System.Diagnostics.Debug.WriteLine("CanStart changing to " + x));

            this.startCommand = ReactiveCommand
                .CreateAsyncObservable(canStart, this.OnStartAsync, schedulerService.MainScheduler)
                .AddTo(this.disposables);

            var canPause = this
                .WhenAnyValue(x => x.IsStarted)
                .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && !isPaused)
                .ObserveOn(schedulerService.MainScheduler);

            this.pauseCommand = ReactiveCommand
                .CreateAsyncObservable(canPause, this.OnPauseAsync, schedulerService.MainScheduler)
                .AddTo(this.disposables);

            var canResume = this
                .WhenAnyValue(x => x.IsStarted)
                .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && isPaused)
                .ObserveOn(schedulerService.MainScheduler);

            this.resumeCommand = ReactiveCommand
                .CreateAsyncObservable(canResume, this.OnResumeAsync, schedulerService.MainScheduler)
                .AddTo(this.disposables);

            var canSkipBackwards = this
                .WhenAnyValue(
                    x => x.ExecutionContext,
                    x => x.ProgressTimeSpan,
                    (ec, progress) => new { ExecutionContext = ec, Progress = progress })
                .Select(x => x.ExecutionContext != null && x.Progress >= skipBackwardsThreshold)
                .ObserveOn(schedulerService.MainScheduler);

            this.skipBackwardsCommand = ReactiveCommand
                .CreateAsyncObservable(canSkipBackwards, this.OnSkipBackwardsAsync, schedulerService.MainScheduler)
                .AddTo(this.disposables);

            var canSkipForwards = this
                .WhenAnyValue(
                    x => x.ExecutionContext,
                    x => x.CurrentExercise,
                    (ec, currentExercise) => new { ExecutionContext = ec, CurrentExercise = currentExercise })
                .Select(x => x.ExecutionContext != null && x.CurrentExercise != null && x.CurrentExercise != this.exercises.LastOrDefault())
                .ObserveOn(schedulerService.MainScheduler);

            this.skipForwardsCommand = ReactiveCommand
                .CreateAsyncObservable(canSkipForwards, this.OnSkipForwardsAsync, schedulerService.MainScheduler)
                .AddTo(this.disposables);

            this.startCommand
                .CanExecuteObservable
                .Subscribe(x => this.IsStartVisible = x)
                .AddTo(this.disposables);

            this.pauseCommand
                .CanExecuteObservable
                .Subscribe(x => this.IsPauseVisible = x)
                .AddTo(this.disposables);

            this.resumeCommand
                .CanExecuteObservable
                .Subscribe(x => this.IsResumeVisible = x)
                .AddTo(this.disposables);

            this.startCommand
                .ThrownExceptions
                .Subscribe(ex => this.OnThrownException("start", ex))
                .AddTo(this.disposables);

            this.pauseCommand
                .ThrownExceptions
                .Subscribe(ex => this.OnThrownException("pause", ex))
                .AddTo(this.disposables);

            this.resumeCommand
                .ThrownExceptions
                .Subscribe(ex => this.OnThrownException("resume", ex))
                .AddTo(this.disposables);

            this.skipBackwardsCommand
                .ThrownExceptions
                .Subscribe(ex => this.OnThrownException("skip backwards", ex))
                .AddTo(this.disposables);

            this.skipForwardsCommand
                .ThrownExceptions
                .Subscribe(ex => this.OnThrownException("skip forwards", ex))
                .AddTo(this.disposables);

            // we don't use a reactive command here because switching in different commands causes it to get confused and
            // command binding leaves the target button disabled. We could also have not used command binding to get around
            // this problem
            this.playbackCommand = new PlaybackCommandImpl(this);

            // cancel the exercise program if the user navigates away
            this
                .hostScreen
                .Router
                .NavigationStack
                .ItemsRemoved
                .OfType<ExerciseProgramViewModel>()
                .SelectMany(x => x.StopAsync())
                .Subscribe()
                .AddTo(this.disposables);
        }
        public ExerciseProgramsViewModel(
            IAudioService audioService,
            IDelayService delayService,
            IExerciseDocumentService exerciseDocumentService,
            ILoggerService loggerService,
            ISchedulerService schedulerService,
            ISpeechService speechService,
            IStateService stateService,
            IScreen hostScreen,
            ExerciseProgramViewModelFactory exerciseProgramViewModelFactory)
        {
            audioService.AssertNotNull(nameof(audioService));
            delayService.AssertNotNull(nameof(delayService));
            exerciseDocumentService.AssertNotNull(nameof(exerciseDocumentService));
            loggerService.AssertNotNull(nameof(loggerService));
            schedulerService.AssertNotNull(nameof(schedulerService));
            speechService.AssertNotNull(nameof(speechService));
            stateService.AssertNotNull(nameof(stateService));
            hostScreen.AssertNotNull(nameof(hostScreen));
            exerciseProgramViewModelFactory.AssertNotNull(nameof(exerciseProgramViewModelFactory));

            this.exerciseDocumentService = exerciseDocumentService;
            this.stateService            = stateService;
            this.logger      = loggerService.GetLogger(this.GetType());
            this.hostScreen  = hostScreen;
            this.disposables = new CompositeDisposable();

            var documentsFromCache = this
                                     .stateService
                                     .GetAsync <string>(exerciseProgramsCacheKey)
                                     .Where(x => x != null)
                                     .Select(x => new DocumentSourceWith <string>(DocumentSource.Cache, x));

            var documentsFromService = this
                                       .exerciseDocumentService
                                       .ExerciseDocument
                                       .Where(x => x != null)
                                       .Select(x => new DocumentSourceWith <string>(DocumentSource.Service, x));

            var documents = documentsFromCache
                            .Catch((Exception ex) => Observable.Empty <DocumentSourceWith <string> >())
                            .Concat(documentsFromService)
                            .Do(x => this.logger.Debug("Received document from {0}.", x.Source))
                            .Publish();

            var safeDocuments = documents
                                .Catch((Exception ex) => Observable.Empty <DocumentSourceWith <string> >());

            var results = documents
                          .ObserveOn(schedulerService.TaskPoolScheduler)
                          .Select(
                x =>
            {
                IResult <ExercisePrograms> parsedExercisePrograms;

                using (this.logger.Perf("Parsing exercise programs from {0}.", x.Source))
                {
                    parsedExercisePrograms = ExercisePrograms.TryParse(x.Item, audioService, delayService, loggerService, speechService);
                }

                return(new DocumentSourceWith <IResult <ExercisePrograms> >(x.Source, parsedExercisePrograms));
            })
                          .Publish();

            var safeResults = results
                              .Catch((Exception ex) => Observable.Empty <DocumentSourceWith <IResult <ExercisePrograms> > >());

            safeResults
            .Select(x => x.Item.WasSuccessful ? null : x.Item.ToString())
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.ParseErrorMessage = x)
            .AddTo(this.disposables);

            results
            .Select(x => !x.Item.WasSuccessful ? ExerciseProgramsViewModelStatus.ParseFailed : x.Source == DocumentSource.Cache ? ExerciseProgramsViewModelStatus.LoadedFromCache : ExerciseProgramsViewModelStatus.LoadedFromService)
            .Catch((Exception ex) => Observable.Return(ExerciseProgramsViewModelStatus.LoadFailed))
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.Status = x)
            .AddTo(this.disposables);

            safeResults
            .Select(x => x.Item.WasSuccessful ? x.Item.Value : null)
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.Model = x)
            .AddTo(this.disposables);

            this.WhenAnyValue(x => x.Model)
            .Select(x => x == null ? null : x.Programs.CreateDerivedCollection(y => exerciseProgramViewModelFactory(y)))
            .ObserveOn(schedulerService.MainScheduler)
            .Subscribe(x => this.Programs = x)
            .AddTo(this.disposables);

            safeDocuments
            .Where(x => x.Source == DocumentSource.Service)
            .SelectMany(x => this.stateService.SetAsync(exerciseProgramsCacheKey, x.Item))
            .Subscribe()
            .AddTo(this.disposables);

            results
            .Connect()
            .AddTo(this.disposables);

            documents
            .Connect()
            .AddTo(this.disposables);

            this
            .WhenAnyValue(x => x.SelectedProgram)
            .Where(x => x != null)
            .Subscribe(x => this.hostScreen.Router.Navigate.Execute(x))
            .AddTo(this.disposables);

            this
            .hostScreen
            .Router
            .CurrentViewModel
            .OfType <ExerciseProgramsViewModel>()
            .Subscribe(x => x.SelectedProgram = null)
            .AddTo(this.disposables);
        }