private void RegisterPage(Guid storyboardId, [NotNull] StoryboardPageInfo pageInfo)
        {
            if (pageInfo == null)
            {
                throw new ArgumentNullException(nameof(pageInfo));
            }
            if (pageInfo.View == null)
            {
                throw new ArgumentException($"{nameof(pageInfo.View)} is null");
            }
            if (pageInfo.ViewModel == null)
            {
                throw new ArgumentException($"{nameof(pageInfo.ViewModel)} is null");
            }

            var pageInstanceInfo = new InnerStoryboardPageInfo
            {
                IsStartPage  = pageInfo.IsStartPage,
                PageId       = pageInfo.PageId,
                StoryboardId = storyboardId,
                PageUniqueId = Guid.NewGuid()
            };

            var creationInfo = new PageCreationInfo
            {
                ViewModel = pageInfo.ViewModel,
                View      = pageInfo.View
            };

            RegisteredPages[pageInstanceInfo] = creationInfo;
        }
        protected virtual async Task RemovePageAsync(InnerStoryboardPageInfo pageInfo, Storyboard storyboard)
        {
            Journal.Remove(pageInfo);

            //start pages always in memory
            if (CachedPages.ContainsKey(pageInfo.PageUniqueId) &&
                !pageInfo.IsStartPage)
            {
                CachedPages.Remove(pageInfo.PageUniqueId);

                if (storyboard?.ActivePage != null)
                {
                    var previousPage = storyboard.ActivePage;
                    var viewModel    = previousPage.ViewModel;
                    // can close called early

                    viewModel.PageBackRequested       -= ViewModelOnPageBackRequested;
                    viewModel.PageCanceled            -= ViewModelOnPageCanceled;
                    viewModel.PageCompleted           -= ViewModelOnPageCompleted;
                    viewModel.PageTransitionRequested -= ViewModelOnPageTransitionRequested;

                    await viewModel.CloseAsync().ConfigureAwait(true);

                    storyboard.ActivePage = null;
                    viewModel.Dispose();
                }
            }

            if (PageContexts.ContainsKey(pageInfo.PageUniqueId))
            {
                PageContexts.Remove(pageInfo.PageUniqueId);
            }

            //todo maybe add isShared flag?
            if (pageInfo.IsStartPage &&
                StartPageContexts.ContainsKey(pageInfo.PageUniqueId))
            {
                PageContexts[pageInfo.PageUniqueId] =
                    StartPageContexts[pageInfo.PageUniqueId];
            }
        }
        private IStoryboardPageView CreatePageView([NotNull] InnerStoryboardPageInfo pageInfo)
        {
            if (!RegisteredPages.ContainsKey(pageInfo))
            {
                throw new InvalidOperationException($"Page creation info with pageTypeId {pageInfo.PageId} not registered");
            }

            var pageCreationInfo = RegisteredPages[pageInfo];
            var view             = PageCreator.CreateView(pageCreationInfo.View);
            var viewModel        = PageCreator.CreateViewModel(pageCreationInfo.ViewModel);

            viewModel.PageId                   = pageInfo.PageId;
            viewModel.StoryboardId             = pageInfo.StoryboardId;
            viewModel.PageBackRequested       += ViewModelOnPageBackRequested;
            viewModel.PageCanceled            += ViewModelOnPageCanceled;
            viewModel.PageCompleted           += ViewModelOnPageCompleted;
            viewModel.PageTransitionRequested += ViewModelOnPageTransitionRequested;
            view.ViewModel = viewModel;
            CachedPages.Add(pageInfo.PageUniqueId, view);
            return(view);
        }
        protected virtual async Task <TransitionResult> OpenPageAsync(
            [NotNull] InnerStoryboardPageInfo pageInfo,
            [CanBeNull] IStoryboardPageContext pageContext = null,
            bool addToJournal = true)
        {
            if (!Storyboards.ContainsKey(pageInfo.StoryboardId))
            {
                throw new InvalidOperationException($"Storyboard {pageInfo.StoryboardId} does not registered");
            }

            var tcs = new TaskCompletionSource <TransitionResult>();
            await Invoker.InvokeAsync(async() =>
            {
                try
                {
                    var storyboard = Storyboards[pageInfo.StoryboardId];

                    if (ActiveStoryboard?.ActivePage != null)
                    {
                        var previousPage = ActiveStoryboard.ActivePage;
                        var viewModel    = previousPage.ViewModel;

                        var canLeave = await viewModel.CanLeaveAsync().ConfigureAwait(true);
                        if (!canLeave)
                        {
                            tcs.SetResult(TransitionResult.CanceledByUser);
                            return;
                        }

                        await viewModel.LeaveAsync().ConfigureAwait(true);
                    }

                    var storyboardsWasChanged = ActiveStoryboard == null ||
                                                ActiveStoryboard.StoryboardId != storyboard.StoryboardId;
                    ActiveInnerStoryboardPageInfo = pageInfo;
                    ActiveStoryboard = storyboard;

                    if (storyboardsWasChanged)
                    {
                        ActiveStoryboardChanged?.Invoke(this, pageInfo.StoryboardId);
                    }

                    if (CachedPages.ContainsKey(pageInfo.PageUniqueId))
                    {
                        var page = CachedPages[pageInfo.PageUniqueId];

                        storyboard.ActivePage = page;
                        PageContexts.TryGetValue(pageInfo.PageUniqueId, out var restoredPageContext);

                        StartPagesOpenningStat.TryGetValue(pageInfo.PageUniqueId, out var wasPageOpenned);
                        if (wasPageOpenned)
                        {
                            await page.ViewModel.ReturnAsync(pageContext ?? restoredPageContext).ConfigureAwait(true);
                        }
                        else
                        {
                            await page.ViewModel.OpenAsync(pageContext ?? restoredPageContext).ConfigureAwait(true);
                            StartPagesOpenningStat[pageInfo.PageUniqueId] = true;
                        }
                    }
                    else
                    {
                        var view = CreatePageView(pageInfo);
                        storyboard.ActivePage = view;

                        await view.ViewModel.OpenAsync(pageContext).ConfigureAwait(true);
                        PageContexts[pageInfo.PageUniqueId] = pageContext;
                    }

                    if (addToJournal)
                    {
                        Journal.AddLast(pageInfo);
                    }

                    tcs.SetResult(TransitionResult.Completed);
                    CanBackChanged?.Invoke(this, EventArgs.Empty);
                }
                catch (Exception e)
                {
                    ExceptionOccured?.Invoke(this, e);
                    tcs.SetResult(TransitionResult.Failed);
                }
            }).ConfigureAwait(false);

            return(await tcs.Task.ConfigureAwait(false));
        }