// RibbonHotKey.cs
// Copyright (c) 2017 Bentley Systems, Incorporated. All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Haestad.Support.Support;

namespace Haestad.Framework.UI.Integration
{
    /// <summary>
    /// Class that defines a ribbon command hot key (e.g., CTRL+S).
    /// Not every possible hot key is supported; clients access RibbonHotKey instances by using public static properties on this class.
    /// Some hot key combinations are reserved for internal use (and have internal scope, by design).
    /// </summary>
    public class RibbonHotKey
    {
        #region Constructor
        /// <summary>
        /// Instantiates a new RibbonHotKey.
        /// This constructor is marked internal since no client should be directly instantiating a RibbonHotKey.
        /// Clients should leverage public static properties to get references to RibbonHotKey instances.
        /// </summary>
        /// <param name="modifierKey">The required modifier key or keys.</param>
        /// <param name="triggerKey">The required trigger key.</param>
        internal RibbonHotKey(ModifierKeys modifierKey, Key triggerKey)
        {
            ModifierKey = modifierKey;
            TriggerKey = triggerKey;
            CheckModifierKeyArgument(ModifierKey);
            HotKeyID = InstanceID++;
        }
        #endregion

        #region Public Override Methods
        public override string ToString()
        {
            if (m_toStringRepresentation != String.Empty)
                return m_toStringRepresentation;

            m_toStringRepresentation = RibbonHotKeyHelper.ToString(ModifierKey, TriggerKey);

            return m_toStringRepresentation;
        }
        #endregion

        #region Private Static Methods
        private static RibbonHotKey NewFunctionKeyHotKey(Key key)
        {
            return GetRibbonHotKey(ModifierKeys.None, key);
        }

        private static RibbonHotKey NewCtrlKeyHotKey(Key key)
        {
            return GetRibbonHotKey(ModifierKeys.Control, key);
        }

        private static RibbonHotKey NewAltKeyHotKey(Key key)
        {
            return GetRibbonHotKey(ModifierKeys.Alt, key);
        }

        private static RibbonHotKey NewCtrlShiftHotKey(Key key)
        {
            return GetRibbonHotKey(ModifierKeys.Control | ModifierKeys.Shift, key);
        }

        private static RibbonHotKey GetRibbonHotKey(ModifierKeys modifierKeys, Key key)
        {
            string keyString = RibbonHotKeyHelper.ToString(modifierKeys, key);

            if (!HotKeyCache.ContainsKey(keyString))
                HotKeyCache.Add(keyString, new RibbonHotKey(modifierKeys, key));

            return HotKeyCache[keyString];
        }
        #endregion

        #region Private Methods
        private void CheckModifierKeyArgument(ModifierKeys modifierKey)
        {
            if (modifierKey == ModifierKeys.Alt)
                return;
            if (modifierKey == ModifierKeys.Control)
                return;
            if (modifierKey == ModifierKeys.Shift)
                return;
            if (modifierKey == ModifierKeys.None)
                return;

            // Only this specific combination (CTRL+SHIFT) supported.
            if (((modifierKey & ModifierKeys.Control) > 0) 
                && ((modifierKey & ModifierKeys.Shift) > 0)
                && ((modifierKey & ModifierKeys.Alt) == 0)
                && ((modifierKey & ModifierKeys.Windows) == 0))
                return;

            throw new ArgumentException("RibbonHotKey does not support the specified modifier key or modifier key combination (modifierKey: " + modifierKey.ToString() + ")"); // I18N. OK.
        }
        #endregion

        #region Public Static Properties
        /// <summary>
        /// Returns a RibbonHotKey instance that represents no HotKey. Unlike other hotkeys, may be used mulltiple times.
        /// </summary>
        public static RibbonHotKey None
        {
            get { return GetRibbonHotKey(ModifierKeys.None, Key.None); }
        }

        public static RibbonHotKey F1
        {
            get { return NewFunctionKeyHotKey(Key.F1); }
        }

        public static RibbonHotKey F2
        {
            get { return NewFunctionKeyHotKey(Key.F2); }
        }

        // Note: <whar> F3 used internally.

        public static RibbonHotKey F4
        {
            get { return NewFunctionKeyHotKey(Key.F4); }
        }

        public static RibbonHotKey F5
        {
            get { return NewFunctionKeyHotKey(Key.F5); }
        }

