using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;

namespace XLAB2
{
    internal sealed class SpnmtpDirectoryWindowViewModel : ObservableObject
    {
        private readonly IDialogService _dialogService;
        private readonly PsvDataService _service;
        private bool _isBusy;
        private string _searchText;
        private SpnmtpDirectoryItem _selectedItem;
        private string _statusText;

        public SpnmtpDirectoryWindowViewModel(PsvDataService service, IDialogService dialogService)
        {
            _service = service;
            _dialogService = dialogService;

            Items = new ObservableCollection<SpnmtpDirectoryItem>();
            ItemsView = CollectionViewSource.GetDefaultView(Items);
            ItemsView.Filter = FilterItems;

            AddCommand = new RelayCommand(delegate { AddItemAsync(); }, delegate { return !IsBusy; });
            DeleteCommand = new RelayCommand(delegate { DeleteSelectedItemAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
            EditCommand = new RelayCommand(delegate { EditSelectedItemAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
            RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });

            UpdateStatus();
        }

        public ICommand AddCommand { get; private set; }

        public ICommand DeleteCommand { get; private set; }

        public ICommand EditCommand { get; private set; }

        public ObservableCollection<SpnmtpDirectoryItem> Items { get; private set; }

        public ICollectionView ItemsView { get; private set; }

        public bool IsBusy
        {
            get { return _isBusy; }
            private set
            {
                if (SetProperty(ref _isBusy, value))
                {
                    RaiseCommandStates();
                }
            }
        }

        public ICommand RefreshCommand { get; private set; }

        public string SearchText
        {
            get { return _searchText; }
            set
            {
                if (SetProperty(ref _searchText, value))
                {
                    ItemsView.Refresh();
                    UpdateStatus();
                }
            }
        }

        public SpnmtpDirectoryItem SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (SetProperty(ref _selectedItem, value))
                {
                    RaiseCommandStates();
                }
            }
        }

        public string StatusText
        {
            get { return _statusText; }
            private set { SetProperty(ref _statusText, value); }
        }

        public async Task InitializeAsync()
        {
            await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); });
        }

        private void AddItemAsync()
        {
            var result = _dialogService.ShowSpnmtpEditDialog(new SpnmtpDirectoryItem(), true, Items.ToList());
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var createdId = await Task.Run(delegate { return _service.AddSpnmtpItem(result); });
                await RefreshCoreAsync(createdId);
                _dialogService.ShowInfo("Запись справочника добавлена.");
            });
        }

        private bool Contains(string source, string searchText)
        {
            return !string.IsNullOrWhiteSpace(source)
                   && source.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
        }

        private void DeleteSelectedItemAsync()
        {
            if (SelectedItem == null)
            {
                return;
            }

            var selectedItem = SelectedItem;
            if (!_dialogService.Confirm(string.Format("Удалить запись \"{0}\"?", selectedItem.Name)))
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var deleteResult = await Task.Run(delegate { return _service.DeleteSpnmtpItem(selectedItem.Id); });
                if (!deleteResult.IsDeleted)
                {
                    _dialogService.ShowWarning(deleteResult.WarningMessage);
                    return;
                }

                await RefreshCoreAsync(null);
                _dialogService.ShowInfo("Запись справочника удалена.");
            });
        }

        private void EditSelectedItemAsync()
        {
            if (SelectedItem == null)
            {
                return;
            }

            var seed = new SpnmtpDirectoryItem
            {
                Id = SelectedItem.Id,
                Name = SelectedItem.Name,
                SpecialName = SelectedItem.SpecialName
            };

            var result = _dialogService.ShowSpnmtpEditDialog(seed, false, Items.ToList());
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                await Task.Run(delegate { _service.UpdateSpnmtpItem(result); });
                await RefreshCoreAsync(result.Id);
                _dialogService.ShowInfo("Запись справочника обновлена.");
            });
        }

        private async Task ExecuteBusyOperationAsync(Func<Task> operation)
        {
            try
            {
                IsBusy = true;
                await operation();
            }
            catch (Exception ex)
            {
                _dialogService.ShowError(ex.Message);
            }
            finally
            {
                IsBusy = false;
            }
        }

        private async Task ExecuteMutationOperationAsync(Func<Task> operation)
        {
            try
            {
                IsBusy = true;
                await operation();
            }
            catch (InvalidOperationException ex)
            {
                _dialogService.ShowWarning(ex.Message);
            }
            catch (Exception ex)
            {
                _dialogService.ShowError(ex.Message);
            }
            finally
            {
                IsBusy = false;
            }
        }

        private bool FilterItems(object item)
        {
            var directoryItem = item as SpnmtpDirectoryItem;
            if (directoryItem == null)
            {
                return false;
            }

            if (string.IsNullOrWhiteSpace(SearchText))
            {
                return true;
            }

            return Contains(directoryItem.Name, SearchText)
                   || Contains(directoryItem.SpecialName, SearchText)
                   || directoryItem.Id.ToString().IndexOf(SearchText, StringComparison.OrdinalIgnoreCase) >= 0;
        }

        private async Task RefreshCoreAsync(int? idToSelect)
        {
            var items = await _service.LoadSpnmtpItemsAsync();
            var currentId = idToSelect ?? (SelectedItem == null ? (int?)null : SelectedItem.Id);

            Items.Clear();
            foreach (var item in items)
            {
                Items.Add(item);
            }

            ItemsView.Refresh();
            SelectedItem = currentId.HasValue
                ? Items.FirstOrDefault(delegate(SpnmtpDirectoryItem item) { return item.Id == currentId.Value; })
                : Items.FirstOrDefault();
            UpdateStatus();
        }

        private void RefreshAsync()
        {
            RunBusyOperation(delegate { return RefreshCoreAsync(null); });
        }

        private void RaiseCommandStates()
        {
            ((RelayCommand)AddCommand).RaiseCanExecuteChanged();
            ((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
            ((RelayCommand)EditCommand).RaiseCanExecuteChanged();
            ((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
        }

        private async void RunBusyOperation(Func<Task> operation)
        {
            try
            {
                await ExecuteBusyOperationAsync(operation);
            }
            catch (Exception ex)
            {
                IsBusy = false;
                _dialogService.ShowError(ex.Message);
            }
        }

        private async void RunMutationOperation(Func<Task> operation)
        {
            try
            {
                await ExecuteMutationOperationAsync(operation);
            }
            catch (Exception ex)
            {
                IsBusy = false;
                _dialogService.ShowError(ex.Message);
            }
        }

        private void UpdateStatus()
        {
            var visibleCount = ItemsView.Cast<object>().Count();
            StatusText = string.Format("Всего записей: {0}. По фильтру: {1}.", Items.Count, visibleCount);
        }
    }
}
