using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Windows.Threading;
using DatabaseEditor.BL;
using DatabaseEditor.BL.Enums;
using DatabaseEditor.BL.Filters.Conditions;
using DatabaseEditor.BL.ItemsProviders;
using DatabaseEditor.BL.Models;
using DatabaseEditor.Commands;
using DatabaseEditor.Messages;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Ioc;
using PropertyChanged;

namespace DatabaseEditor.ViewModels
{
    [ImplementPropertyChanged]
    public class PageViewModel<TListModel, TDetail> : ViewModelBase
        where TDetail : TListModel, new()
        where TListModel : ItemModelBase
    {
        private const int PollingInterval = 5;
        private readonly MySQLItemsProvider<TListModel, TDetail> _itemsProvider;
        private readonly DispatcherTimer _poller;
        private TListModel _lastSelectedItem;

        public bool EditDialogVisible { get; set; } = false;

        public PageViewModel()
        {
            _itemsProvider = SimpleIoc.Default.GetInstance<MySQLItemsProvider<TListModel, TDetail>>();
            _poller = new DispatcherTimer {Interval = TimeSpan.FromSeconds(PollingInterval)};
            _poller.Tick += OnPollerTick;

            EditedItems = new List<TDetail>();

            MessengerInstance.Register<ConnectMessage>(this, OnConnectionChanged);
            MessengerInstance.Register<ShowItemMessageGeneric<TListModel>>(this, OnShowItem);
            MessengerInstance.Register<FilterChangedMessage<TListModel>>(this, OnFilterChanged);
            MessengerInstance.Register<EditItemMessage<TDetail>>(this, OnEditItem);
            MessengerInstance.Register<CancelEditMessage>(this, OnCancelledEdit);
            MessengerInstance.Register<ConfirmEditMessage<TDetail>>(this, OnConfirmedEdit);
            MessengerInstance.Register<CommitMessage>(this, Commit);
            MessengerInstance.Register<RefreshCommand>(this, Refresh);
            MessengerInstance.Register<DeleteItemMessage<TDetail>>(this, DeleteItem);
        }

        private void PollDatabase()
        {
            if (_itemsProvider.SourceChanged)
            {
                DatabaseChanged = true;
            }
        }

        private void DeleteItem(DeleteItemMessage<TDetail> message)
        {
            ItemsCollection.Remove(message.Item);
            ReloadTable();
        }

        private void Refresh(RefreshCommand obj)
        {
            ReloadTable();
        }

        /// <summary>
        /// Saves <see cref="EditedItems"/> to the underlying database.
        /// </summary>
        /// <param name="obj"></param>
        private void Commit(CommitMessage obj = null)
        {
            EditedItems.ForEach(_itemsProvider.UpdateOrInsert);
            EditedItems.Clear();
        }

        private void OnConfirmedEdit(ConfirmEditMessage<TDetail> message)
        {
            var item = message.Item;
            EditedItems.Add(item);
            Commit();

            PollDatabase(); // update time of last update
            ReloadTable();

            HideEditDialog();
        }

        private void OnCancelledEdit(CancelEditMessage message)
        {
            HideEditDialog();
        }

        private void OnEditItem(EditItemMessage<TDetail> message)
        {
            ShowEditDialog();
        }

        private void HideEditDialog()
        {
            EditDialogVisible = false;
        }

        private void ShowEditDialog()
        {
            EditDialogVisible = true;
        }

        private void OnFilterChanged(FilterChangedMessage<TListModel> message)
        {
            ChangeFilter(message.Conditions);
            if (IsTableLoaded) ReloadTable();
        }

        private void ChangeFilter(List<PropertyCondition<TListModel>> conditions)
        {
            _itemsProvider.UseConditions(conditions);
        }

        private void OnShowItem(ShowItemMessageGeneric<TListModel> message)
        {
            ItemSelectionChanged(message.Item);
        }

        public bool DatabaseChanged { get; private set; }

        public List<TDetail> EditedItems { get; set; }

        public VirtualizingCollection<TListModel> ItemsCollection { get; set; }

        public void OnConnectionChanged(ConnectMessage message)
        {
            if (_itemsProvider.DatabaseExists)
            {
                if (message.Connect)
                    LoadTable();
                else
                {
                    UnloadTable();
                }
            }
            else
            {
                MessengerInstance.Send(new ConnectionNotExistsMessage());
            }
        }

        public void LoadTable()
        {
            ItemsCollection = new VirtualizingCollection<TListModel>(_itemsProvider);
            MessengerInstance.Send(new CallBackWhenRenderedMessage<TListModel>(OnRenderedTable));
            _poller.Start();
        }

        private void OnRenderedTable()
        {
            SelectedIndex = ItemsCollection.IndexOf(_lastSelectedItem);
        }

        public void UnloadTable()
        {
            _poller.Stop();

            if (SelectedIndex != -1)
            {
                _lastSelectedItem = ItemsCollection[SelectedIndex];
            }
            else
            {
                _lastSelectedItem = null;
            }

            ItemsCollection = null;
            ItemSelectionChanged(default(TListModel));
        }

        private void OnPollerTick(object sender, EventArgs e)
        {
            PollDatabase();
        }

        public void ReloadTable()
        {
            var wasLoaded = IsTableLoaded;
            UnloadTable();
            if (wasLoaded) LoadTable();
            DatabaseChanged = false;
        }

        private int _selectedIndex;

        public int SelectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                _selectedIndex = value;
                Trace.WriteLine("Selected index changed to " + value);
            }
        }

        public SortTableCommand SortTableAscCommand
            => new SortTableCommand(SortTable, SortDirection.Ascending, () => IsTableLoaded);

        public SortTableCommand SortTableDescCommand
            => new SortTableCommand(SortTable, SortDirection.Descending, () => IsTableLoaded);

        private bool IsTableLoaded => ItemsCollection != null;

        private void SortTable(string columnName, SortDirection sortDirection)
        {
            _itemsProvider.SortBy(columnName, sortDirection);
            if (IsTableLoaded) ReloadTable();
        }

        public RelayCommand<TListModel> ItemSelectedCommand => new RelayCommand<TListModel>(ItemSelectionChanged);

        private void ItemSelectionChanged(TListModel item)
        {
            MessengerInstance.Send(new ItemSelectedMessage<TListModel>(item));
        }

        public RelayCommand AddNewCommand => new RelayCommand(AddNew, () => IsTableLoaded);

        public RelayCommand ReloadTableCommand => new RelayCommand(ReloadTable, () => IsTableLoaded);

        private void AddNew()
        {
            MessengerInstance.Send(new EditItemMessage<TDetail>(new TDetail()));
        }
    }
}