        public static RibbonHotKey F6
        {
            get { return NewFunctionKeyHotKey(Key.F6); }
        }

        public static RibbonHotKey F7
        {
            get { return NewFunctionKeyHotKey(Key.F7); }
        }

        public static RibbonHotKey F8
        {
            get { return NewFunctionKeyHotKey(Key.F8); }
        }

        public static RibbonHotKey F9
        {
            get { return NewFunctionKeyHotKey(Key.F9); }
        }

        public static RibbonHotKey Ctrl0
        {
            get { return NewCtrlKeyHotKey(Key.D0); }
        }

        public static RibbonHotKey Ctrl1
        {
            get { return NewCtrlKeyHotKey(Key.D1); }
        }

        public static RibbonHotKey Ctrl2
        {
            get { return NewCtrlKeyHotKey(Key.D2); }
        }

        public static RibbonHotKey Ctrl3
        {
            get { return NewCtrlKeyHotKey(Key.D3); }
        }

        public static RibbonHotKey Ctrl4
        {
            get { return NewCtrlKeyHotKey(Key.D4); }
        }

        public static RibbonHotKey Ctrl5
        {
            get { return NewCtrlKeyHotKey(Key.D5); }
        }

        public static RibbonHotKey Ctrl6
        {
            get { return NewCtrlKeyHotKey(Key.D6); }
        }

        public static RibbonHotKey Ctrl7
        {
            get { return NewCtrlKeyHotKey(Key.D7); }
        }

        public static RibbonHotKey Ctrl8
        {
            get { return NewCtrlKeyHotKey(Key.D8); }
        }

        public static RibbonHotKey Ctrl9
        {
            get { return NewCtrlKeyHotKey(Key.D9); }
        }

        public static RibbonHotKey CtrlA
        {
            get { return NewCtrlKeyHotKey(Key.A); }
        }

        public static RibbonHotKey CtrlF
        {
            get { return NewCtrlKeyHotKey(Key.F); }
        }

        public static RibbonHotKey CtrlN
        {
            get { return NewCtrlKeyHotKey(Key.N); }
        }

        public static RibbonHotKey CtrlO
        {
            get { return NewCtrlKeyHotKey(Key.O); }
        }

        public static RibbonHotKey CtrlS
        {
            get { return NewCtrlKeyHotKey(Key.S); }
        }

        public static RibbonHotKey CtrlY
        {
            get { return NewCtrlKeyHotKey(Key.Y); }
        }

        public static RibbonHotKey CtrlZ
        {
            get { return NewCtrlKeyHotKey(Key.Z); }
        }

        public static RibbonHotKey CtrlF4
        {
            get { return NewCtrlKeyHotKey(Key.F4); }
        }

        public static RibbonHotKey CtrlF7
        {
            get { return NewCtrlKeyHotKey(Key.F7); }
        }

        public static RibbonHotKey CtrlF9
        {
            get { return NewCtrlKeyHotKey(Key.F9); }
        }

        public static RibbonHotKey Alt0
        {
            get { return NewAltKeyHotKey(Key.D0); }
        }

        public static RibbonHotKey Alt1
        {
            get { return NewAltKeyHotKey(Key.D1); }
        }

        public static RibbonHotKey Alt2
        {
            get { return NewAltKeyHotKey(Key.D2); }
        }

        public static RibbonHotKey Alt3
        {
            get { return NewAltKeyHotKey(Key.D3); }
        }

        public static RibbonHotKey Alt4
        {
            get { return NewAltKeyHotKey(Key.D4); }
        }

        public static RibbonHotKey Alt5
        {
            get { return NewAltKeyHotKey(Key.D5); }
        }

        public static RibbonHotKey Alt6
        {
            get { return NewAltKeyHotKey(Key.D6); }
        }

        public static RibbonHotKey Alt7
        {
            get { return NewAltKeyHotKey(Key.D7); }
        }

        public static RibbonHotKey Alt8
        {
            get { return NewAltKeyHotKey(Key.D8); }
        }

        public static RibbonHotKey Alt9
        {
            get { return NewAltKeyHotKey(Key.D9); }
        }

        public static RibbonHotKey AltF4
        {
            get { return NewAltKeyHotKey(Key.F4); }
        }

        public static RibbonHotKey CtrlShift0
        {
            get { return NewCtrlShiftHotKey(Key.D0); }
        }

