protected override async Task InitializeForNewStudySessionAsync(
            MultiLineTextList multiLineTexts)
        {
            #region DisableNav/Thinking
            DisableNavigationRequestedEventMessage.Publish();
            var targetId = Guid.NewGuid();
            History.Events.ThinkingAboutTargetEvent.Publish(targetId);
            #endregion
            try
            {
                #region ThinkPing
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
                #endregion
                var retrieverStudyData = await Business.StudyDataRetriever.CreateNewAsync();

                _CurrentUserNativeLanguageText = retrieverStudyData.StudyData.NativeLanguageText;
                #region ThinkPing
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
                #endregion
                var retrieverRecentBeliefs = await MostRecentPhraseDataBeliefsRetriever.CreateNewAsync();

                _MostRecentPhraseBeliefsCache = retrieverRecentBeliefs.MostRecentPhraseBeliefs;
                #region ThinkPing
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
                #endregion
            }
            finally
            {
                #region EnableNav/Thinked
                EnableNavigationRequestedEventMessage.Publish();
                History.Events.ThinkedAboutTargetEvent.Publish(targetId);
                #endregion
            }
        }
        /// <summary>
        /// Unregistered means not registered in either beliefs from cache or pseudo beliefs from cache.
        /// </summary>
        /// <param name="multiLineTextList"></param>
        /// <param name="unknownBeliefsFromCache"></param>
        /// <param name="unknownPseudoBeliefsFromCache"></param>
        /// <returns></returns>
        private List <PhraseEdit> GetUnregisteredUnknownPhraseBeliefs(MultiLineTextList multiLineTextList,
                                                                      List <PhraseBeliefEdit> unknownBeliefsFromCache, List <KeyValuePair <string, Tuple <string, double> > > unknownPseudoBeliefsFromCache)
        {
            List <PhraseEdit> retPhrases = new List <PhraseEdit>();

            foreach (var mlt in multiLineTextList)
            {
                var phrasesInNeitherCache = from line in mlt.Lines
                                            where //LINE IS NOT IN EITHER CACHE (THUS EXCLUDING ASSOCIATED UNKNOWN BELIEFS)
                                            (!(from keyIsPhraseText in unknownPseudoBeliefsFromCache
                                               select keyIsPhraseText.Key).Union(
                                                 from belief in unknownBeliefsFromCache
                                                 select belief.Phrase.Text).Contains(line.Phrase.Text))

                                            &&

                                            //AND PHRASE HAS NO ASSOCIATED BELIEF (THUS EXCLUDING KNOWN BELIEFS)
                                            (!(from keyIsPhraseText in _PseudoBeliefsCache
                                               select keyIsPhraseText.Key).Union(
                                                 from belief in _MostRecentPhraseBeliefsCache
                                                 select belief.Phrase.Text).Contains(line.Phrase.Text))
                                            select line.Phrase;
                retPhrases.AddRange(phrasesInNeitherCache);
            }

            return(retPhrases);
        }
        public async Task GET()
        {
            Guid testId = Guid.Empty;
            MultiLineTextEdit multiLineTextEdit = null;
            var isAsyncComplete = false;
            var hasError        = false;

            EnqueueConditional(() => isAsyncComplete);
            await Setup();

            try
            {
                var allMultiLineTexts = await MultiLineTextList.GetAllAsync();

                testId            = allMultiLineTexts.First().Id;
                multiLineTextEdit = await MultiLineTextEdit.GetMultiLineTextEditAsync(testId);
            }
            catch
            {
                hasError = true;
            }
            finally
            {
                EnqueueCallback(
                    () => Assert.IsFalse(hasError),
                    () => Assert.IsNotNull(multiLineTextEdit),
                    () => Assert.IsTrue(multiLineTextEdit.Lines.Count >= 2),
                    () => Assert.AreEqual(testId, multiLineTextEdit.Id)
                    );

                EnqueueTestComplete();
                Teardown();
                isAsyncComplete = true;
            }
        }
        private bool PhraseTextIsInMultiLineTextList(string phraseText, MultiLineTextList multiLineTextList)
        {
            var exists = (from mlt in multiLineTextList
                          where (from line in mlt.Lines
                                 where line.Phrase.Text == phraseText
                                 select line).Count() > 0
                          select mlt).Count() > 0;

            return(exists);
        }
        /// <summary>
        /// Simply gets the phrase from a random line from any of the MultiLineTexts
        /// in the given multiLineTextList parameter.
        /// </summary>
        /// <returns></returns>
        private PhraseEdit GetRandomPhrase(MultiLineTextList multiLineTextList)
        {
            var relevantBeliefs =
                PhraseBeliefList.GetBeliefsAboutPhrasesInMultiLineTextsAsync(multiLineTextList);

            var randomMlt  = RandomPicker.Ton.PickOne <MultiLineTextEdit>(multiLineTextList);
            var randomLine = RandomPicker.Ton.PickOne <LineEdit>(randomMlt.Lines);

            return(randomLine.Phrase);
        }
        /// <summary>
        /// This method is called when the user presses the Go button.
        /// </summary>
        public async Task Go()
        {
            var ids = new MobileList <Guid>();

            foreach (var songViewModel in Items)
            {
                if (songViewModel.IsChecked)
                {
                    ids.Add(songViewModel.Model.Id);
                }
            }

            //var targetId = new Guid(@"5D4355FE-C46E-4AA1-9E4A-45288C341C44");
            var targetId = Guid.NewGuid();

            History.Events.ThinkingAboutTargetEvent.Publish(targetId);
            var songs = await MultiLineTextList.NewMultiLineTextListAsync(ids);

            History.Events.ThinkedAboutTargetEvent.Publish(targetId);

            var nativeLanguage = await LanguageEdit.GetLanguageEditAsync(GetNativeLanguageText());

            var noExpirationDate = StudyJobInfo <MultiLineTextList, IViewModelBase> .NoExpirationDate;
            var precision        = double.Parse(AppResources.DefaultExpectedPrecision);

            //CREATE JOB INFO
            var studyJobInfo = new StudyJobInfo <MultiLineTextList, IViewModelBase>(songs,
                                                                                    nativeLanguage,
                                                                                    noExpirationDate,
                                                                                    precision);
            //CREATE OPPORTUNITY
            var opportunity = new Opportunity <MultiLineTextList, IViewModelBase>(Id,
                                                                                  this,
                                                                                  studyJobInfo,
                                                                                  StudyResources.CategoryStudy);

            //ADD OPPORTUNITY TO OUR FUTURE OPPORTUNITIES
            FutureOpportunities.Add(opportunity);

            //LET THE HISTORY SHOW THAT YOU ARE THINKING ABOUT THIS OPPORTUNITY
            var opportunityId = opportunity.Id;

            History.Events.ThinkingAboutTargetEvent.Publish(opportunityId);

            //PUBLISH THE OPPORTUNITY
            Exchange.Ton.Publish(opportunity);

            //NOW, WE WAIT UNTIL WE HEAR A HANDLE(OFFER) MESSAGE.
            //TODO: TIMEOUT FOR OPPORTUNITY, BOTH EXPIRATION DATE AND WAITING FOR OFFER TIMEOUT
        }
