using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace BeWo.Controls
{
    public class MaskedTextBox : TextBox
    {
        /// <summary>
        /// Dependency property to store the mask to apply to the textbox
        /// </summary>
        public static readonly DependencyProperty MaskProperty = DependencyProperty.Register("Mask", typeof(string), typeof(MaskedTextBox), new UIPropertyMetadata(null, MaskChanged));

        // callback for when the Mask property is changed

        /// <summary>
        /// Static Constructor
        /// </summary>
        static MaskedTextBox()
        {
            // override the meta data for the Text Proeprty of the textbox 
            FrameworkPropertyMetadata metaData = new FrameworkPropertyMetadata();
            metaData.CoerceValueCallback = ForceText;
            TextProperty.OverrideMetadata(typeof(MaskedTextBox), metaData);
        }

        // force the text of the control to use the mask

        ///<summary>
        /// Default  constructor
        ///</summary>
        public MaskedTextBox()
        {
            // cancel the paste and cut command
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, null, CancelCommand));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, null, CancelCommand));
        }

        /// <summary>
        /// Gets or sets the mask to apply to the textbox
        /// </summary>
        public string Mask
        {
            get
            {
                return (string)this.GetValue(MaskProperty);
            }

            set
            {
                this.SetValue(MaskProperty, value);
            }
        }

        /// <summary>
        /// Gets the MaskTextProvider for the specified Mask
        /// </summary>
        public MaskedTextProvider MaskProvider
        {
            get
            {
                MaskedTextProvider maskProvider = null;
                if (this.Mask != null)
                {
                    maskProvider = new MaskedTextProvider(this.Mask);
                    maskProvider.Set(this.Text);
                }

                return maskProvider;
            }
        }

        // cancel the command

        /// <summary>
        /// override the key down to handle delete of a character
        /// </summary>
        /// <param name="e">Arguments for the event</param>
        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);
            MaskedTextProvider provider = this.MaskProvider;
            int position = this.SelectionStart;
            if (e.Key == Key.Delete && position < this.Text.Length)
            {
                // handle the delete key
                if (provider.RemoveAt(position))
                {
                    this.RefreshText(provider, position);
                }

                e.Handled = true;
            }
            else if (e.Key == Key.Space)
            {
                if (provider.InsertAt(" ", position))
                {
                    this.RefreshText(provider, position);
                }

                e.Handled = true;
            }
            else if (e.Key == Key.Back)
            {
                // handle the back space
                if (position > 0)
                {
                    position--;
                    if (provider.RemoveAt(position))
                    {
                        this.RefreshText(provider, position);
                    }
                }

                e.Handled = true;
            }
        }

        /// <summary>
        /// override this method to replace the characters enetered with the mask
        /// </summary>
        /// <param name="e">Arguments for event</param>
        protected override void OnPreviewTextInput(TextCompositionEventArgs e)
        {
            int position = this.SelectionStart;
            MaskedTextProvider provider = this.MaskProvider;
            if (position < this.Text.Length)
            {
                position = this.GetNextCharacterPosition(position);

                if (Keyboard.IsKeyToggled(Key.Insert))
                {
                    if (provider.Replace(e.Text, position))
                    {
                        position++;
                    }
                }
                else
                {
                    if (provider.InsertAt(e.Text, position))
                    {
                        position++;
                    }
                }

                position = this.GetNextCharacterPosition(position);
            }

            this.RefreshText(provider, position);
            e.Handled = true;

            base.OnPreviewTextInput(e);
        }

        private static void CancelCommand(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = false;
            e.Handled = true;
        }

        private static object ForceText(DependencyObject sender, object value)
        {
            MaskedTextBox textBox = (MaskedTextBox)sender;
            if (textBox.Mask != null)
            {
                MaskedTextProvider provider = new MaskedTextProvider(textBox.Mask);
                provider.Set((string)value);
                return provider.ToDisplayString();
            }
            else
            {
                return value;
            }
        }

        private static void MaskChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            // make sure to update the text if the mask changes
            MaskedTextBox textBox = (MaskedTextBox)sender;
            textBox.RefreshText(textBox.MaskProvider, 0);
        }

        // refreshes the text of the textbox

        // gets the next position in the textbox to move
        private int GetNextCharacterPosition(int startPosition)
        {
            int position = this.MaskProvider.FindEditPositionFrom(startPosition, true);
            if (position == -1)
            {
                return startPosition;
            }
            else
            {
                return position;
            }
        }

        private void RefreshText(MaskedTextProvider provider, int position)
        {
            this.Text = provider.ToDisplayString();
            this.SelectionStart = position;
        }
    }
}