        public static RibbonHotKey CtrlShift1
        {
            get { return NewCtrlShiftHotKey(Key.D1); }
        }

        public static RibbonHotKey CtrlShift2
        {
            get { return NewCtrlShiftHotKey(Key.D2); }
        }

        public static RibbonHotKey CtrlShift3
        {
            get { return NewCtrlShiftHotKey(Key.D3); }
        }

        public static RibbonHotKey CtrlShift4
        {
            get { return NewCtrlShiftHotKey(Key.D4); }
        }

        public static RibbonHotKey CtrlShift5
        {
            get { return NewCtrlShiftHotKey(Key.D5); }
        }

        public static RibbonHotKey CtrlShift6
        {
            get { return NewCtrlShiftHotKey(Key.D6); }
        }

        public static RibbonHotKey CtrlShift7
        {
            get { return NewCtrlShiftHotKey(Key.D7); }
        }

        public static RibbonHotKey CtrlShift8
        {
            get { return NewCtrlShiftHotKey(Key.D8); }
        }

        public static RibbonHotKey CtrlShift9
        {
            get { return NewCtrlShiftHotKey(Key.D9); }
        }
        #endregion

        #region Public Properties

        /// <summary>
        /// Returns whether this hot key is valid as a hot key.
        /// Originates from legacy implementations where clients could define custom hot key combinations through public constructors.
        /// Too many references to this property to do away with it (or change it to a method), plus it serves as a useful check when 
        /// adding new static properties for new key combinations. Futhermore the None hot key returns false for this, by design.
        /// Finally, as the unit test cases prove, it's not hard to instantiate the class using reflection and bypass the static factory
        /// properties, so in that case, if someone is so determined to do it, we can still make sure they the hot key they create is valid.
        /// (Don't take this as an invitation to use reflection; if you do you are responsible for ensuring that there is only ever one instance
        /// of your RibbonHotKey, else it could potentially clash with other hot keys).
        /// </summary>
        public bool IsValidShortCut
        {
            get { return RibbonHotKeyHelper.GetIsValidShortCut(ModifierKey, TriggerKey); }
        }
        #endregion

        #region Internal Static Properties
        /// <summary>
        /// Internal hot key used for Ribbon key tips.
        /// </summary>
        internal static RibbonHotKey LeftAlt
        {
            get { return GetRibbonHotKey(ModifierKeys.Alt, Key.LeftAlt);  }
        }

        /// <summary>
        /// Internal hot key used for Ribbon key tips.
        /// </summary>
        internal static RibbonHotKey RightAlt
        {
            get { return GetRibbonHotKey(ModifierKeys.Alt, Key.RightAlt); }
        }

        /// <summary>
        /// Internal hot key used for accessing the Ribbon backstage when key tips are not enabled.
        /// </summary>
        internal static RibbonHotKey AltF
        {
            get { return GetRibbonHotKey(ModifierKeys.Alt, Key.F); }
        }

        /// <summary>
        /// Internal hot key used for accessing Ribbon Search.
        /// </summary>
        internal static RibbonHotKey F3
        {
            get { return NewFunctionKeyHotKey(Key.F3); }
        }
        #endregion

        #region Internal Properties
        /// <summary>
        /// A unique instance ID issued to a non-temporary RibbonHotKey instance. Allows registration with system hot keys.
        /// </summary>
        internal int HotKeyID
        {
            get;
            private set;
        }
        #endregion

        #region Private Properties
        private Key TriggerKey
        {
            get;
            set;
        }

        private ModifierKeys ModifierKey
        {
            get;
            set;
        }

        /// <summary>
        /// Cache of instantiated RibbonHotKey instances. Ensures there is only ever one instance of a RibbonHotKey for any particular key combination.
        /// Each unique RibbonHotKey instance has its own unique session wide instance ID that can be used to register the hot key with the system
        /// (non global hook method).
        /// </summary>
        private static IDictionary<string, RibbonHotKey> HotKeyCache
        {
            get
            {
                if (m_hotKeyCache == null)
                    m_hotKeyCache = new Dictionary<string, RibbonHotKey>();
                return m_hotKeyCache;
            }
        }
        #endregion

        #region Private Static Properties
        private static int InstanceID
        {
            get;
            set;
        }
        #endregion

        #region Private Fields
        private string m_toStringRepresentation = String.Empty;
        private static IDictionary<string, RibbonHotKey> m_hotKeyCache;
        #endregion
    }
}
