// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
//
//
// Description: The KeyGesture class is used by the developer to create Keyboard Input Bindings
//
//              See spec at : http://avalon/coreUI/Specs/Commanding%20--%20design.htm
//
//* KeyGesture class serves the purpose of Input Bindings for Keyboard Device.
//* one will be passing the instance of this around with CommandLink to represent Accelerator Keys
//
//

using System;
using System.Globalization;
using System.Windows.Input;
using System.Windows;
using System.Windows.Markup;
using System.ComponentModel;
using MS.Internal.PresentationCore;

namespace System.Windows.Input
{
    /// <summary>
    /// KeyGesture - Key and Modifier combination.
    ///              Can be set on properties of KeyBinding and RoutedCommand.
    /// </summary>
    [TypeConverter(typeof(KeyGestureConverter))]
    [ValueSerializer(typeof(KeyGestureValueSerializer))]
    public class KeyGesture : InputGesture
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
#region Constructors
        /// <summary>
        ///  constructor
        /// </summary>
        /// <param name="key">key</param>
        public KeyGesture(Key key)
            : this(key, ModifierKeys.None)
        {
        }

        /// <summary>
        ///  constructor
        /// </summary>
        /// <param name="modifiers">modifiers</param>
        /// <param name="key">key</param>
        public KeyGesture(Key key, ModifierKeys modifiers)
            : this(key, modifiers, String.Empty, true)
        {
        }

        /// <summary>
        ///  constructor
        /// </summary>
        /// <param name="modifiers">modifiers</param>
        /// <param name="key">key</param>
        /// <param name="displayString">display string</param>
        public KeyGesture(Key key, ModifierKeys modifiers, string displayString)
            : this(key, modifiers, displayString, true)
        {
        }


        /// <summary>
        /// Internal constructor used by KeyBinding to avoid key and modifier validation
        /// This allows setting KeyBinding.Key and KeyBinding.Modifiers without regard
        /// to order.
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="modifiers">Modifiers</param>
        /// <param name="validateGesture">If true, throws an exception if the key and modifier are not valid</param>
        internal KeyGesture(Key key, ModifierKeys modifiers, bool validateGesture)
            : this(key, modifiers, String.Empty, validateGesture)
        {
        }

        /// <summary>
        /// Private constructor that does the real work.
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="modifiers">Modifiers</param>
        /// <param name="displayString">display string</param>
        /// <param name="validateGesture">If true, throws an exception if the key and modifier are not valid</param>
        private KeyGesture(Key key, ModifierKeys modifiers, string displayString, bool validateGesture)
        {
            if(!ModifierKeysConverter.IsDefinedModifierKeys(modifiers))
                throw new InvalidEnumArgumentException("modifiers", (int)modifiers, typeof(ModifierKeys));

            if(!IsDefinedKey(key))
                throw new InvalidEnumArgumentException("key", (int)key, typeof(Key));

            ArgumentNullException.ThrowIfNull(displayString);

            if (validateGesture && !IsValid(key, modifiers))
            {
                throw new NotSupportedException(SR.Format(SR.KeyGesture_Invalid, modifiers, key));
            }

            _modifiers = modifiers;
            _key = key;
            _displayString = displayString;
        }
#endregion Constructors

        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
#region Public Methods
        /// <summary>
        /// Modifier
        /// </summary>
        public ModifierKeys Modifiers
        {
            get
            {
                return _modifiers;
            }
        }

        /// <summary>
        /// Key
        /// </summary>
        public Key Key
        {
            get
            {
                return _key;
            }
        }

        /// <summary>
        /// DisplayString
        /// </summary>
        public string DisplayString
        {
            get
            {
                return _displayString;
            }
        }

        /// <summary>
        /// Returns a string that can be used to display the KeyGesture.  If the
        /// DisplayString was set by the constructor, it is returned.  Otherwise
        /// a suitable string is created from the Key and Modifiers, with any
        /// conversions being governed by the given CultureInfo.
        /// </summary>
        /// <param name="culture">the culture used when creating a string from Key and Modifiers</param>
        public string GetDisplayStringForCulture(CultureInfo culture)
        {
            // return the DisplayString, if it was set by the ctor
            if (!String.IsNullOrEmpty(_displayString))
            {
                return _displayString;
            }

            // otherwise use the type converter
            return (string)_keyGestureConverter.ConvertTo(null, culture, this, typeof(string));
        }