Example #7
0
 protected abstract Task InitializeForNewStudySessionAsync(MultiLineTextList multiLineTexts);
Example #8
0
        protected virtual async Task StudyAsync(MultiLineTextList multiLineTexts)
        {
            _AbortIsFlagged = false;
            StudyItemViewModelBase contentVM  = null;
            IFeedbackViewModelBase feedbackVM = _ViewModel.FeedbackViewModel;

            ///INITIALIZE HISTORY PUBLISHER
            History.HistoryPublisher.Ton.PublishEvent(new StartingStudySessionEvent());


            ///OKAY, SO AT THIS POINT, WE HAVE DONE OUR WORK THROUGH THE EXCHANGE.
            ///THE CALLER HAS A REFERENCE TO OUR _VIEWMODEL PROPERTY.  WE HAVE CONTROL
            ///OF THE STUDY PROCESS THROUGH THIS _VIEWMODEL PROPERTY.  WE USE TWO SUB VIEWMODELS
            ///AT THIS POINT: STUDYITEM VIEWMODEL, AND FEEDBACK VIEWMODEL.  WE WILL IGNORE TIMEOUTS
            ///TO SIMPLIFY THINGS.  WE JUST NEED TO DO A FEW THINGS:
            ///1) INITIALIZE FOR OUR STUDY SESSION
            ///2) GET NEXT/ ASSIGN _VIEWMODEL.STUDYITEMVIEWMODEL AND _VIEWMODEL.FEEDBACKVIEWMODEL.
            ///3) SHOW STUDYITEMVIEWMODEL.
            ///4) WHEN SHOW IS DONE, ENABLE FEEDBACK VIEWMODEL AND GET FEEDBACK


            #region Thinking (try..)
            var targetId = Guid.NewGuid();
            History.Events.ThinkingAboutTargetEvent.Publish(targetId);
            try
            {
                #endregion
                ///1) INITIALIZE FOR OUR STUDY SESSION
                await InitializeForNewStudySessionAsync(multiLineTexts);

                #region (...finally) Thinked
            }
            finally
            {
                History.Events.ThinkedAboutTargetEvent.Publish(targetId);
            }
            #endregion

            //STUDY SESSION LOOP
            while (!_CompleteIsFlagged && !_AbortIsFlagged)
            {
                //BEGINNING OF A SINGLE STUDY ITEM
                _IsStudying = true;
                _ViewModel.FeedbackViewModel.IsEnabled = false;

                ///2) GET NEXT/ ASSIGN _VIEWMODEL.STUDYITEMVIEWMODEL AND _VIEWMODEL.FEEDBACKVIEWMODEL.
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
                //STUDY ITEM VIEWMODEL CONTENT
                _ViewModel.StudyItemViewModel = await GetNextStudyItemViewModelAsync();

                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);

                contentVM = _ViewModel.StudyItemViewModel;
                if (contentVM == null)
                {
                    //IF WE CAN'T GET A NEW STUDY ITEM VIEWMODEL, THEN THE STUDY SESSION IS COMPLETED.
                    _CompleteIsFlagged = true;
                    break;
                }

                #region Abort Check
                if (_AbortIsFlagged)
                {
                    _IsStudying = false;
                    break;
                }
                #endregion

                ///3) SHOW STUDY ITEM VIEWMODEL.
                ///   DO *NOT* THINK BEFORE AFTER, CAUSE THIS IS WAITING FOR USER INPUT
                await contentVM.ShowAsync();

                #region Abort Check
                if (_AbortIsFlagged)
                {
                    _IsStudying = false;
                    break;
                }
                #endregion

                ///4) WHEN SHOW IS DONE, GET FEEDBACK
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
                var feedbackTimeout = int.Parse(StudyResources.DefaultFeedbackTimeoutMilliseconds);
                var feedback        = await feedbackVM.GetFeedbackAsync(feedbackTimeout);

                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);

                //RIGHT NOW I'M NOT DOING ANYTHING WITH THIS FEEDBACK, AS IT IS PUBLISHED WHEN
                //IT IS CREATED BY THE FEEDBACKVM. I'M JUST KEEPING IT HERE TO SHOW THAT I CAN
                //GET THE FEEDBACK HERE. THERE MIGHT BE A BETTER WAY TO DO THIS ANYWAY.
                #region Abort Check
                if (_AbortIsFlagged)
                {
                    _IsStudying = false;
                    break;
                }
                #endregion
            }
        }
