#region << Using Directives >>
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using Volpe.Cafe.Settings;
using Volpe.Cafe.Utils;
#endregion
namespace Volpe.Cafe.UI.Panels
{
    public class CafeUserControl : UserControl
    {
        #region 
        public abstract class BaseTextControl
        {
            protected BaseTextControl(Control value, string errorMessage, bool ignoreErrorIfHiddenOrDisabled)
            {
                this.Value                         = value;
                this.ErrorMessage                  = errorMessage;
                this.IgnoreErrorIfHiddenOrDisabled = ignoreErrorIfHiddenOrDisabled;
            }
            public abstract bool HasChanged { get; }
            public abstract bool ValueValid { get; }
            public Control Value;
            public string  ErrorMessage;
            public bool    IgnoreErrorIfHiddenOrDisabled;
        }
        public class TextControl : BaseTextControl
        {
            public TextControl(Control value, string defaultValue, string[] validValues, string errorMessage,
                bool ignoreErrorIfHiddenOrDisabled)
                : base(value, errorMessage, ignoreErrorIfHiddenOrDisabled)
            {
                this.DefaultValue = defaultValue;
                this.ValidValues  = validValues;
            }
            public override bool HasChanged { get { return (this.Value.Text != this.DefaultValue); } }
            public override bool ValueValid
            {
                get
                {
                    if (this.ValidValues != null && this.ValidValues.Length == 0)
                    {
                        return Interaction.StringCompareAny(this.Value.Text, this.ValidValues, false);
                    }
                    return true;
                }
            }
            public string   DefaultValue;
            public string[] ValidValues;
        }
        public class NumericTextControl : BaseTextControl
        {
            public NumericTextControl(Control value, double defaultValue, double minValue, double maxValue, string errorMessage,
                bool ignoreErrorIfHiddenOrDisabled, string format)
                : base(value, errorMessage, ignoreErrorIfHiddenOrDisabled)
            {
                this.DefaultValue = defaultValue;
                this.MinValue     = minValue;
                this.MaxValue     = maxValue;
                this.Format       = format;
            }
            public override bool HasChanged { get { return (Interaction.GetDouble(this.Value.Text) != this.DefaultValue); } }
            public override bool ValueValid
            {
                get
                {
                    if (Interaction.IsNumeric(this.Value.Text))
                    {
                        double d = Interaction.GetDouble(this.Value.Text);
                        return ((double.IsNaN(this.MinValue) || d >= this.MinValue) &&
                                (double.IsNaN(this.MaxValue) || d <= this.MaxValue));
                    }
                    return false;
                }
            }
            public double DefaultValue;
            public double MinValue;
            public double MaxValue;
            public string Format;
        }
        #endregion
        #region 
        protected CafeUserControl()
        {
            this.InitializeComponent();
            this._messages            = new Messages();
            this._controlChanges      = new List<Control>();
            this._textControls        = new List<TextControl>();
            this._numericTextControls = new List<NumericTextControl>();
            this._visualTracking      = true;
            this._visualTrackingColor = Color.Blue;
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
        #endregion
        #region 
        #region 
        void InitializeComponent()
        {
            this.SuspendLayout();
            this.AutoScroll = true;
            this.Name = "CafeUserControl";
            this.ResumeLayout(false);
        }
        #endregion
        public virtual void SaveChanges()
        {
            Color defaultForeColor = Control.DefaultForeColor;
            for (int i = 0, count = this._controlChanges.Count; i < count; i++)
            {
                Control value = this._controlChanges[i];
                value.ForeColor = defaultForeColor;
            }
            for (int i = 0, count = this._textControls.Count; i < count; i++)
            {
                TextControl tc = this._textControls[i];
                if (tc.ValueValid) { tc.DefaultValue = tc.Value.Text; }
                else { this.RefreshRegisteredTextControl(tc); }
            }
            for (int i = 0, count = this._numericTextControls.Count; i < count; i++)
            {
                NumericTextControl ntc = this._numericTextControls[i];
                if (ntc.ValueValid) { ntc.DefaultValue = Interaction.GetDouble(ntc.Value.Text); }
                else { this.RefreshRegisteredTextControl(ntc); }
            }
            this._controlChanges.Clear();
        }
        protected void InitializeCheckBoxes(CheckBox parent, params CheckBox[] children)
        {
            parent.Tag = children;
            parent.CheckedChanged += new EventHandler(this.ParentCheckBox_CheckedChanged);
            for (int i = 0; i < children.Length; i++)
            {
                children[i].Tag = parent;
                children[i].CheckedChanged += new EventHandler(this.CheckChangesDetector);
                children[i].CheckedChanged += new EventHandler(this.ChildCheckBox_CheckedChanged);
            }
        }
        protected void ParentCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox   parent   = (CheckBox  )sender;
            CheckBox[] children = (CheckBox[])parent.Tag;
            for (int i = 0; i < children.Length; i++)
            {
                children[i].CheckedChanged -= new EventHandler(this.ChildCheckBox_CheckedChanged);
                if      (parent.CheckState == CheckState.Checked  ) { children[i].Checked = true ; }
                else if (parent.CheckState == CheckState.Unchecked) { children[i].Checked = false; }
                children[i].CheckedChanged += new EventHandler(this.ChildCheckBox_CheckedChanged);
            }
        }
        protected void ChildCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox   child    = (CheckBox  )sender;
            CheckBox   parent   = (CheckBox  )child .Tag;
            CheckBox[] children = (CheckBox[])parent.Tag;
            int count = 0;
            for (int i = 0; i < children.Length; i++)
            {
                if (children[i].Checked) { count++; }
            }
            if      (count == children.Length) { parent.CheckState = CheckState.Checked      ; }
            else if (count == 0              ) { parent.CheckState = CheckState.Unchecked    ; }
            else                               { parent.CheckState = CheckState.Indeterminate; }
        }
        protected void AutoInitializeCheckChangesDetector(Control.ControlCollection controls)
        {
            for (int i = 0, count = controls.Count; i < count; i++)
            {
                Control c = controls[i];
                if      (c is RadioButton) { ((RadioButton)c).CheckedChanged += new EventHandler(this.CheckChangesDetector); }
                else if (c is CheckBox   ) { ((CheckBox   )c).CheckedChanged += new EventHandler(this.CheckChangesDetector); }
                if (c.HasChildren) { this.AutoInitializeCheckChangesDetector(c.Controls); }
            }
        }
        protected void AutoInitializeCheckChangesDetector(params CheckBox[] controls)
        {
            for (int i = 0; i < controls.Length; i++)
            {
                controls[i].CheckedChanged += new EventHandler(this.CheckChangesDetector);
            }
        }
        protected void AutoInitializeCheckChangesDetector(params RadioButton[] controls)
        {
            for (int i = 0; i < controls.Length; i++)
            {
                controls[i].CheckedChanged += new EventHandler(this.CheckChangesDetector);
            }
        }
        protected void DeInitializeCheckChangesDetector(CheckBox control)
        {
            control.CheckedChanged -= new EventHandler(this.CheckChangesDetector);
        }
        protected void DeInitializeCheckChangesDetector(RadioButton control)
        {
            control.CheckedChanged -= new EventHandler(this.CheckChangesDetector);
        }
        protected void CheckChangesDetector(object sender, System.EventArgs e)
        {
            Control control = (Control)sender;
            if (this._controlChanges.Contains(control))
            {
                this._controlChanges.Remove(control);
                if (this._visualTracking) { control.ForeColor = Control.DefaultForeColor; }
            }
            else
            {
                this._controlChanges.Add(control);
                if (this._visualTracking) { control.ForeColor = this._visualTrackingColor; }
            }
        }
        protected bool IsRegistered(Control value)
        {
            return !(this.FindTextControl(value) == null && this.FindNumericTextControl(value) == null);
        }
        protected CafeUserControl.TextControl FindTextControl(Control value)
        {
            for (int i = 0, count = this._textControls.Count; i < count; i++)
            {
                if ((this._textControls[i]).Value == value)
                {
                    return this._textControls[i];
                }
            }
            return null;
        }
        protected CafeUserControl.NumericTextControl FindNumericTextControl(Control value)
        {
            for (int i = 0, count = this._numericTextControls.Count; i < count; i++)
            {
                if ((this._numericTextControls[i]).Value == value)
                {
                    return this._numericTextControls[i];
                }
            }
            return null;
        }
        protected void RefreshRegisteredTextControl(Control value)
        {
            if (this.IsRegistered(value))
            {
                this.RefreshRegisteredTextControl((BaseTextControl)value.Tag);
                if (value.Tag is NumericTextControl) { this.NumericTextControl_Leave  (value, EventArgs.Empty); }
            }
            else { throw new InvalidOperationException("The provided control was not registered."); }
        }
        protected void RegisterTextControl(Control value, string defaultValue, string[] validValues, string errorMessage,
            bool ignoreErrorIfHiddenOrDisabled)
        {
            if (this.IsRegistered(value)) { throw new InvalidOperationException("The provided control was already registered."); }
            TextControl tc = new TextControl(value, defaultValue, validValues, errorMessage, ignoreErrorIfHiddenOrDisabled);
            this._textControls.Add(tc);
            value.Tag = tc;
            value.TextChanged    += new EventHandler(this.TextControl_TextChanged);
            value.VisibleChanged += new EventHandler(this.TextControl_VisibleOrEnabledChanged);
            value.EnabledChanged += new EventHandler(this.TextControl_VisibleOrEnabledChanged);
        }
        protected void RegisterNumericTextControl(Control value, double defaultValue, double minValue, double maxValue,
            string errorMessage, bool ignoreErrorIfHiddenOrDisabled, string format)
        {
            if (this.IsRegistered(value)) { throw new InvalidOperationException("The provided control was already registered."); }
            NumericTextControl tc = new NumericTextControl(value, defaultValue, minValue, maxValue, errorMessage,
                ignoreErrorIfHiddenOrDisabled, format);
            this._numericTextControls.Add(tc);
            value.Tag = tc;
            value.TextChanged    += new EventHandler(this.TextControl_TextChanged);
            value.VisibleChanged += new EventHandler(this.TextControl_VisibleOrEnabledChanged);
            value.EnabledChanged += new EventHandler(this.TextControl_VisibleOrEnabledChanged);
            value.Leave          += new EventHandler(this.NumericTextControl_Leave);
            value.KeyPress       += new KeyPressEventHandler(this.NumericTextControl_KeyPress);
        }
        void TextControl_TextChanged(object sender, System.EventArgs e)
        {
            BaseTextControl tc = (BaseTextControl)((Control)sender).Tag;
            this.RefreshRegisteredTextControl(tc);
        }
        void TextControl_VisibleOrEnabledChanged(object sender, EventArgs e)
        {
            BaseTextControl tc = (BaseTextControl)((Control)sender).Tag;
            this.RefreshRegisteredTextControl(tc);
        }
        void RefreshRegisteredTextControl(BaseTextControl tc)
        {
            this._messages.RemoveMessage(tc.Value);
            if (this._visualTracking)
            {
                tc.Value.ForeColor = Control.DefaultForeColor;
                tc.Value.BackColor = Color.Empty;
            }
            this._controlChanges.Remove(tc.Value);
            if (tc.ValueValid && tc.HasChanged)
            {   
                if (this._visualTracking) { tc.Value.ForeColor = this._visualTrackingColor; }
                this._controlChanges.Add(tc.Value);
            }
            else if (!tc.ValueValid)
            {   
                if (!tc.IgnoreErrorIfHiddenOrDisabled || (tc.Value.Visible && tc.Value.Enabled))
                {   
                    string err = tc.ErrorMessage;
                    if (err != null && err != string.Empty)
                    {
                        if (tc is NumericTextControl)
                        {
                            NumericTextControl ntc = (NumericTextControl)tc;
                            if (err.IndexOf("{MIN}") != -1) { err = err.Replace("{MIN}", ntc.MinValue.ToString()); }
                            if (err.IndexOf("{MAX}") != -1) { err = err.Replace("{MAX}", ntc.MaxValue.ToString()); }
                        }
                        this._messages.AddMessage(err, null, Color.Red, tc.Value);
                    }
                    if (this._visualTracking)
                    {
                        tc.Value.ForeColor = Color.Red;
                        tc.Value.BackColor = Color.Honeydew;
                    }
                }
            }
        }
        void NumericTextControl_Leave(object sender, EventArgs e)
        {
            object tag = ((Control)sender).Tag;
            if (tag is NumericTextControl)
            {
                NumericTextControl tc = (NumericTextControl)tag;
                if (tc.Format != null && tc.Format != string.Empty && Interaction.IsNumeric(tc.Value.Text))
                {
                    tc.Value.Text = Interaction.GetDouble(tc.Value.Text).ToString(tc.Format);
                }
            }
        }
        void NumericTextControl_KeyPress(object sender, KeyPressEventArgs e)
        {
            const char backspace = (char)8;
            const char negative  = (char)45;
            const char period    = (char)46;
            char       key       = e.KeyChar;
            e.Handled = !char.IsDigit(key) && key != backspace && key != negative && key != period;
        }
        #endregion
        #region 
        [Browsable(false)]
        public Messages Messages { get { return this._messages; } }
        [Browsable(false)]
        public virtual bool SettingsChanged { get { return this._controlChanges.Count != 0; } }
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Indicates whether settings, which have changed since the last save, will be color-coded.")]
        [DefaultValue(true)]
        public bool VisualTracking { get { return this._visualTracking; } set { this._visualTracking = value; } }
        [Browsable(true)]
        [Category("Behavior")]
        [Description("Determines the color to use when color-coding settings that have changed since the last save.")]
        [DefaultValue(typeof(Color), "Blue")]
        public Color VisualTrackingColor { get { return this._visualTrackingColor; } set { this._visualTrackingColor = value; } }
        #endregion
        #region 
        protected Messages _messages;
        protected List<Control> _controlChanges;
        protected List<TextControl> _textControls;
        protected List<NumericTextControl> _numericTextControls;
        protected bool _visualTracking;
        protected Color _visualTrackingColor;
        #endregion
    }
}

