using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using Tango.Core;
using Tango.Core.Commands;
using Tango.FSE.Common;
using Tango.FSE.Common.Modules;
using Tango.FSE.Common.Navigation;
using Tango.FSE.Common.Notifications;
using Tango.FSE.Common.Threading;
using Tango.FSE.UI.Views;
using Tango.SharedUI.Controls;
namespace Tango.FSE.UI.Navigation
{
///
/// Represents the default FSE navigation manager.
///
///
public class DefaultNavigationManager : ExtendedObject, INavigationManager
{
//private event Action NavigationCycleCompleted;
//private event Action BeforeNavigationCycleCompleted;
private class AwaitingVMResult
{
public FSEViewModel FromVM { get; set; }
public FSEViewModel ToVM { get; set; }
public Action Action { get; set; }
}
private List _awaitingVMResults;
private IDispatcherProvider _dispatcherProvider;
private IFSEModuleLoader _moduleLoader;
private INotificationProvider _notificationProvider;
private Object _currentVM;
private String _lastFullPath;
private bool _preventHistory;
private bool _force;
private Stack _navigationHistory;
///
/// Occurs when the navigation manager if navigating to another view.
///
public event EventHandler Navigating;
///
/// Gets the menu items.
///
public ObservableCollection MenuItems { get; private set; }
///
/// Gets the current view model.
///
public FSEViewModel CurrentVM
{
get { return _currentVM as FSEViewModel; }
}
private IFSEModule _currentModule;
///
/// Gets or sets the current module.
///
public IFSEModule CurrentModule
{
get { return _currentModule; }
private set { _currentModule = value; RaisePropertyChangedAuto(); }
}
///
/// Navigates to the previous view.
///
public RelayCommand NavigateBackCommand { get; private set; }
///
/// Navigates to the specified full path in command parameter.
///
public RelayCommand NavigateToCommand { get; private set; }
///
/// Initializes a new instance of the class.
///
/// The module loader.
public DefaultNavigationManager(IFSEModuleLoader moduleLoader, IDispatcherProvider dispatcherProvider, INotificationProvider notificationProvider)
{
IsBackEnabled = true;
MenuItems = new ObservableCollection();
MenuItems.EnableCrossThreadOperations();
ICollectionView collection = CollectionViewSource.GetDefaultView(MenuItems);
collection.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Ascending));
_awaitingVMResults = new List();
_navigationHistory = new Stack();
_moduleLoader = moduleLoader;
_notificationProvider = notificationProvider;
NavigateToCommand = new RelayCommand(async (x) => await NavigateTo(x));
NavigateBackCommand = new RelayCommand(async () => await NavigateBack());
_dispatcherProvider = dispatcherProvider;
_moduleLoader.ModulesLoaded += (x, e) => { MenuItems.Clear(); ClearHistory(); };
}
///
/// Navigates to the specified FSE view.
///
/// The view.
public Task NavigateTo(NavigationView view, bool pushToHistory = true)
{
if (view == NavigationView.Home)
{
_navigationHistory.Clear();
RaisePropertyChanged(nameof(CanNavigateBack));
_lastFullPath = null;
var firstModule = _moduleLoader.UserModules.SingleOrDefault(x => x.Name == "Internal Module");
if (firstModule != null)
{
var moduleAtt = firstModule.GetType().GetCustomAttribute();
if (moduleAtt != null)
{
return NavigateTo(firstModule.GetType(), pushToHistory, moduleAtt.HomeViewName);
}
else
{
return NavigateTo(firstModule.GetType(), pushToHistory);
}
}
else
{
return NavigateTo(NavigationView.NoPermissionsView);
}
}
else
{
LogManager.Log($"Navigating to: {view.ToString()}...");
var fromView = MainView.Instance.NavigationControl.SelectedElement;
FrameworkElement toView = null;
toView = MainView.Instance.NavigationControl.NavigateTo(view.ToString(), () =>
{
_currentVM = toView.DataContext;
NotifyOnNavigated(fromView.DataContext, toView.DataContext);
});
NotifyOnBeforeNavigated(fromView.DataContext, toView.DataContext);
return Task.FromResult(true);
}
}
///
/// Navigates to the specified FSE view with the specified receive object.
///
/// The view.
///
///
///
public async Task NavigateWithObject(NavigationView view, TPass obj, bool pushToHistory = true)
{
LogManager.Log($"Navigating to: {view.ToString()}, with object {typeof(TPass).Name}...");
if (_currentVM != null && _currentVM is INavigationBlocker)
{
if (!await (_currentVM as INavigationBlocker).OnNavigateOutRequest())
{
return false;
}
}
var fromVM = CurrentVM;
var toView = MainView.Instance.NavigationControl.GetElement(view.ToString());
NotifyOnBeforeNavigated(CurrentVM, toView.DataContext);
INavigationObjectReceiver receiver = MainView.Instance.NavigationControl.Elements.FirstOrDefault(x => (x.GetType().Name == view.ToString() || NavigationControl.GetNavigationName(x) == view.ToString()) && x.DataContext is INavigationObjectReceiver).DataContext as INavigationObjectReceiver;
if (receiver != null)
{
receiver.OnNavigatedToWithObject(obj);
}
toView = await MainView.Instance.NavigationControl.NavigateToAsync(view.ToString());
_currentVM = toView.DataContext;
NotifyOnNavigated(fromVM, toView.DataContext);
return true;
}
///
/// Navigates to the specified module.
///
///
public Task NavigateTo(bool pushToHistory = true) where T : IFSEModule
{
return NavigateTo(typeof(T));
}
///
/// Navigates to the specified module using the view path (e.g MainView.JobsView).
///
///
/// The view path.
public Task NavigateTo(string viewPath, bool pushToHistory = true) where T : IFSEModule
{
return NavigateTo(pushToHistory, viewPath.Split('.'));
}
///
/// Navigates to the specified module using the view path (e.g MainView,JobsView).
/// This method makes it easy to do stuff like NavigateTo(nameof(MainView),nameof(JobsView));
///
///
/// The view path.
public Task NavigateTo(bool pushToHistory = true, params String[] viewPath) where T : IFSEModule
{
return NavigateTo(typeof(T), pushToHistory, viewPath);
}
///
/// Navigates to the specified module and view by full path (e.g Jobs.JobsView).
///
/// The full path.
private async Task NavigateTo(String fullPath, bool pushToHistory = true, Action onNavigating = null, Action onNavigated = null, bool navigatingBack = false)
{
try
{
if (_lastFullPath == fullPath && !_force)
{
return true;
}
String[] path = fullPath.Split('.');
var module = _moduleLoader.UserModules.SingleOrDefault(x => x.GetType().Name == path[0] || x.Name == path[0]);
if (module == null)
{
await _notificationProvider.ShowError("The specified module was not loaded.");
return false;
}
if (path.Length == 1 && path[0] == CurrentModule.Name) return true;
var fromVM = _currentVM;
if (_currentVM != null && _currentVM is INavigationBlocker)
{
if (navigatingBack)
{
if (!await (_currentVM as INavigationBlocker).OnNavigateBackRequest())
{
return false;
}
}
else
{
if (!await (_currentVM as INavigationBlocker).OnNavigateOutRequest())
{
return false;
}
}
}
LogManager.Log($"Navigating to: {fullPath}...");
if (pushToHistory && _lastFullPath != null && !_preventHistory)
{
if (_navigationHistory.Count == 0 || _navigationHistory.Peek() != _lastFullPath)
{
_navigationHistory.Push(_lastFullPath);
}
RaisePropertyChanged(nameof(CanNavigateBack));
DistinctNavigationHistory();
}
_lastFullPath = fullPath;
if (MainView.Instance.NavigationControl.GetSelectedElementNavigationName() != NavigationView.LayoutView.ToString())
{
NotifyOnBeforeNavigated(null, LayoutView.Instance.DataContext);
MainView.Instance.NavigationControl.NavigateTo(NavigationView.LayoutView.ToString(), () =>
{
NotifyOnNavigated(null, LayoutView.Instance.DataContext);
});
}
var navigationControl = LayoutView.Instance.NavigationControl;
CurrentModule = module;
var moduleView = navigationControl.NavigateTo(module.Name);
_currentVM = moduleView.DataContext;
if (path.Length > 1)
{
var moduleNavigation = moduleView.FindChildOffline();
if (moduleNavigation != null)
{
moduleNavigation.RegisterForLoadedOrNow(async (x, e) =>
{
var lastView = moduleNavigation.GetElement(path.Last());
if (lastView != null)
{
onNavigating?.Invoke(fromVM as FSEViewModel, lastView.DataContext as FSEViewModel);
}
foreach (var view in path.Skip(1))
{
await Task.Delay(100);
FrameworkElement v = null;
v = moduleNavigation.NavigateTo(view, () =>
{
if (v != null)
{
NotifyOnNavigated(fromVM, v.DataContext);
onNavigated?.Invoke(fromVM as FSEViewModel, v.DataContext as FSEViewModel);
NotifyAwaitingVMResults(fromVM as FSEViewModel, v.DataContext as FSEViewModel);
}
});
NotifyOnBeforeNavigated(fromVM, v.DataContext);
if (v != null)
{
_currentVM = v.DataContext;
if (view != path.Last())
{
moduleNavigation = v.FindChildOffline();
}
}
else
{
throw LogManager.Log(new ArgumentNullException("Could not navigate to " + fullPath));
}
}
});
}
else
{
onNavigating?.Invoke(fromVM as FSEViewModel, _currentVM as FSEViewModel);
NotifyOnBeforeNavigated(fromVM, _currentVM);
await Task.Delay(navigationControl.TransitionDuration.TimeSpan);
NotifyOnNavigated(fromVM, _currentVM);
onNavigated?.Invoke(fromVM as FSEViewModel, _currentVM as FSEViewModel);
NotifyAwaitingVMResults(fromVM as FSEViewModel, _currentVM as FSEViewModel);
}
}
else
{
NotifyOnBeforeNavigated(fromVM, _currentVM);
onNavigating?.Invoke(fromVM as FSEViewModel, _currentVM as FSEViewModel);
await Task.Delay(navigationControl.TransitionDuration.TimeSpan);
NotifyOnNavigated(fromVM, _currentVM);
onNavigated?.Invoke(fromVM as FSEViewModel, _currentVM as FSEViewModel);
NotifyAwaitingVMResults(fromVM as FSEViewModel, _currentVM as FSEViewModel);
if (_currentVM.GetType().GetCustomAttribute() != null)
{
var moduleNavigation = moduleView.FindChildOffline();
await NavigateTo(module.GetType().Name + "." + moduleNavigation.SelectedElement.GetType().Name, false);
}
}
return true;
}
catch (Exception ex)
{
LogManager.Log(ex, $"Error navigating to '{fullPath}'.");
_notificationProvider.PushErrorReportingSnackbar(ex, "Navigation Error", $"Error navigating to '{fullPath}'.");
return false;
}
}
///
/// Navigates for result.
///
/// The type of the module.
/// The type of the view.
/// The type of the result.
/// The type of the object.
/// The object.
/// if set to true [push to history].
///
public async Task NavigateForResult(TObject obj, bool pushToHistory = true)
where TModule : IFSEModule
{
TaskCompletionSource source = new TaskCompletionSource();
var fromVM = _currentVM;
await NavigateTo(typeof(TModule).Name + "." + typeof(TView).Name, pushToHistory, (from, to) =>
{
_awaitingVMResults.Add(new AwaitingVMResult()
{
FromVM = fromVM as FSEViewModel,
ToVM = to as FSEViewModel,
Action = () =>
{
if (to is INavigationResultProvider)
{
source.SetResult((to as INavigationResultProvider).GetNavigationResult());
}
}
});
if (to is INavigationResultProvider)
{
(to as INavigationResultProvider).OnNavigationObjectReceived(obj);
}
});
return await source.Task;
}
///
/// Navigates to the specified module and view with the specified object.
///
/// The type of the module.
/// The type of the view.
/// The type of the pass.
/// The object.
/// Push the current view to history ?.
/// Force the navigation even if we are on the same view.
///
public Task NavigateWithObject(TPass obj, bool pushToHistory = true, bool force = false) where TModule : IFSEModule
{
_force = force;
return NavigateTo(typeof(TModule).Name + "." + typeof(TView).Name, pushToHistory, (fromVM, toVM) =>
{
_force = false;
if (toVM is INavigationObjectReceiver)
{
(toVM as INavigationObjectReceiver).OnNavigatedToWithObject(obj);
}
});
}
///
/// Navigates to the specified module and view with the specified object.
/// Ideally will be used for navigating to a module that is not references by the current module.
///
/// The view full path (Module.ViewName.SubViewName).
/// The object.
/// Push the current view to history.
///
public Task NavigateWithObject(string viewPath, object obj, bool pushToHistory = true)
{
return NavigateTo(viewPath, pushToHistory, (fromVM, toVM) =>
{
_force = false;
if (toVM is INavigationObjectReceiver)
{
var methodName = "OnNavigatedToWithObject";
var methodParamTypeName = obj.GetType().Name;
foreach (var method in toVM.GetType().GetRuntimeMethods())
{
if (method.Name == methodName)
{
var parameters = method.GetParameters().ToList();
if (parameters.Count == 1)
{
var parameter = parameters.First();
if (parameter.ParameterType.Name == methodParamTypeName)
{
var json = JsonConvert.SerializeObject(obj);
object targetObject = JsonConvert.DeserializeObject(json, parameter.ParameterType);
method.Invoke(toVM, new object[] { targetObject });
break;
}
}
}
}
}
});
}
private Task NavigateTo(Type moduleType, bool pushToHistory = true, params String[] viewPath)
{
if (viewPath != null && viewPath.Length > 0)
{
return NavigateTo(moduleType.Name + "." + String.Join(".", viewPath), pushToHistory);
}
else
{
return NavigateTo(moduleType.Name, pushToHistory);
}
}
///
/// Gets a value indicating whether the navigation system is able to navigate to the previous view.
///
public bool CanNavigateBack
{
get { return _navigationHistory.Count > 0; }
}
private bool _isBackEnabled;
///
/// Gets a value indicating whether the back should be enabled.
///
public bool IsBackEnabled
{
get { return _isBackEnabled; }
set { _isBackEnabled = value; RaisePropertyChangedAuto(); }
}
///
/// Navigates to the previous view if is true.
///
public async Task NavigateBack()
{
LogManager.Log("Navigating back...");
if (_navigationHistory.Count > 0)
{
while (_navigationHistory.Peek() == _lastFullPath)
{
_navigationHistory.Pop();
}
}
RaisePropertyChanged(nameof(CanNavigateBack));
if (_navigationHistory.Count > 0)
{
String first = _navigationHistory.Pop();
_preventHistory = true;
if (await NavigateTo(first, true, null, null, true))
{
RaisePropertyChanged(nameof(CanNavigateBack));
_preventHistory = false;
return true;
}
else
{
if (_navigationHistory.Count == 0 || _navigationHistory.Peek() != first)
{
_navigationHistory.Push(first);
}
_preventHistory = false;
RaisePropertyChanged(nameof(CanNavigateBack));
return false;
}
}
else
{
await NavigateTo(NavigationView.Home);
RaisePropertyChanged(nameof(CanNavigateBack));
_preventHistory = false;
return true;
}
}
///
/// Clears the navigation back history.
///
public void ClearHistory()
{
LogManager.Log("Navigation history cleared.");
_navigationHistory.Clear();
RaisePropertyChanged(nameof(CanNavigateBack));
}
///
/// Clears the navigation back history except the specified view type.
///
///
public void ClearHistoryExcept()
{
LogManager.Log($"Navigation history cleared except for {typeof(T).Name}.");
var history_list = _navigationHistory.ToList();
history_list = history_list.Where(x => x.Contains(typeof(T).Name)).Distinct().ToList();
history_list.Reverse();
_navigationHistory.Clear();
foreach (var item in history_list)
{
_navigationHistory.Push(item);
}
RaisePropertyChanged(nameof(CanNavigateBack));
}
private void NotifyOnBeforeNavigated(object fromVM, object toVM)
{
if (fromVM is FSEViewModel)
{
(fromVM as FSEViewModel)?.OnBeforeNavigatedFrom();
}
if (toVM is FSEViewModel)
{
(toVM as FSEViewModel)?.OnBeforeNavigatedTo();
}
Navigating?.Invoke(this, new NavigationToEventArgs()
{
FromVM = fromVM as FSEViewModel,
ToVM = toVM as FSEViewModel,
});
}
private void NotifyOnNavigated(object fromVM, object toVM)
{
if (fromVM is FSEViewModel)
{
(fromVM as FSEViewModel)?.OnNavigatedFrom();
}
if (toVM is FSEViewModel)
{
(toVM as FSEViewModel)?.OnNavigatedTo();
(toVM as FSEViewModel)?.OnNavigatedTo(fromVM as FSEViewModel);
}
}
private void NotifyAwaitingVMResults(FSEViewModel fromVM, FSEViewModel toVM)
{
var awaiter = _awaitingVMResults.SingleOrDefault(x => x.FromVM == toVM && x.ToVM == fromVM);
if (awaiter != null)
{
_awaitingVMResults.Remove(awaiter);
awaiter.Action();
}
}
public void DeleteHistoryItem() where TModule : IFSEModule
{
var history_list = _navigationHistory.ToList();
history_list = history_list.Where(x => x != typeof(TModule).Name + "." + typeof(TView).Name).ToList();
history_list.Reverse();
_navigationHistory.Clear();
foreach (var item in history_list)
{
_navigationHistory.Push(item);
}
}
private void DistinctNavigationHistory()
{
var history_list = _navigationHistory.Distinct().ToList();
history_list.Reverse();
_navigationHistory.Clear();
foreach (var item in history_list)
{
_navigationHistory.Push(item);
}
RaisePropertyChanged(nameof(CanNavigateBack));
}
}
}