        /// <summary>
        /// Compares InputEventArgs with current Input
        /// </summary>
        /// <param name="targetElement">the element to receive the command</param>
        /// <param name="inputEventArgs">inputEventArgs to compare to</param>
        /// <returns>True - KeyGesture matches, false otherwise.
        /// </returns>
        public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
        {
            KeyEventArgs keyEventArgs = inputEventArgs as KeyEventArgs;
            if(keyEventArgs != null && IsDefinedKey(keyEventArgs.Key))
            {
                return ( ( (int)Key == (int)keyEventArgs.RealKey ) && ( this.Modifiers == Keyboard.Modifiers ) );
            }
            return false;
        }

        // Check for Valid enum, as any int can be casted to the enum.
        internal static bool IsDefinedKey(Key key)
        {
            return (key >= Key.None && key <= Key.OemClear);
        }

#endregion Public Methods

        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
#region Internal Methods
        ///<summary>
        /// Is Valid Keyboard input to process for commands
        ///</summary>
        internal static bool IsValid(Key key, ModifierKeys modifiers)
        {
            //
            //  Don't enforce any rules on the Function keys or on the number pad keys.
            //
            if(!( ( key >= Key.F1 && key <= Key.F24 ) || ( key >= Key.NumPad0 && key <= Key.Divide ) ))
            {
                //
                //  We check whether Control/Alt/Windows key is down for modifiers. We don't check
                //  for shift at this time as Shift with any combination is already covered in above check.
                //  Shift alone as modifier case, we defer to the next condition to avoid conflicing with
                //  TextInput.

                if(( modifiers & ( ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows ) ) != 0)
                {
                    switch(key)
                    {
                        case Key.LeftCtrl:
                        case Key.RightCtrl:
                        case Key.LeftAlt:
                        case Key.RightAlt:
                        case Key.LWin:
                        case Key.RWin:
                            return false;

                        default:
                            return true;
                    }
                }
                else if(( key >= Key.D0 && key <= Key.D9 ) || ( key >= Key.A && key <= Key.Z ))
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Decode the strings keyGestures and displayStrings, creating a sequence
        /// of KeyGestures.  Add each KeyGesture to the given InputGestureCollection.
        /// The two input strings typically come from a resource file.
        /// </summary>
        internal static void AddGesturesFromResourceStrings(string keyGestures, string displayStrings, InputGestureCollection gestures)
        {
            while (!String.IsNullOrEmpty(keyGestures))
            {
                string keyGestureToken;
                string keyDisplayString;

                // break apart first gesture from the rest
                int index = keyGestures.IndexOf(MULTIPLEGESTURE_DELIMITER);
                if (index >= 0)
                {   // multiple gestures exist
                    keyGestureToken  = keyGestures.Substring(0, index);
                    keyGestures   = keyGestures.Substring(index + 1);
                }
                else
                {
                    keyGestureToken = keyGestures;
                    keyGestures = String.Empty;
                }

                // similarly, break apart first display string from the rest
                index = displayStrings.IndexOf(MULTIPLEGESTURE_DELIMITER);
                if (index >= 0)
                {   // multiple display strings exist
                    keyDisplayString  = displayStrings.Substring(0, index);
                    displayStrings   = displayStrings.Substring(index + 1);
                }
                else
                {
                    keyDisplayString = displayStrings;
                    displayStrings = String.Empty;
                }

                KeyGesture keyGesture = CreateFromResourceStrings(keyGestureToken, keyDisplayString);

                if (keyGesture != null)
                {
                    gestures.Add(keyGesture);
                }
            }
        }

        internal static KeyGesture CreateFromResourceStrings(string keyGestureToken, string keyDisplayString)
        {
            // combine the gesture and the display string, producing a string
            // that the type converter will recognize
            if (!String.IsNullOrEmpty(keyDisplayString))
            {
                keyGestureToken += KeyGestureConverter.DISPLAYSTRING_SEPARATOR + keyDisplayString;
            }

            return _keyGestureConverter.ConvertFromInvariantString(keyGestureToken) as KeyGesture;
        }

#endregion Internal Methods

        //------------------------------------------------------
        //
        //   Private Fields
        //
        //------------------------------------------------------
#region Private Fields
        private ModifierKeys   _modifiers = ModifierKeys.None;
        private Key            _key = Key.None;
        private string         _displayString;
        private const char MULTIPLEGESTURE_DELIMITER = ';';
        private static TypeConverter _keyGestureConverter = new KeyGestureConverter();
        //private static bool    _classRegistered = false;
#endregion Private Fields
    }
 }