Example #9
0
        /// <summary>
        /// This method is called when the user presses the Go button.
        /// </summary>
        public async Task Go()
        {
            if (!CanGo)
            {
                return;
            }

            DisableNavigationRequestedEventMessage.Publish();
            GoInProgress = true;
            try
            {
                var ids = new MobileList <Guid>();
                foreach (var songViewModel in Items)
                {
                    if (songViewModel.IsChecked)
                    {
                        Guid id      = default(Guid);
                        var  results = from entry in _MultiLineTextIdsAndTitles
                                       where entry.Value == songViewModel.SongTitle
                                       select entry;
                        id = results.First().Key;
                        ids.Add(id);
                    }
                }
                MultiLineTextList songs = null;
                #region Try ...(thinking)
                //var targetId = new Guid(@"5D4355FE-C46E-4AA1-9E4A-45288C341C44");
                var targetId = Guid.NewGuid();
                History.Events.ThinkingAboutTargetEvent.Publish(targetId);
                try
                {
                    #endregion
                    songs = await MultiLineTextList.NewMultiLineTextListAsync(ids);

                    #region ...Finally (thinked)
                }

                finally
                {
                    History.Events.ThinkedAboutTargetEvent.Publish(targetId);
                }
                #endregion

                var nativeLanguage = await LanguageEdit.GetLanguageEditAsync(GetNativeLanguageText());

                var noExpirationDate = StudyJobInfo <MultiLineTextList, IViewModelBase> .NoExpirationDate;
                var precision        = double.Parse(StudyResources.DefaultExpectedPrecision);

                //CREATE JOB INFO
                var studyJobInfo = new StudyJobInfo <MultiLineTextList, IViewModelBase>(songs,
                                                                                        nativeLanguage,
                                                                                        noExpirationDate,
                                                                                        precision);
                //CREATE OPPORTUNITY
                var opportunity = new Opportunity <MultiLineTextList, IViewModelBase>(Id,
                                                                                      this,
                                                                                      studyJobInfo,
                                                                                      StudyResources.CategoryStudy);

                //ADD OPPORTUNITY TO OUR FUTURE OPPORTUNITIES
                FutureOpportunities.Add(opportunity);

                //LET THE HISTORY SHOW THAT YOU ARE THINKING ABOUT THIS OPPORTUNITY
                var opportunityId = opportunity.Id;
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);

                //PUBLISH THE OPPORTUNITY
                Exchange.Ton.Publish(opportunity);

                //NOW, WE WAIT UNTIL WE HEAR A HANDLE(OFFER) MESSAGE.
                //TODO: TIMEOUT FOR OPPORTUNITY, BOTH EXPIRATION DATE AND WAITING FOR OFFER TIMEOUT
            }
            finally
            {
                EnableNavigationRequestedEventMessage.Publish();
            }
        }
        /// <summary>
        /// Gets the most recent phrase beliefs for all of the current user's phrases.
        /// </summary>
        /// <returns></returns>
        private async Task <PhraseEdit> GetRandomUnknownPhrase(MultiLineTextList multiLineTextList)
        {
            History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);

            if (_MostRecentPhraseBeliefsCache == null)
            {
                throw new Exception("MostRecentPhraseBeliefsCache should not be null");
            }
            if (_PseudoBeliefsCache == null)
            {
                _PseudoBeliefsCache = new MobileDictionary <string, Tuple <string, double> >();
            }

            PhraseEdit retPhrase = null;

            //GET OUR UNKNOWNS OUT OF OUR CACHES
            var threshold = double.Parse(StudyResources.DefaultKnowledgeThreshold);
            var unknownBeliefsFromCache = (from belief in _MostRecentPhraseBeliefsCache
                                           where belief.Strength < threshold &&
                                           PhraseTextIsInMultiLineTextList(belief.Phrase.Text, multiLineTextList)
                                           select belief).ToList();
            var unknownPseudoBeliefsFromCache = (from entry in _PseudoBeliefsCache
                                                 where entry.Value.Item2 < threshold &&
                                                 PhraseTextIsInMultiLineTextList(entry.Key, multiLineTextList)
                                                 select entry).ToList();

            //GET OUR UNKNOWN PHRASES THAT DONT HAVE A BELIEF OR PSUEDO BELIEF REGISTERED WITH THEM YET
            var unregisteredUnknownPhrases = GetUnregisteredUnknownPhraseBeliefs(multiLineTextList,
                                                                                 unknownBeliefsFromCache,
                                                                                 unknownPseudoBeliefsFromCache);

            var indexToPick = -1;
            //TOTAL COUNT = MOST RECENT PHRASE BELIEFS + PSEUDO BELIEFS COUNT + PHRASES WITHOUT BELIEFS
            var totalCountUnknown = 0;

            totalCountUnknown += unknownBeliefsFromCache.Count;
            if (_PseudoBeliefsCache != null)
            {
                totalCountUnknown += unknownPseudoBeliefsFromCache.Count;
            }
            totalCountUnknown += unregisteredUnknownPhrases.Count;

            if (totalCountUnknown == 0)
            {
                //EVERYTHING IS KNOWN, SO RETURN NULL
                return(null);
            }

            //IF WE TRY TO PICK FROM THE PSEUDO CACHE, THEN IT IS POSSIBLE
            //THAT OUR PHRASETEXT NO LONGER MATCHES A PHRASE. THEREFORE, WE
            //SET UP A LOOP TO KEEP TRYING TO PICK A RANDOM PHRASE FROM THE
            //ENTIRE LIST OF BOTH CACHES. IF WE FAIL TO DO THIS, THEN WE WILL
            //PICK FROM JUST THE ACTUAL BELIEF CACHE (NOT THE PSEUDO).
            var maxTries = int.Parse(StudyResources.MaxTriesToPickRandomFromEntireList);

            for (int i = 0; i < maxTries; i++)
            {
                History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);

                indexToPick = RandomPicker.Ton.NextInt(0, totalCountUnknown);

                if (indexToPick < unknownBeliefsFromCache.Count)
                {
                    //WE PICK FROM THE PHRASE BELIEF CACHE
                    var belief = unknownBeliefsFromCache[indexToPick];
                    return(belief.Phrase);
                }
                else if (indexToPick < (unknownBeliefsFromCache.Count + unknownPseudoBeliefsFromCache.Count))
                {
                    //WE _TRY_ TO PICK FROM THE PSUEDO CACHE
                    indexToPick -= unknownBeliefsFromCache.Count;
                    var beliefEntry  = unknownPseudoBeliefsFromCache[indexToPick];
                    var phraseText   = beliefEntry.Key;
                    var languageText = beliefEntry.Value.Item1;
                    var phrase       = await PhraseEdit.NewPhraseEditAsync(languageText);

                    phrase.Text = phraseText;
                    var phraseId = Guid.NewGuid();
                    phrase.Id = phraseId;
                    var retriever = await PhrasesByTextAndLanguageRetriever.CreateNewAsync(phrase);

                    retPhrase = retriever.RetrievedSinglePhrase;
                    if (retPhrase != null)
                    {
                        //WE HAVE FOUND A RETURN PHRASE, SO BREAK OUT OF OUR ATTEMPT LOOP
                        return(retPhrase);
                    }
                }
                else
                {
                    //WE PICK FROM THE UNKNOWN PHRASES WITHOUT BELIEFS I'M CONVERTING THIS
                    //TO LIST THIS LATE BECAUSE WE DON'T NEED IT AS A LIST UNTIL NOW.
                    var asList = unregisteredUnknownPhrases.ToList();
                    indexToPick -= (unknownBeliefsFromCache.Count + unknownPseudoBeliefsFromCache.Count);
                    retPhrase    = unregisteredUnknownPhrases[indexToPick];
                    return(retPhrase);
                }
            }

            //IF WE'VE GOTTEN THIS FAR, THEN WE COULDN'T FIND AN UNKNOWN PHRASE
            Services.PublishMessageEvent("Couldn't retrieve unknown phrase.", MessagePriority.Medium, MessageType.Warning);
            return(null);
        }
        /// <summary>
        /// Gets all songs in DB for this user, and calls PopulateItems.
        /// </summary>
        private async Task StartPopulateAllSongsAsync()
        {
            #region Thinking
            var targetId = Guid.NewGuid();
            History.Events.ThinkingAboutTargetEvent.Publish(targetId);
            #endregion
            var allMultiLineTexts = await MultiLineTextList.GetAllAsync();

            History.Events.ThinkedAboutTargetEvent.Publish(targetId);

            #region Sort MLT by Title Comparison (Comparison<MultiLineTextEdit> comparison = (a, b) =>)

            Comparison <MultiLineTextEdit> comparison = (a, b) =>
            {
                //WE'RE GOING TO TEST CHAR ORDER IN ALPHABET
                string aTitle = a.Title.ToLower();
                string bTitle = b.Title.ToLower();

                //IF THEY'RE THE SAME TITLES IN LOWER CASE, THEN THEY ARE EQUAL
                if (aTitle == bTitle)
                {
                    return(0);
                }

                //ONLY NEED TO TEST CHARACTERS UP TO LENGTH
                int  shorterTitleLength = aTitle.Length;
                bool aIsShorter         = true;
                if (bTitle.Length < shorterTitleLength)
                {
                    shorterTitleLength = bTitle.Length;
                    aIsShorter         = false;
                }

                int result = 0; //assume a and b are equal (though we know they aren't if we've reached this point)

                for (int i = 0; i < shorterTitleLength; i++)
                {
                    if (aTitle[i] < bTitle[i])
                    {
                        result = -1;
                        break;
                    }
                    else if (aTitle[i] > bTitle[i])
                    {
                        result = 1;
                        break;
                    }
                }

                //IF THEY ARE STILL EQUAL, THEN THE SHORTER PRECEDES THE LONGER
                if (result == 0)
                {
                    if (aIsShorter)
                    {
                        result = -1;
                    }
                    else
                    {
                        result = 1;
                    }
                }

                return(result);
            };

            #endregion

            ModelList = allMultiLineTexts;

            List <MultiLineTextEdit> songs = null;
            #region Thinking (try..)
            targetId = Guid.NewGuid();
            History.Events.ThinkingAboutTargetEvent.Publish(targetId);
            try
            {
                #endregion
                songs = (from multiLineText in allMultiLineTexts
                         where multiLineText.AdditionalMetadata.Contains(MultiLineTextEdit.MetadataEntrySong)
                         select multiLineText).ToList();

                songs.Sort(comparison);
                #region (...finally) Thinked
            }
            finally
            {
                History.Events.ThinkedAboutTargetEvent.Publish(targetId);
            }
            #endregion

            //CACHE ALL SONGS SO WE WON'T HAVE TO DOWNLOAD THEM AGAIN.  THIS IS ASSUMING MY CURRENT NAVIGATION
            //STRUCTURE WHICH MEANS THAT YOU CAN'T ADD SONGS WITHOUT RECREATING THIS ENTIRE VIEWMODEL.
            //THIS CACHE WILL BE USED FOR FILTERING.

            SortedModelListCache = songs;

            PopulateItems();
        }
 protected abstract Task InitializeForNewStudySessionAsync(MultiLineTextList multiLineTexts);
    protected virtual async Task StudyAsync(MultiLineTextList multiLineTexts)
    {
      _AbortIsFlagged = false;
      StudyItemViewModelBase contentVM = null;
      IFeedbackViewModelBase feedbackVM = _ViewModel.FeedbackViewModel;

      ///INITIALIZE HISTORY PUBLISHER
      History.HistoryPublisher.Ton.PublishEvent(new StartingStudySessionEvent());


      ///OKAY, SO AT THIS POINT, WE HAVE DONE OUR WORK THROUGH THE EXCHANGE.  
      ///THE CALLER HAS A REFERENCE TO OUR _VIEWMODEL PROPERTY.  WE HAVE CONTROL
      ///OF THE STUDY PROCESS THROUGH THIS _VIEWMODEL PROPERTY.  WE USE TWO SUB VIEWMODELS
      ///AT THIS POINT: STUDYITEM VIEWMODEL, AND FEEDBACK VIEWMODEL.  WE WILL IGNORE TIMEOUTS
      ///TO SIMPLIFY THINGS.  WE JUST NEED TO DO A FEW THINGS:
      ///1) INITIALIZE FOR OUR STUDY SESSION
      ///2) GET NEXT/ ASSIGN _VIEWMODEL.STUDYITEMVIEWMODEL AND _VIEWMODEL.FEEDBACKVIEWMODEL.
      ///3) SHOW STUDYITEMVIEWMODEL.
      ///4) WHEN SHOW IS DONE, ENABLE FEEDBACK VIEWMODEL AND GET FEEDBACK


      #region Thinking (try..)
      var targetId = Guid.NewGuid();
      History.Events.ThinkingAboutTargetEvent.Publish(targetId);
      try
      {
      #endregion
        ///1) INITIALIZE FOR OUR STUDY SESSION
        await InitializeForNewStudySessionAsync(multiLineTexts);
      #region (...finally) Thinked
      }
      finally
      {
        History.Events.ThinkedAboutTargetEvent.Publish(targetId);
      }
        #endregion

      //STUDY SESSION LOOP
      while (!_CompleteIsFlagged && !_AbortIsFlagged)
      {
        //BEGINNING OF A SINGLE STUDY ITEM
        _IsStudying = true;
        _ViewModel.FeedbackViewModel.IsEnabled = false;

        ///2) GET NEXT/ ASSIGN _VIEWMODEL.STUDYITEMVIEWMODEL AND _VIEWMODEL.FEEDBACKVIEWMODEL.
        History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
        //STUDY ITEM VIEWMODEL CONTENT
        _ViewModel.StudyItemViewModel = await GetNextStudyItemViewModelAsync();
        History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);

        contentVM = _ViewModel.StudyItemViewModel;
        if (contentVM == null)
        {
          //IF WE CAN'T GET A NEW STUDY ITEM VIEWMODEL, THEN THE STUDY SESSION IS COMPLETED.
          _CompleteIsFlagged = true;
          break;
        }
        
        #region Abort Check
        if (_AbortIsFlagged)
        {
          _IsStudying = false;
          break;
        }
        #endregion

        ///3) SHOW STUDY ITEM VIEWMODEL.
        ///   DO *NOT* THINK BEFORE AFTER, CAUSE THIS IS WAITING FOR USER INPUT
          await contentVM.ShowAsync();
        
        #region Abort Check
        if (_AbortIsFlagged)
        {
          _IsStudying = false;
          break;
        }
        #endregion

        ///4) WHEN SHOW IS DONE, GET FEEDBACK
        History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
        var feedbackTimeout = int.Parse(StudyResources.DefaultFeedbackTimeoutMilliseconds);
        var feedback = await feedbackVM.GetFeedbackAsync(feedbackTimeout);
        History.Events.ThinkingAboutTargetEvent.Publish(System.Guid.Empty);
        
        //RIGHT NOW I'M NOT DOING ANYTHING WITH THIS FEEDBACK, AS IT IS PUBLISHED WHEN
        //IT IS CREATED BY THE FEEDBACKVM. I'M JUST KEEPING IT HERE TO SHOW THAT I CAN
        //GET THE FEEDBACK HERE. THERE MIGHT BE A BETTER WAY TO DO THIS ANYWAY.
        #region Abort Check
        if (_AbortIsFlagged)
        {
          _IsStudying = false;
          break;
        }
        #endregion
      }
    }