using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using TimeScheduler.Common;
using TimeScheduler.Domain;
using TimeScheduler.Model;

namespace TimeScheduler.ViewModel
{
  class MainViewModel : NotifyableObject
  {
    #region Konstruktion
    /// <summary>aktuelles Ele</summary>
    private ITimeItem lastTimeItem_;
    /// <summary>Zyklische Aktuallisierung</summary>
    private System.Timers.Timer timer_;
    /// <summary>Funktion zum ermitteln der aktuellen Uhrzeit</summary>
    protected Func<DateTime> currentTime_ = new Func<DateTime>(() => DateTime.Now);

    /// <summary>Standard-Konstruktor</summary>
    public MainViewModel() : this(new Domain.Impl.FileDomain()) { Refresh(); }

    /// <summary>Konstruktor</summary>
    /// <param name="provider"></param>
    public MainViewModel(IDomain provider)
    {
      Provider = provider;

      // Elementliste aufbauen
      TimeItems = new ObservableCollection<ITimeItem>();
      TimeItems.CollectionChanged += TimeItems_CollectionChanged;
      var view = System.Windows.Data.CollectionViewSource.GetDefaultView(TimeItems);
      view.SortDescriptions.Add(new System.ComponentModel.SortDescription("From", System.ComponentModel.ListSortDirection.Ascending));

      // Ereignis zum autmoatisierten speichern konsumieren
      Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
      Microsoft.Win32.SystemEvents.SessionEnded += SystemEvents_SessionEnded;
      Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;

      // Kommandos erzeugen
      CreateCommands();

      // Zyklischer Timer aufbauen
      timer_ = new System.Timers.Timer(10000);
      timer_.Elapsed += Timer_Elapsed;
      timer_.Start();
    }

    private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
    {
      try
      {
        switch (e.Mode)
        {
          case Microsoft.Win32.PowerModes.Resume: break;
          case Microsoft.Win32.PowerModes.StatusChange: break;
          // Beim wechseln in Suspend, ein neues Element erzeugen, wenn das vorhergehende lang genug ist
          case Microsoft.Win32.PowerModes.Suspend: CreateNewLastItem(); break;
        }
      }
      catch (Exception exc) { }
    }
    private void SystemEvents_SessionEnded(object sender, Microsoft.Win32.SessionEndedEventArgs e) { UpdateLastItem(); }
    private void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e)
    {
      try
      {
        switch (e.Reason)
        {
          case Microsoft.Win32.SessionSwitchReason.ConsoleConnect: break;
          case Microsoft.Win32.SessionSwitchReason.ConsoleDisconnect: break;
          case Microsoft.Win32.SessionSwitchReason.RemoteConnect: break;
          case Microsoft.Win32.SessionSwitchReason.RemoteDisconnect: break;
          case Microsoft.Win32.SessionSwitchReason.SessionLogon: break;
          case Microsoft.Win32.SessionSwitchReason.SessionLogoff: break;
          // Beim Sperren, eine neue Session beginnen
          case Microsoft.Win32.SessionSwitchReason.SessionLock: CreateNewLastItem(); break;
          case Microsoft.Win32.SessionSwitchReason.SessionUnlock: break;
          case Microsoft.Win32.SessionSwitchReason.SessionRemoteControl: break;
        }
      }
      catch (Exception exc) { }
    }
    private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { UpdateLastItem(); }


    /// <summary>Datenbeschaffer</summary>
    protected IDomain Provider { get; private set; }
    #endregion

    #region Eigenschaften
    private DateTime selectedDate_;
    /// <summary>Beinhaltet den selektierten Tag</summary>
    public DateTime SelectedDate
    {
      get { return selectedDate_; }
      set
      {
        if (SetField(ref selectedDate_, value))
          RefreshForDate(selectedDate_);
      }
    }

    private Dictionary<int, string> costUnits_;
    /// <summary>Liste der vorhanden Kostenstellen</summary>
    public Dictionary<int, string> CostUnits { get { return costUnits_; } private set { SetField(ref costUnits_, value); } }

    private Dictionary<TimeItemType, string> itemTypes_;
    /// <summary>Liste der vorhanden Typen von TimeItem</summary>
    public Dictionary<TimeItemType, string> ItemTypes { get { return itemTypes_; } private set { SetField(ref itemTypes_, value); } }

    private ObservableCollection<ITimeItem> timeItems_;
    /// <summary>Beinhaltet die Elemente des aktuell ausgewählten Tages</summary>
    public ObservableCollection<ITimeItem> TimeItems { get { return timeItems_; } private set { SetField(ref timeItems_, value); } }

