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

namespace XLAB2
{
    internal sealed class FrpdDirectoryWindowViewModel : ObservableObject
    {
        private readonly IFrpdDirectoryDialogService _dialogService;
        private readonly FrpdDirectoryService _service;
        private List<FrpdDirectoryItem> _frpdCache;
        private bool _isBusy;
        private string _searchText;
        private FrpdDirectoryItem _selectedFrpd;
        private FrpdvdDirectoryItem _selectedFrpdvd;
        private string _statusText;

        public FrpdDirectoryWindowViewModel(FrpdDirectoryService service, IFrpdDirectoryDialogService dialogService)
        {
            _service = service;
            _dialogService = dialogService;
            _frpdCache = new List<FrpdDirectoryItem>();

            FrpdItems = new ObservableCollection<FrpdDirectoryItem>();
            FrpdvdItems = new ObservableCollection<FrpdvdDirectoryItem>();

            AddFrpdCommand = new RelayCommand(delegate { AddFrpdAsync(); }, delegate { return !IsBusy; });
            EditFrpdCommand = new RelayCommand(delegate { EditFrpdAsync(); }, delegate { return !IsBusy && SelectedFrpd != null; });
            DeleteFrpdCommand = new RelayCommand(delegate { DeleteFrpdAsync(); }, delegate { return !IsBusy && SelectedFrpd != null; });
            AddFrpdvdCommand = new RelayCommand(delegate { AddFrpdvdAsync(); }, delegate { return !IsBusy && SelectedFrpd != null; });
            EditFrpdvdCommand = new RelayCommand(delegate { EditFrpdvdAsync(); }, delegate { return !IsBusy && SelectedFrpdvd != null; });
            DeleteFrpdvdCommand = new RelayCommand(delegate { DeleteFrpdvdAsync(); }, delegate { return !IsBusy && SelectedFrpdvd != null; });
            RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });

            UpdateStatus();
        }

        public ICommand AddFrpdCommand { get; private set; }
        public ICommand AddFrpdvdCommand { get; private set; }
        public ICommand DeleteFrpdCommand { get; private set; }
        public ICommand DeleteFrpdvdCommand { get; private set; }
        public ICommand EditFrpdCommand { get; private set; }
        public ICommand EditFrpdvdCommand { get; private set; }
        public ObservableCollection<FrpdDirectoryItem> FrpdItems { get; private set; }
        public ObservableCollection<FrpdvdDirectoryItem> FrpdvdItems { get; private set; }
        public ICommand RefreshCommand { get; private set; }

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

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

        public FrpdDirectoryItem SelectedFrpd
        {
            get { return _selectedFrpd; }
            set
            {
                if (SetProperty(ref _selectedFrpd, value))
                {
                    RaiseCommandStates();
                    LoadFrpdvdForSelection();
                    UpdateStatus();
                }
            }
        }

        public FrpdvdDirectoryItem SelectedFrpdvd
        {
            get { return _selectedFrpdvd; }
            set
            {
                if (SetProperty(ref _selectedFrpdvd, value))
                {
                    RaiseCommandStates();
                    UpdateStatus();
                }
            }
        }

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

        public async Task InitializeAsync()
        {
            await ExecuteBusyOperationAsync(async delegate { await RefreshFrpdCoreAsync(null, null); });
        }

        private void AddFrpdAsync()
        {
            var result = _dialogService.ShowFrpdEditDialog(new FrpdDirectoryItem(), true, _frpdCache.ToList(), _service);
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var createdId = await Task.Run(delegate { return _service.AddFrpdItem(result); });
                await RefreshFrpdCoreAsync(createdId, null);
                _dialogService.ShowInfo("Запись FRPD добавлена.");
            });
        }

        private void AddFrpdvdAsync()
        {
            if (SelectedFrpd == null)
            {
                return;
            }

            var result = _dialogService.ShowFrpdvdEditDialog(new FrpdvdDirectoryItem { FrpdId = SelectedFrpd.Id }, true, FrpdvdItems.ToList(), _service);
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var createdId = await Task.Run(delegate { return _service.AddFrpdvdItem(result); });
                await RefreshFrpdCoreAsync(result.FrpdId, createdId);
                _dialogService.ShowInfo("Запись FRPDVD добавлена.");
            });
        }

        private void ApplyFrpdFilter(int? preferredId)
        {
            var filteredItems = _frpdCache.Where(delegate(FrpdDirectoryItem item) { return MatchesSearch(item); }).ToList();
            FrpdItems.Clear();
            foreach (var item in filteredItems)
            {
                FrpdItems.Add(item);
            }

            SelectedFrpd = preferredId.HasValue
                ? FrpdItems.FirstOrDefault(delegate(FrpdDirectoryItem item) { return item.Id == preferredId.Value; })
                : FrpdItems.FirstOrDefault();
        }

        private void ApplySearchFilter()
        {
            if (!IsBusy)
            {
                ApplyFrpdFilter(SelectedFrpd == null ? (int?)null : SelectedFrpd.Id);
            }
        }

        private static FrpdDirectoryItem CloneFrpd(FrpdDirectoryItem source)
        {
            return new FrpdDirectoryItem
            {
                CreatedOn = source.CreatedOn,
                Guid = source.Guid,
                Id = source.Id,
                LiquidatedOn = source.LiquidatedOn,
                LocalCode = source.LocalCode,
                Name = source.Name,
                ParentId = source.ParentId,
                ParentName = source.ParentName
            };
        }

        private static FrpdvdDirectoryItem CloneFrpdvd(FrpdvdDirectoryItem source)
        {
            return new FrpdvdDirectoryItem
            {
                ActivityId = source.ActivityId,
                ActivityName = source.ActivityName,
                FrpdId = source.FrpdId,
                Id = source.Id
            };
        }

        private void DeleteFrpdAsync()
        {
            if (SelectedFrpd == null)
            {
                return;
            }

            var selected = SelectedFrpd;
            if (!_dialogService.Confirm(string.Format("Удалить организацию/подразделение \"{0}\"?", selected.Name)))
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var result = await Task.Run(delegate { return _service.DeleteFrpdItem(selected.Id); });
                if (!result.IsDeleted)
                {
                    _dialogService.ShowWarning(result.WarningMessage);
                    return;
                }

                await RefreshFrpdCoreAsync(null, null);
                _dialogService.ShowInfo("Запись удалена.");
            });
        }

        private void DeleteFrpdvdAsync()
        {
            if (SelectedFrpdvd == null)
            {
                return;
            }

            var selected = SelectedFrpdvd;
            if (!_dialogService.Confirm(string.Format("Удалить вид деятельности \"{0}\"?", selected.ActivityName)))
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var result = await Task.Run(delegate { return _service.DeleteFrpdvdItem(selected.Id); });
                if (!result.IsDeleted)
                {
                    _dialogService.ShowWarning(result.WarningMessage);
                    return;
                }

                await RefreshFrpdCoreAsync(selected.FrpdId, null);
                _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 void EditFrpdAsync()
        {
            if (SelectedFrpd == null)
            {
                return;
            }

            var result = _dialogService.ShowFrpdEditDialog(CloneFrpd(SelectedFrpd), false, _frpdCache.ToList(), _service);
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                await Task.Run(delegate { _service.UpdateFrpdItem(result); });
                await RefreshFrpdCoreAsync(result.Id, SelectedFrpdvd == null ? (int?)null : SelectedFrpdvd.Id);
                _dialogService.ShowInfo("Запись обновлена.");
            });
        }

        private void EditFrpdvdAsync()
        {
            if (SelectedFrpdvd == null)
            {
                return;
            }

            var result = _dialogService.ShowFrpdvdEditDialog(CloneFrpdvd(SelectedFrpdvd), false, FrpdvdItems.ToList(), _service);
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                await Task.Run(delegate { _service.UpdateFrpdvdItem(result); });
                await RefreshFrpdCoreAsync(result.FrpdId, result.Id);
                _dialogService.ShowInfo("Запись обновлена.");
            });
        }

        private string[] GetSearchTokens()
        {
            return (SearchText ?? string.Empty)
                .Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(delegate(string token) { return token.Trim().ToUpperInvariant(); })
                .Where(delegate(string token) { return token.Length > 0; })
                .ToArray();
        }

        private void LoadFrpdvdForSelection()
        {
            if (!IsBusy)
            {
                RunBusyOperation(async delegate
                {
                    if (SelectedFrpd == null)
                    {
                        FrpdvdItems.Clear();
                        SelectedFrpdvd = null;
                        UpdateStatus();
                        return;
                    }

                    await RefreshFrpdvdCoreAsync(SelectedFrpd.Id, null);
                });
            }
        }

        private bool MatchesSearch(FrpdDirectoryItem item)
        {
            if (item == null)
            {
                return false;
            }

            var tokens = GetSearchTokens();
            if (tokens.Length == 0)
            {
                return true;
            }

            var haystack = string.Join(
                " ",
                new[]
                {
                    item.Id.ToString(),
                    item.Name,
                    item.ParentName,
                    item.LocalCode,
                    item.Guid,
                    item.ActivityNames
                }
                .Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }))
                .ToUpperInvariant();
            return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; });
        }

        private async Task RefreshFrpdCoreAsync(int? frpdIdToSelect, int? frpdvdIdToSelect)
        {
            var currentFrpdId = SelectedFrpd == null ? (int?)null : SelectedFrpd.Id;
            _frpdCache = (await Task.Run(delegate { return _service.LoadFrpdItems(); })).ToList();

            ApplyFrpdFilter(frpdIdToSelect.HasValue ? frpdIdToSelect : currentFrpdId);
            if (SelectedFrpd == null)
            {
                FrpdvdItems.Clear();
                SelectedFrpdvd = null;
                UpdateStatus();
                return;
            }

            await RefreshFrpdvdCoreAsync(SelectedFrpd.Id, frpdvdIdToSelect);
            UpdateStatus();
        }

        private async Task RefreshFrpdvdCoreAsync(int frpdId, int? frpdvdIdToSelect)
        {
            var items = await Task.Run(delegate { return _service.LoadFrpdvdItems(frpdId); });
            FrpdvdItems.Clear();
            foreach (var item in items)
            {
                FrpdvdItems.Add(item);
            }

            SelectedFrpdvd = frpdvdIdToSelect.HasValue
                ? FrpdvdItems.FirstOrDefault(delegate(FrpdvdDirectoryItem item) { return item.Id == frpdvdIdToSelect.Value; })
                : FrpdvdItems.FirstOrDefault();
            UpdateStatus();
        }

        private void RaiseCommandStates()
        {
            ((RelayCommand)AddFrpdCommand).RaiseCanExecuteChanged();
            ((RelayCommand)EditFrpdCommand).RaiseCanExecuteChanged();
            ((RelayCommand)DeleteFrpdCommand).RaiseCanExecuteChanged();
            ((RelayCommand)AddFrpdvdCommand).RaiseCanExecuteChanged();
            ((RelayCommand)EditFrpdvdCommand).RaiseCanExecuteChanged();
            ((RelayCommand)DeleteFrpdvdCommand).RaiseCanExecuteChanged();
            ((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
        }

        private void RefreshAsync()
        {
            RunBusyOperation(async delegate { await RefreshFrpdCoreAsync(SelectedFrpd == null ? (int?)null : SelectedFrpd.Id, SelectedFrpdvd == null ? (int?)null : SelectedFrpdvd.Id); });
        }

        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 searchText = string.IsNullOrWhiteSpace(SearchText) ? null : SearchText.Trim();
            StatusText = string.Format(
                "{0}Подразделений: {1}/{2}. Видов деятельности: {3}.",
                string.IsNullOrWhiteSpace(searchText) ? string.Empty : string.Format("Поиск: \"{0}\". ", searchText),
                FrpdItems.Count,
                _frpdCache.Count,
                FrpdvdItems.Count);
        }
    }
}
