using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GFATask
{
    /// <summary>
    /// Eine Klasse zum zwischenspeichern von Einstellungen in den JSON-Models
    /// </summary>
    public class PreChanging
    {
        private Dictionary<Control, object> Temps { get; } = new Dictionary<Control, object>();

        public Dictionary<Control, JsonChange> PreChanges { get; } = new Dictionary<Control, JsonChange>();


        public PreChanging() { }
        
        public void GetTemps(IEnumerable<Control> tempedcontrols)
        {
            tempedcontrols.ToList().ForEach(control =>
            {
                object value = new object();
                if (control.GetType() == typeof(SwitchButton))
                {
                    value = (control as SwitchButton).Checked;
                }
                else if (control.GetType() == typeof(NumericEx))
                {
                    value = (control as NumericEx).Value;
                }
                else if (control.GetType() == typeof(ListBox))
                {
                    value = (control as ListBox).SelectedItem;
                }
                else if (control.GetType() == typeof(ComboBox))
                {
                    value = (control as ComboBox).SelectedItem;
                }
                else if (control.GetType() == typeof(AppListButton) && control.FindForm() != Forms.Settings)
                {
                    value = (control as AppListButton).AnimateImageBackColor;
                }
                this.AddToTemp(control, value);
            });
        }

        /// <summary>
        /// Speichert Änderungen vorab in einer Auflistung. Änderungen werden erst mit der Funktion SaveAll wirksam
        /// </summary>
        /// <param name="dictionary"></param>
        /// <param name="property"></param>
        /// <param name="propname"></param>
        /// <param name="value"></param>
        /// <param name="controlname">Bei Model-Eigenschaften mit dem gleichen Namen (z.B. Grouping, oder Show in den List-Eigenschaften von Config.User), muss der Steuerlementname angegeben werden</param>
        /// <param name="action">Eine Methode, die eine individualisierte Aktion ausführt</param>
        public void Add(object t, string property, Control control, object value, Action<object> action)
        {
            if (PreChanges.ContainsKey(control))
            {
                PreChanges[control] = new JsonChange(property, t, GetProperty(t, property), value, action);
                PreChanges[control].Commit();
            }
            else
            {
                this.AddToTemp(control, value);
                PreChanges.Add(control, new JsonChange(property, t, GetProperty(t, property), value, action));
                PreChanges[control].Commit();
            }
        }

        private void AddToTemp(Control control, object value)
        {
            if (!Temps.ContainsKey(control))
                Temps.Add(control, value);
        }


        public PropertyInfo GetProperty(object t, string PropertyName)
        {
            if (t.GetType().GetProperties().Count(p => p.Name == PropertyName.Split('.')[0]) == 0)
                throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertyName, t.ToString()));
            if (PropertyName.Split('.').Length == 1)
                return t.GetType().GetProperty(PropertyName);
            else
                return GetProperty(t.GetType().GetProperty(PropertyName.Split('.')[0]).GetValue(t, null), PropertyName.Split('.')[1]);
        }


        public void Reset(Control control) 
        {
            if (Temps.ContainsKey(control))
            {
                if (control.GetType() == typeof(SwitchButton))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as SwitchButton).Checked = (bool)value;
                    }
                }
                else if (control.GetType() == typeof(NumericEx))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as NumericEx).Value = decimal.Parse(value.ToString());
                    }
                }
                else if (control.GetType() == typeof(ListBox))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as ListBox).SelectedItem = value;
                    }
                }
                else if (control.GetType() == typeof(ComboBox))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as ComboBox).SelectedItem = value;
                    }
                }
                else if (control.GetType() == typeof(AppListButton) && control.FindForm() != Forms.Settings)
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as AppListButton).AnimateImageBackColor = (Color)value;
                    }
                }
                PreChanges.Remove(control);
            }
        }

        public void CommitAll()
        {
            // Anstatt Temps.Clear sollten die Steuerelement aus PreChanges die mit Temps übereinstimmen, den Wert aktualisiert bekommen
            this.DoItAll();
            this.PreChanges.ToList().ForEach(change => this.Temps[change.Key] = change.Value.Value );
            //this.Temps.Clear();
            this.PreChanges.Values.ToList().ForEach(change => change.Commit());
            this.PreChanges.Clear();
        }


        public void DoItAll()
        {
            // Anstatt Temps.Clear sollten die Steuerelement aus PreChanges die mit Temps übereinstimmen, den Wert aktualisiert bekommen
            this.PreChanges.ToList().ForEach(change =>
            {
                Temps[change.Key] = change.Value.Value;
                change.Value.DoIt();
            });
        }

        public void Commit(Control control)
        {
            this.Temps[control] = this.PreChanges[control].Value;
            this.PreChanges[control].Commit();
            this.PreChanges.Remove(control);
        }

        public void ResetAll()
        {
            Temps.ToList().ForEach(kv =>
            {
                Control control = kv.Key;
                if (control.GetType() == typeof(SwitchButton))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as SwitchButton).Checked = (bool)value;
                    }
                }
                else if (control.GetType() == typeof(NumericEx))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as NumericEx).Value = decimal.Parse(value.ToString());
                    }
                }
                else if (control.GetType() == typeof(ListBox))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        (control as ListBox).SelectedItem = value;
                    }
                }
                else if (control.GetType() == typeof(ComboBox))
                {
                    object value;
                    if (Temps.TryGetValue(control, out value))
                    {
                        if (value == null)
                            return;
                        (control as ComboBox).Text = value.ToString();
                    }
                }
            });
            PreChanges.Clear();
        }
    }


    /// <summary>
    /// Erzeugt eine Struktur von Änderungen in einer JSON-Datei, die vorab in einer Auflistung gespeichert werden
    /// </summary>
    public struct JsonChange
    {
        /// <summary>
        /// Eine Ausführbare Methode, die beim speichern des Elements ausgeführt werden kann
        /// </summary>
        public Action<object> Action { get; set; }


        /// <summary>
        /// Die Struktur des JSON-Models 
        /// </summary>
        public string Property { get; set; }


        public object PropType { get; set; }

        /// <summary>
        /// Name der Eigenschaft aus der JSON-Model-Struktur
        /// </summary>
        public PropertyInfo PropInfo { get; set; }

        /// <summary>
        /// Neuer Wert
        /// </summary>
        public object Value { get; set; }

        /// <summary>
        /// Erzeugt eine Struktur von Änderungen in einer JSON-Datei, die vorab in einer Auflistung gespeichert werden
        /// </summary>
        /// <param name="property">Die Struktur des JSON-Models </param>
        /// <param name="propinfo">Name der Eigenschaft aus der JSON-Model-Struktur</param>
        /// <param name="value">Neuer Wert</param>
        public JsonChange(string property, object proptype, PropertyInfo propinfo, object value, Action<object> action)
        {
            Property = property;
            PropType = proptype;
            PropInfo = propinfo;
            Value = value;
            Action = action;
        }

        /// <summary>
        /// Übernimmt die Änderung bis zum abschließenden Speichern in die Laufzeit-Eigenschaft
        /// </summary>
        public void Commit()
        {
            try
            {
                PropInfo.SetValue(PropType, Value);
            }
            catch (Exception ex)
            {
                Log.EventLog.Write(string.Format("Einstellung {0} nicht gespeichert", Property), LogClassification.CriticalError, ex);
                //Log.EventLog.Write(string.Format("Einstellung {0} nicht gespeichert", Property.ToString().Substring(0, Property.ToString().LastIndexOf("+")).Replace('+', '.') + "." + PropInfo.Name), LogClassification.CriticalError, ex);
            }
        }


        /// <summary>
        /// Führt eine individualisierte Aktion aus
        /// </summary>
        public void DoIt()
        {
            Action?.Invoke(Value);
        }
    }
}