    private ITimeItem selectedTimeItem_;
    /// <summary>Das aktuell selektierte Element, von dem weitere Details angezeigt werden sollen</summary>
    public ITimeItem SelectedTimeItem { get { return selectedTimeItem_; } set { SetField(ref selectedTimeItem_, value); } }
    #endregion

    #region Kommandos
    private void CreateCommands()
    {
      RefreshCommand = new RelayCommand(Refresh);
      AddNewCommand = new RelayCommand(AddNew);
      DeleteCommand = new RelayCommand(Delete, CanDelete);
      SaveCommand = new RelayCommand(Save);
    }

    public ICommand RefreshCommand { get; private set; }
    private void Refresh()
    {
      // Standardwerte belegen
      CostUnits = Provider.GetCostUnits();
      var it = new Dictionary<TimeItemType, string>();
      foreach (TimeItemType tit in Enum.GetValues(typeof(TimeItemType)))
        it[tit] = Enum.GetName(typeof(TimeItemType), tit);
      ItemTypes = it;

      // Andere Daten ermitteln
      SelectedDate = currentTime_().Date;
      //RefreshForDate(SelectedDate);

      // Letztes Element ermitteln, auf dem weiter gelaufen wird
      var lastItem = TimeItems.OrderByDescending(x => x.From).FirstOrDefault();
      if (lastItem == null || lastItem.From.Date != SelectedDate.Date)
      {// Wenn keines gefunden wurde oder keins für den heutigen Tag gefunden wurde, dann eins neu erzeugen
        lastItem = Provider.NewItem();
        lastItem.From = currentTime_();
        TimeItems.Add(lastItem);
      }
      // und abspeichern
      lastTimeItem_ = lastItem;
      lastTimeItem_.IsCurrentItem = true;
      // Und direkt den aktuellen Wert speichern
      UpdateLastItem();
    }
    private void RefreshForDate(DateTime date)
    {
      foreach (var item in TimeItems)
        item.PropertyChanged -= Item_PropertyChanged;

      // Laufende Datein belegen
      var items = Provider.GetItems(date);

      // Event abhängen, damit keine Änderungen an den Provider geschickt werden
      TimeItems.CollectionChanged -= TimeItems_CollectionChanged;

      try
      {
        // Event der Objekte entfernen
        foreach (var item in TimeItems)
          item.PropertyChanged -= Item_PropertyChanged;

        // Elemente umlagern
        TimeItems.Clear();
        foreach (var item in items)
        {
          item.PropertyChanged += Item_PropertyChanged;
          TimeItems.Add(item);
        }
      }
      finally
      {
        // Event wieder anhängen
        TimeItems.CollectionChanged += TimeItems_CollectionChanged;
      }

      SelectedTimeItem = TimeItems.FirstOrDefault();
    }

    public ICommand AddNewCommand { get; private set; }
    private void AddNew() { CreateNewLastItem(); }

    public ICommand DeleteCommand { get; private set; }
    private void Delete() { TimeItems.Remove(SelectedTimeItem); }
    public bool CanDelete() { return SelectedTimeItem != null; }

    public ICommand SaveCommand { get; private set; }
    private void Save() { UpdateLastItem(); }
    #endregion

    #region Hilfsfunktionen
    /// <summary>Schließt das aktuelle "Letzte Element" ab und erzeugt ein neues, aber nur wenn das aktuelle >15min ist</summary>
    private void CreateNewLastItem()
    {
      // Aktuelles Element speichern
      UpdateLastItem();

      // Wenn ein letztes Element existiert und dies eine Dauer >15min besitzt, dann erst ein neues erzeugen
      if (lastTimeItem_ != null && (lastTimeItem_.Till - lastTimeItem_.From) > TimeSpan.FromMinutes(15))
      {
        // neues Element erzeugen und belegen
        var newItem = Provider.NewItem();
        newItem.From = lastTimeItem_.Till;
        newItem.IsCurrentItem = true;
        // Aufnehmen und altes LastItem durch neues ersetzen
        TimeItems.Add(newItem);
        lastTimeItem_.IsCurrentItem = false;
        lastTimeItem_ = newItem;
      }
    }

    /// <summary>Aktualisiert das "Letzte Element"</summary>
    private void UpdateLastItem()
    {
      if (lastTimeItem_ != null)
        lastTimeItem_.Till = currentTime_();
    }
    #endregion  

    #region Ereignisse
    private void TimeItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
      if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
      {
        foreach (ITimeItem item in e.NewItems)
          item.Key = Provider.ElementAdded(item);
      }
      else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
      {
        foreach (ITimeItem item in e.OldItems)
          Provider.ElementRemoved(item);
      }
    }

    private void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
      Provider.ElementChanged((ITimeItem)sender);
    }
    #endregion
  }
}
