// 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.

using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using static Interop;

namespace System.Windows.Forms
{
    /// <summary>
    ///  Control's IME feature.
    /// </summary>
    public partial class Control
    {
        /// <summary>
        ///  Constants starting/ending the WM_CHAR messages to ignore count.  See ImeWmCharsToIgnore property.
        /// </summary>
        private const int ImeCharsToIgnoreDisabled = -1;
        private const int ImeCharsToIgnoreEnabled = 0;

        /// <summary>
        ///  The ImeMode value for controls with ImeMode = ImeMode.NoControl.  See PropagatingImeMode property.
        /// </summary>
        private static ImeMode propagatingImeMode = ImeMode.Inherit; // Inherit means uninitialized.

        /// <summary>
        ///  This flag prevents resetting ImeMode value of the focused control.  See IgnoreWmImeNotify property.
        /// </summary>
        private static bool ignoreWmImeNotify;

        /// <summary>
        ///  The ImeMode in the property store.
        /// </summary>
        internal ImeMode CachedImeMode
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_CachedImeMode(), this = " + this);
                Debug.Indent();

                // Get the ImeMode from the property store
                //
                ImeMode cachedImeMode = (ImeMode)Properties.GetInteger(s_imeModeProperty, out bool found);
                if (!found)
                {
                    cachedImeMode = DefaultImeMode;
                    Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "using DefaultImeMode == " + cachedImeMode);
                }
                else
                {
                    Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "using property store ImeMode == " + cachedImeMode);
                }

                // If inherited, get the mode from this control's parent
                //
                if (cachedImeMode == ImeMode.Inherit)
                {
                    Control? parent = ParentInternal;
                    if (parent is not null)
                    {
                        cachedImeMode = parent.CachedImeMode;
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "inherited from parent = " + parent.GetType());
                    }
                    else
                    {
                        cachedImeMode = ImeMode.NoControl;
                    }
                }

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "returning CachedImeMode == " + cachedImeMode);
                Debug.Unindent();

                return cachedImeMode;
            }

            set
            {
                // When the control is in restricted mode (!CanEnableIme) the CachedImeMode should be changed only programatically,
                //           calls generated by user interaction should be wrapped with a check for CanEnableIme.

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_CachedImeMode(), this = " + this);
                Debug.Indent();

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Warning, "Setting cached Ime == " + value);
                Properties.SetInteger(s_imeModeProperty, (int)value);

                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Specifies whether the ImeMode property value can be changed to an active value.
        ///  Added to support Password &amp; ReadOnly (and maybe other) properties, which when set, should force disabling
        ///  the IME if using one.
        /// </summary>
        protected virtual bool CanEnableIme
        {
            get
            {
                // Note: If overriding this property make sure to add the Debug tracing code and call this method (base.CanEnableIme).

                Debug.Indent();
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside get_CanEnableIme(), value = {0}, this = {1}", ImeSupported, this));
                Debug.Unindent();

                return ImeSupported;
            }
        }

        /// <summary>
        ///  Gets the current IME context mode.  If no IME associated, ImeMode.Inherit is returned.
        /// </summary>
        internal ImeMode CurrentImeContextMode
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_CurrentImeContextMode(), this = " + this);

                if (IsHandleCreated)
                {
                    return ImeContext.GetImeMode(Handle);
                }
                else
                {
                    // window is not yet created hence no IME associated yet.
                    return ImeMode.Inherit;
                }
            }
        }

        protected virtual ImeMode DefaultImeMode
        {
            get { return ImeMode.Inherit; }
        }

        /// <summary>
        ///  Flag used to avoid re-entrancy during WM_IME_NOTFIY message processing - see WmImeNotify().
        ///  Also to avoid raising the ImeModeChanged event more than once during the process of changing the ImeMode.
        /// </summary>
        internal int DisableImeModeChangedCount
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_DisableImeModeChangedCount()");
                Debug.Indent();

                int val = (int)Properties.GetInteger(s_disableImeModeChangedCountProperty, out bool dummy);

                Debug.Assert(val >= 0, "Counter underflow.");
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value: " + val);
                Debug.Unindent();

                return val;
            }
            set
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_DisableImeModeChangedCount(): " + value);
                Properties.SetInteger(s_disableImeModeChangedCountProperty, value);
            }
        }

        /// <summary>
        ///  Flag used to prevent setting ImeMode in focused control when losing focus and hosted in a non-Form shell.
        ///  See WmImeKillFocus() for more info.
        /// </summary>
        private static bool IgnoreWmImeNotify
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_IgnoreWmImeNotify()");
                Debug.Indent();

                bool val = Control.ignoreWmImeNotify;

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value: " + val);
                Debug.Unindent();

                return val;
            }
            set
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_IgnoreWmImeNotify(): " + value);
                Control.ignoreWmImeNotify = value;
            }
        }

        /// <summary>
        ///  Specifies a value that determines the IME (Input Method Editor) status of the
        ///  object when that object is selected.
        /// </summary>
        [SRCategory(nameof(SR.CatBehavior))]
        [Localizable(true)]
        [AmbientValue(ImeMode.Inherit)]
        [SRDescription(nameof(SR.ControlIMEModeDescr))]
        public ImeMode ImeMode
        {
            get
            {
                ImeMode imeMode = ImeModeBase;

                if (imeMode == ImeMode.OnHalf) // This is for compatibility.  See QFE#4448.
                {
                    imeMode = ImeMode.On;
                }

                return imeMode;
            }
            set
            {
                ImeModeBase = value;
            }
        }

        /// <summary>
        ///  Internal version of ImeMode property.  This is provided for controls that override CanEnableIme and that
        ///  return ImeMode.Disable for the ImeMode property when CanEnableIme is false - See TextBoxBase controls.
        /// </summary>
        protected virtual ImeMode ImeModeBase
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_ImeModeBase(), this = " + this);
                Debug.Indent();

                ImeMode imeMode = CachedImeMode;

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value = " + imeMode);
                Debug.Unindent();

                return imeMode;
            }
            set
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside set_ImeModeBase({0}), this = {1}", value, this));
                Debug.Indent();

                //valid values are -1 to 0xb
                SourceGenerated.EnumValidator.Validate(value);

                ImeMode oldImeMode = CachedImeMode;
                CachedImeMode = value;

                if (oldImeMode != value)
                {
                    // Cache current value to determine whether we need to raise the ImeModeChanged.
                    Control? ctl = null;

                    if (!DesignMode && ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable)
                    {
                        // Set the context to the new value if control is focused.
                        if (Focused)
                        {
                            ctl = this;
                        }
                        else if (ContainsFocus)
                        {
                            ctl = FromChildHandle(User32.GetFocus());
                        }

                        if (ctl is not null && ctl.CanEnableIme)
                        {
                            // Block ImeModeChanged since we are checking for it below.
                            DisableImeModeChangedCount++;

                            try
                            {
                                ctl.UpdateImeContextMode();
                            }
                            finally
                            {
                                DisableImeModeChangedCount--;
                            }
                        }
                    }

                    VerifyImeModeChanged(oldImeMode, CachedImeMode);
                }

                ImeContext.TraceImeStatus(this);
                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Determines whether the Control supports IME handling by default.
        /// </summary>
        private bool ImeSupported
        {
            get
            {
                return DefaultImeMode != ImeMode.Disable;
            }
        }

        [WinCategory("Behavior")]
        [SRDescription(nameof(SR.ControlOnImeModeChangedDescr))]
        public event EventHandler ImeModeChanged
        {
            add => Events.AddHandler(s_imeModeChangedEvent, value);
            remove => Events.RemoveHandler(s_imeModeChangedEvent, value);
        }

        /// <summary>
        ///  Returns the current number of WM_CHAR messages to ignore after processing corresponding WM_IME_CHAR msgs.
        /// </summary>
        internal int ImeWmCharsToIgnore
        {
            // The IME sends WM_IME_CHAR messages for each character in the composition string, and then
            // after all messages are sent, corresponding WM_CHAR messages are also sent. (in non-unicode
            // windows two WM_CHAR messages are sent per char in the IME).  We need to keep a counter
            // not to process each character twice or more.
            get
            {
                return Properties.GetInteger(s_imeWmCharsToIgnoreProperty);
            }
            set
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside set_ImeWmCharToIgnore(value={0}), this = {1}", value, this));
                Debug.Indent();

                // WM_CHAR is not send after WM_IME_CHAR when the composition has been closed by either, changing the conversion mode or
                // dissasociating the IME (for instance when loosing focus and conversion is forced to complete).
                if (ImeWmCharsToIgnore != ImeCharsToIgnoreDisabled)
                {
                    Properties.SetInteger(s_imeWmCharsToIgnoreProperty, value);
                }

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImeWmCharsToIgnore on leaving setter: " + ImeWmCharsToIgnore);
                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Gets the last value CanEnableIme property when it was last checked for ensuring IME context restriction mode.
        ///  This is used by controls that implement some sort of IME restriction mode (like TextBox on Password/ReadOnly mode).
        ///  See the VerifyImeRestrictedModeChanged() method.
        /// </summary>
        private bool LastCanEnableIme
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_LastCanEnableIme()");
                Debug.Indent();

                int val = (int)Properties.GetInteger(s_lastCanEnableImeProperty, out bool valueFound);

                if (valueFound)
                {
                    valueFound = val == 1;
                }
                else
                {
                    valueFound = true;
                }

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value: " + valueFound);
                Debug.Unindent();

                return valueFound;
            }
            set
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_LastCanEnableIme(): " + value);
                Properties.SetInteger(s_lastCanEnableImeProperty, value ? 1 : 0);
            }
        }

        /// <summary>
        ///  Represents the internal ImeMode value for controls with ImeMode = ImeMode.NoControl.  This property is changed
        ///  only by user interaction and is required to set the IME context appropriately while keeping the ImeMode property
        ///  unchanged.
        /// </summary>
        protected static ImeMode PropagatingImeMode
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_PropagatingImeMode()");
                Debug.Indent();

                if (Control.propagatingImeMode == ImeMode.Inherit)
                {
                    // Initialize the propagating IME mode to the value the IME associated to the focused window currently has,
                    // this enables propagating the IME mode from/to unmanaged applications hosting winforms controls.
                    Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Initializing PropagatingImeMode");

                    ImeMode imeMode = ImeMode.Inherit;
                    IntPtr focusHandle = User32.GetFocus();

                    if (focusHandle != IntPtr.Zero)
                    {
                        imeMode = ImeContext.GetImeMode(focusHandle);

                        // If focused control is disabled we won't be able to get the app ime context mode, try the top window.
                        // this is the case of a disabled winforms control hosted in a non-Form shell.
                        if (imeMode == ImeMode.Disable)
                        {
                            focusHandle = User32.GetAncestor(focusHandle, User32.GA.ROOT);

                            if (focusHandle != IntPtr.Zero)
                            {
                                imeMode = ImeContext.GetImeMode(focusHandle);
                            }
                        }
                    }

                    // If IME is disabled the PropagatingImeMode will not be initialized, see property setter below.
                    PropagatingImeMode = imeMode;
                }

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Value: " + Control.propagatingImeMode);
                Debug.Unindent();

                return Control.propagatingImeMode;
            }
            private set
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_PropagatingImeMode()");
                Debug.Indent();

                if (Control.propagatingImeMode != value)
                {
                    switch (value)
                    {
                        case ImeMode.NoControl:
                        case ImeMode.Disable:
                            // Cannot set propagating ImeMode to one of these values.
                            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Cannot change PropagatingImeMode to " + value);
                            return;

                        default:
                            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Warning, string.Format(CultureInfo.CurrentCulture, "Setting PropagatingImeMode: Current value = {0}, New value = {1}", propagatingImeMode, value));
                            Control.propagatingImeMode = value;
                            break;
                    }
                }

                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Sets the IME context to the appropriate ImeMode according to the control's ImeMode state.
        ///  This method is commonly used when attaching the IME to the control's window.
        /// </summary>
        internal void UpdateImeContextMode()
        {
            ImeMode[] inputLanguageTable = ImeModeConversion.InputLanguageTable;
            if (!DesignMode && (inputLanguageTable != ImeModeConversion.UnsupportedTable) && Focused)
            {
                // Note: CHN IME won't send WM_IME_NOTIFY msg when getting associated, setting the IME context mode
                // forces the message to be sent as a side effect.

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside UpdateImeContextMode(), this = " + this);
                Debug.Indent();

                // If the value is not supported by the Ime, it will be mapped to a corresponding one, we need
                // to update the cached ImeMode to the actual value.

                ImeMode newImeContextMode = ImeMode.Disable;
                ImeMode currentImeMode = CachedImeMode;

                if (ImeSupported && CanEnableIme)
                {
                    newImeContextMode = currentImeMode == ImeMode.NoControl ? PropagatingImeMode : currentImeMode;
                }

                // If PropagatingImeMode has not been initialized it will return ImeMode.Inherit above, need to check newImeContextMode for this.
                if (CurrentImeContextMode != newImeContextMode && newImeContextMode != ImeMode.Inherit)
                {
                    // If the context changes the window will receive one or more WM_IME_NOTIFY messages and as part of its
                    // processing it will raise the ImeModeChanged event if needed.  We need to prevent the event from been
                    // raised here from here.
                    DisableImeModeChangedCount++;

                    // Setting IME status to Disable will first close the IME and then disable it.  For CHN IME, the first action will
                    // update the PropagatingImeMode to ImeMode.Close which is incorrect.  We need to save the PropagatingImeMode in
                    // this case and restore it after the context has been changed.
                    // Also this call here is very important since it will initialize the PropagatingImeMode if not already initialized
                    // before setting the IME context to the control's ImeMode value which could be different from the propagating value.
                    ImeMode savedPropagatingImeMode = PropagatingImeMode;

                    try
                    {
                        ImeContext.SetImeStatus(newImeContextMode, Handle);
                    }
                    finally
                    {
                        DisableImeModeChangedCount--;

                        if (newImeContextMode == ImeMode.Disable && inputLanguageTable == ImeModeConversion.ChineseTable)
                        {
                            // Restore saved propagating mode.
                            PropagatingImeMode = savedPropagatingImeMode;
                        }
                    }

                    // Get mapped value from the context.
                    if (currentImeMode == ImeMode.NoControl)
                    {
                        if (CanEnableIme)
                        {
                            PropagatingImeMode = CurrentImeContextMode;
                        }
                    }
                    else
                    {
                        if (CanEnableIme)
                        {
                            CachedImeMode = CurrentImeContextMode;
                        }

                        // Need to raise the ImeModeChanged event?
                        VerifyImeModeChanged(newImeContextMode, CachedImeMode);
                    }
                }

                ImeContext.TraceImeStatus(this);
                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Checks if specified ImeMode values are different and raise the event if true.
        /// </summary>
        private void VerifyImeModeChanged(ImeMode oldMode, ImeMode newMode)
        {
            if (ImeSupported && (DisableImeModeChangedCount == 0) && (newMode != ImeMode.NoControl))
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside VerifyImeModeChanged(oldMode={0}, newMode={1}), this = {2}", oldMode, newMode, this));
                Debug.Indent();

                if (oldMode != newMode)
                {
                    OnImeModeChanged(EventArgs.Empty);
                }

                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Verifies whether the IME context mode is correct based on the control's Ime restriction mode (CanEnableIme)
        ///  and updates the IME context if needed.
        /// </summary>
        internal void VerifyImeRestrictedModeChanged()
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside VerifyImeRestrictedModeChanged(), this = " + this);
            Debug.Indent();

            bool currentCanEnableIme = CanEnableIme;

            if (LastCanEnableIme != currentCanEnableIme)
            {
                if (Focused)
                {
                    // Disable ImeModeChanged from the following call since we'll raise it here if needed.
                    DisableImeModeChangedCount++;
                    try
                    {
                        UpdateImeContextMode();
                    }
                    finally
                    {
                        DisableImeModeChangedCount--;
                    }
                }

                // Assume for a moment the control is getting restricted;
                ImeMode oldImeMode = CachedImeMode;
                ImeMode newImeMode = ImeMode.Disable;

                if (currentCanEnableIme)
                {
                    // Control is actually getting unrestricted, swap values.
                    newImeMode = oldImeMode;
                    oldImeMode = ImeMode.Disable;
                }

                // Do we need to raise the ImeModeChanged event?
                VerifyImeModeChanged(oldImeMode, newImeMode);

                // Finally update the saved CanEnableIme value.
                LastCanEnableIme = currentCanEnableIme;
            }

            Debug.Unindent();
        }

        /// <summary>
        ///  Update internal ImeMode properties (PropagatingImeMode/CachedImeMode) with actual IME context mode if needed.
        ///  This method can be used with a child control when the IME mode is more relevant to it than to the control itself,
        ///  for instance ComboBox and its native ListBox/Edit controls.
        /// </summary>
        internal void OnImeContextStatusChanged(IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside OnImeContextStatusChanged(), this = " + this);
            Debug.Indent();

            Debug.Assert(ImeSupported, "WARNING: Attempting to update ImeMode properties on IME-Unaware control!");
            Debug.Assert(!DesignMode, "Shouldn't be updating cached ime mode at design-time");

            ImeMode fromContext = ImeContext.GetImeMode(handle);

            if (fromContext != ImeMode.Inherit)
            {
                ImeMode oldImeMode = CachedImeMode;

                if (CanEnableIme)
                { // Cache or Propagating ImeMode should not be updated by interaction when the control is in restricted mode.
                    if (oldImeMode != ImeMode.NoControl)
                    {
                        CachedImeMode = fromContext; // This could end up in the same value due to ImeMode language mapping.

                        // ImeMode may be changing by user interaction.
                        VerifyImeModeChanged(oldImeMode, CachedImeMode);
                    }
                    else
                    {
                        PropagatingImeMode = fromContext;
                    }
                }
            }

            Debug.Unindent();
        }

        /// <summary>
        ///  Raises the <see cref='OnImeModeChanged'/>
        ///  event.
        /// </summary>
        protected virtual void OnImeModeChanged(EventArgs e)
        {
            Debug.Assert(ImeSupported, "ImeModeChanged should not be raised on an Ime-Unaware control.");
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside OnImeModeChanged(), this = " + this);
            ((EventHandler?)Events[s_imeModeChangedEvent])?.Invoke(this, e);
        }

        /// <summary>
        ///  Resets the Ime mode.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public void ResetImeMode()
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ResetImeMode(), this = " + this);
            ImeMode = DefaultImeMode;
        }

        /// <summary>
        ///  Returns true if the ImeMode should be persisted in code gen.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal virtual bool ShouldSerializeImeMode()
        {
            // This method is for designer support.  If the ImeMode has not been changed or it is the same as the
            // default value it should not be serialized.
            int imeMode = Properties.GetInteger(s_imeModeProperty, out bool found);

            return (found && imeMode != (int)DefaultImeMode);
        }

        /// <summary>
        ///  Handles the WM_INPUTLANGCHANGE message
        /// </summary>
        private void WmInputLangChange(ref Message m)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmInputLangChange(), this = " + this);
            Debug.Indent();

            // Make sure the IME context is associated with the correct (mapped) mode.
            UpdateImeContextMode();

            // If detaching IME (setting to English) reset propagating IME mode so when reattaching the IME is set to direct input again.
            if (ImeModeConversion.InputLanguageTable == ImeModeConversion.UnsupportedTable)
            {
                PropagatingImeMode = ImeMode.Off;
            }

            Form form = FindForm();

            if (form is not null)
            {
                InputLanguageChangedEventArgs e = InputLanguage.CreateInputLanguageChangedEventArgs(m);
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Culture=" + e.Culture);
                form.PerformOnInputLanguageChanged(e);
            }

            DefWndProc(ref m);

            ImeContext.TraceImeStatus(this);
            Debug.Unindent();
        }

        /// <summary>
        ///  Handles the WM_INPUTLANGCHANGEREQUEST message
        /// </summary>
        private void WmInputLangChangeRequest(ref Message m)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmInputLangChangeRequest(), this=" + this);
            Debug.Indent();

            InputLanguageChangingEventArgs e = InputLanguage.CreateInputLanguageChangingEventArgs(m);
            Form form = FindForm();

            if (form is not null)
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Culture=" + e.Culture);
                form.PerformOnInputLanguageChanging(e);
            }

            if (!e.Cancel)
            {
                DefWndProc(ref m);
            }
            else
            {
                m.Result = IntPtr.Zero;
            }

            Debug.Unindent();
        }

        /// <summary>
        ///  Handles the WM_IME_CHAR message
        /// </summary>
        private void WmImeChar(ref Message m)
        {
            if (ProcessKeyEventArgs(ref m))
            {
                return;
            }

            DefWndProc(ref m);
        }

        /// <summary>
        ///  Handles the WM_IME_ENDCOMPOSITION message
        /// </summary>
        private void WmImeEndComposition(ref Message m)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeEndComposition() - Disabling ImeWmCharToIgnore, this=" + this);
            ImeWmCharsToIgnore = ImeCharsToIgnoreDisabled;
            DefWndProc(ref m);
        }

        /// <summary>
        ///  Handles the WM_IME_NOTIFY message
        /// </summary>
        private void WmImeNotify(ref Message m)
        {
            if (ImeSupported && ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable && !IgnoreWmImeNotify)
            {
                int wparam = (int)m.WParam;

                // The WM_IME_NOTIFY message is not consistent across the different IMEs, particularly the notification type
                // we care about (IMN_SETCONVERSIONMODE & IMN_SETOPENSTATUS).
                // The IMN_SETOPENSTATUS command is sent when the open status of the input context is updated.
                // The IMN_SETCONVERSIONMODE command is sent when the conversion mode of the input context is updated.
                // - The Korean IME sents both msg notifications when changing the conversion mode (From/To Hangul/Alpha).
                // - The Chinese IMEs sends the IMN_SETCONVERSIONMODE when changing mode (On/Close, Full Shape/Half Shape)
                //   and IMN_SETOPENSTATUS when getting disabled/enabled or closing/opening as well, but it does not send any
                //   WM_IME_NOTIFY when associating an IME to the app for the first time; setting the IME mode to direct input
                //   during WM_INPUTLANGCHANGED forces the IMN_SETOPENSTATUS message to be sent.
                // - The Japanese IME sends IMN_SETCONVERSIONMODE when changing from Off to one of the active modes (Katakana..)
                //   and IMN_SETOPENSTATUS when changing beteween the active modes or when enabling/disabling the IME.
                // In any case we update the cache.
                // Warning:
                // Attempting to change the IME mode from here will cause re-entrancy - WM_IME_NOTIFY is resent.
                // We guard against re-entrancy since the ImeModeChanged event can be raised and any changes from the handler could
                // lead to another WM_IME_NOTIFY loop.

                if (wparam == (int)Imm32.IMN.SETCONVERSIONMODE || wparam == (int)Imm32.IMN.SETOPENSTATUS)
                {
                    Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside WmImeNotify(m.wparam=[{0}]), this={1}", m.WParam, this));
                    Debug.Indent();

                    // Synchronize internal properties with the IME context mode.
                    OnImeContextStatusChanged(Handle);

                    Debug.Unindent();
                }
            }

            DefWndProc(ref m);
        }

        /// <summary>
        ///  Handles the WM_SETFOCUS message for IME related stuff.
        /// </summary>
        internal void WmImeSetFocus()
        {
            if (ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable)
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeSetFocus(), this=" + this);
                Debug.Indent();

                // Make sure the IME context is set to the correct value.
                // Consider - Perf improvement: ContainerControl controls should update the IME context only when they don't contain
                //            a focusable control since it will be updated by that control.
                UpdateImeContextMode();

                Debug.Unindent();
            }
        }

        /// <summary>
        ///  Handles the WM_IME_STARTCOMPOSITION message
        /// </summary>
        private void WmImeStartComposition(ref Message m)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeStartComposition() - Enabling ImeWmCharToIgnore, this=" + this);

            // Need to call the property store directly because the WmImeCharsToIgnore property is locked when ImeCharsToIgnoreDisabled.
            Properties.SetInteger(s_imeWmCharsToIgnoreProperty, ImeCharsToIgnoreEnabled);
            DefWndProc(ref m);
        }

        /// <summary>
        ///  Handles the WM_KILLFOCUS message
        /// </summary>
        private void WmImeKillFocus()
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeKillFocus(), this=" + this);
            Debug.Indent();

            Control topMostWinformsParent = TopMostParent;
            Form? appForm = topMostWinformsParent as Form;

            if ((appForm is null || appForm.Modal) && !topMostWinformsParent.ContainsFocus)
            {
                // This means the winforms component container is not a WinForms host and it is no longer focused.
                // Or it is not the main app host.

                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Unfocused TopMostParent = " + topMostWinformsParent);

                // We need to reset the PropagatingImeMode to force reinitialization when the winforms component gets focused again;
                // this enables inheritting the propagating mode from an unmanaged application hosting a winforms component.
                // But before leaving the winforms container we need to set the IME to the propagating IME mode since the focused control
                // may not support IME which would leave the IME disabled.
                // See the PropagatingImeMode property

                // Note: We need to check the static field here directly to avoid initialization of the property.
                if (Control.propagatingImeMode != ImeMode.Inherit)
                {
                    // Setting the ime context of the top window will generate a WM_IME_NOTIFY on the focused control which will
                    // update its ImeMode, we need to prevent this temporarily.
                    IgnoreWmImeNotify = true;

                    try
                    {
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Setting IME context to PropagatingImeMode (leaving Winforms container). this = " + this);
                        ImeContext.SetImeStatus(PropagatingImeMode, topMostWinformsParent.Handle);

                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Resetting PropagatingImeMode. this = " + this);
                        PropagatingImeMode = ImeMode.Inherit;
                    }
                    finally
                    {
                        IgnoreWmImeNotify = false;
                    }
                }
            }

            Debug.Unindent();
        }
    } // end class Control

    /////////////////////////////////////////////////////////  ImeContext class /////////////////////////////////////////////////////////
    /// <summary>
    ///  Represents the native IME context.
    /// </summary>
    public static class ImeContext
    {
        /// <summary>
        ///  The IME context handle obtained when first associating an IME.
        /// </summary>
        private static IntPtr originalImeContext;

        /// <summary>
        ///  Disable the IME
        /// </summary>
        public static void Disable(IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ImeContext.Disable(" + handle + ")");
            Debug.Indent();

            if (ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable)
            {
                // Close the IME if necessary
                //
                if (ImeContext.IsOpen(handle))
                {
                    ImeContext.SetOpenStatus(false, handle);
                }

                // Disable the IME by disassociating the context from the window.
                //
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmAssociateContext(" + handle + ", null)");
                IntPtr oldContext = Imm32.ImmAssociateContext(handle, IntPtr.Zero);
                if (oldContext != IntPtr.Zero)
                {
                    originalImeContext = oldContext;
                }
            }

            ImeContext.TraceImeStatus(handle);
            Debug.Unindent();
        }

        /// <summary>
        ///  Enable the IME
        /// </summary>
        public static void Enable(IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ImeContext.Enable(" + handle + ")");
            Debug.Indent();

            if (ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable)
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")");
                IntPtr inputContext = Imm32.ImmGetContext(handle);
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext);

                // Enable IME by associating the IME context to the window.
                if (inputContext == IntPtr.Zero)
                {
                    if (originalImeContext == IntPtr.Zero)
                    {
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmCreateContext()");
                        inputContext = Imm32.ImmCreateContext();
                        if (inputContext != IntPtr.Zero)
                        {
                            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmAssociateContext(" + handle + ", " + inputContext + ")");
                            Imm32.ImmAssociateContext(handle, inputContext);
                        }
                    }
                    else
                    {
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmAssociateContext()");
                        Imm32.ImmAssociateContext(handle, originalImeContext);
                    }
                }
                else
                {
                    Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")");
                    Imm32.ImmReleaseContext(handle, inputContext);
                }

                // Make sure the IME is opened.
                if (!ImeContext.IsOpen(handle))
                {
                    ImeContext.SetOpenStatus(true, handle);
                }
            }

            ImeContext.TraceImeStatus(handle);
            Debug.Unindent();
        }

        /// <summary>
        ///  Gets the ImeMode that corresponds to ImeMode.Disable based on the current input language ImeMode table.
        /// </summary>
        public static ImeMode GetImeMode(IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Insise ImeContext.GetImeMode(" + handle + ")");
            Debug.Indent();

            IntPtr inputContext = IntPtr.Zero;
            ImeMode retval = ImeMode.NoControl;

            // Get the right table for the current keyboard layout
            //
            ImeMode[] countryTable = ImeModeConversion.InputLanguageTable;
            if (countryTable == ImeModeConversion.UnsupportedTable)
            {
                // No IME associated with current culture.
                retval = ImeMode.Inherit;
                goto cleanup;
            }

            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")");
            inputContext = Imm32.ImmGetContext(handle);
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext);

            if (inputContext == IntPtr.Zero)
            {
                // No IME context attached - The Ime has been disabled.
                retval = ImeMode.Disable;
                goto cleanup;
            }

            if (!ImeContext.IsOpen(handle))
            {
                // There's an IME associated with the window but is closed - the input is taken from the keyboard as is (English).
                retval = countryTable[ImeModeConversion.ImeClosed];
                goto cleanup;
            }

            // Determine the IME mode from the conversion status
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetConversionStatus(" + inputContext + ", conversion, sentence)");
            Imm32.ImmGetConversionStatus(inputContext, out Imm32.IME_CMODE conversion, out Imm32.IME_SMODE sentence);

            Debug.Assert(countryTable is not null, "countryTable is null");

            if ((conversion & Imm32.IME_CMODE.NATIVE) != 0)
            {
                if ((conversion & Imm32.IME_CMODE.KATAKANA) != 0)
                {
                    retval = ((conversion & Imm32.IME_CMODE.FULLSHAPE) != 0)
                                ? countryTable[ImeModeConversion.ImeNativeFullKatakana]
                                : countryTable[ImeModeConversion.ImeNativeHalfKatakana];
                    goto cleanup;
                }
                else
                {
                    retval = ((conversion & Imm32.IME_CMODE.FULLSHAPE) != 0)
                                ? countryTable[ImeModeConversion.ImeNativeFullHiragana]
                                : countryTable[ImeModeConversion.ImeNativeHalfHiragana];
                    goto cleanup;
                }
            }
            else
            {
                retval = ((conversion & Imm32.IME_CMODE.FULLSHAPE) != 0)
                            ? countryTable[ImeModeConversion.ImeAlphaFull]
                            : countryTable[ImeModeConversion.ImeAlphaHalf];
            }

        cleanup:
            if (inputContext != IntPtr.Zero)
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")");
                Imm32.ImmReleaseContext(handle, inputContext);
            }

            ImeContext.TraceImeStatus(handle);

            Debug.Unindent();
            return retval;
        }

        /// <summary>
        ///  Get the actual IME status - This method is for debugging purposes only.
        /// </summary>
        [Conditional("DEBUG")]
        internal static void TraceImeStatus(Control ctl)
        {
#if DEBUG
            if (ctl.IsHandleCreated)
            {
                TraceImeStatus(ctl.Handle);
            }
#endif
        }

        [Conditional("DEBUG")]
        private static void TraceImeStatus(IntPtr handle)
        {
#if DEBUG
            if (CompModSwitches.ImeMode.Level >= TraceLevel.Info)
            {
                string status = "?";
                IntPtr inputContext = IntPtr.Zero;
                ImeMode[] countryTable = ImeModeConversion.InputLanguageTable;

                if (countryTable == ImeModeConversion.UnsupportedTable)
                {
                    status = "IME not supported in current language.";
                    goto cleanup;
                }

                inputContext = Imm32.ImmGetContext(handle);

                if (inputContext == IntPtr.Zero)
                {
                    status = string.Format(CultureInfo.CurrentCulture, "No ime context for handle=[{0}]", handle);
                    goto cleanup;
                }

                if (!Imm32.ImmGetOpenStatus(inputContext).IsTrue())
                {
                    status = string.Format(CultureInfo.CurrentCulture, "Ime closed for handle=[{0}]", handle);
                    goto cleanup;
                }

                Imm32.ImmGetConversionStatus(inputContext, out Imm32.IME_CMODE conversion, out Imm32.IME_SMODE sentence);
                ImeMode retval;

                if ((conversion & Imm32.IME_CMODE.NATIVE) != 0)
                {
                    if ((conversion & Imm32.IME_CMODE.KATAKANA) != 0)
                    {
                        retval = ((conversion & Imm32.IME_CMODE.FULLSHAPE) != 0)
                                    ? countryTable[ImeModeConversion.ImeNativeFullKatakana]
                                    : countryTable[ImeModeConversion.ImeNativeHalfKatakana];
                    }
                    else
                    {
                        retval = ((conversion & Imm32.IME_CMODE.FULLSHAPE) != 0)
                                    ? countryTable[ImeModeConversion.ImeNativeFullHiragana]
                                    : countryTable[ImeModeConversion.ImeNativeHalfHiragana];
                    }
                }
                else
                {
                    retval = ((conversion & Imm32.IME_CMODE.FULLSHAPE) != 0)
                                ? countryTable[ImeModeConversion.ImeAlphaFull]
                                : countryTable[ImeModeConversion.ImeAlphaHalf];
                }

                status = string.Format(CultureInfo.CurrentCulture, "Ime conversion status mode for handle=[{0}]: {1}", handle, retval);

            cleanup:
                if (inputContext != IntPtr.Zero)
                {
                    Imm32.ImmReleaseContext(handle, inputContext);
                }

                Debug.WriteLine(status);
            }
#endif
        }

        /// <summary>
        ///  Returns true if the IME is currently open
        /// </summary>
        public static bool IsOpen(IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ImeContext.IsOpen(" + handle + ")");
            Debug.Indent();

            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")");
            IntPtr inputContext = Imm32.ImmGetContext(handle);
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext);

            bool retval = false;

            if (inputContext != IntPtr.Zero)
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetOpenStatus(" + inputContext + ")");
                retval = Imm32.ImmGetOpenStatus(inputContext).IsTrue();
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")");
                Imm32.ImmReleaseContext(handle, inputContext);
            }

            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "    IsOpen = " + retval);
            Debug.Unindent();

            return retval;
        }

        /// <summary>
        ///  Sets the actual IME context value.
        /// </summary>
        public static void SetImeStatus(ImeMode imeMode, IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside ImeContext.SetImeStatus(ImeMode=[{0}, handle=[{1}])", imeMode, handle));
            Debug.Indent();

            Debug.Assert(imeMode != ImeMode.Inherit, "ImeMode.Inherit is an invalid argument to ImeContext.SetImeStatus");

            if (imeMode == ImeMode.Inherit || imeMode == ImeMode.NoControl)
            {
                goto cleanup;     // No action required
            }

            ImeMode[] inputLanguageTable = ImeModeConversion.InputLanguageTable;

            if (inputLanguageTable == ImeModeConversion.UnsupportedTable)
            {
                goto cleanup;     // We only support Japanese, Korean and Chinese IME.
            }

            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Warning, "\tSetting IME context to " + imeMode);

            if (imeMode == ImeMode.Disable)
            {
                ImeContext.Disable(handle);
            }
            else
            {
                // This will make sure the IME is opened.
                ImeContext.Enable(handle);
            }

            switch (imeMode)
            {
                case ImeMode.NoControl:
                case ImeMode.Disable:
                    break;     // No action required

                case ImeMode.On:
                    // IME active mode (CHN = On, JPN = Hiragana, KOR = Hangul).
                    // Setting ImeMode to Hiragana (or any other active value) will force the IME to get to an active value
                    // independent on the language.
                    imeMode = ImeMode.Hiragana;
                    goto default;

                case ImeMode.Off:
                    // IME direct input (CHN = Off, JPN = Off, KOR = Alpha).
                    if (inputLanguageTable == ImeModeConversion.JapaneseTable)
                    {
                        // Japanese IME interprets Close as Off.
                        goto case ImeMode.Close;
                    }

                    // CHN: to differentiate between Close and Off we set the ImeMode to Alpha.
                    imeMode = ImeMode.Alpha;
                    goto default;

                case ImeMode.Close:
                    if (inputLanguageTable == ImeModeConversion.KoreanTable)
                    {
                        // Korean IME has no idea what Close means.
                        imeMode = ImeMode.Alpha;
                        goto default;
                    }

                    ImeContext.SetOpenStatus(false, handle);
                    break;

                default:
                    if (ImeModeConversion.ImeModeConversionBits.ContainsKey(imeMode))
                    {
                        // Update the conversion status
                        //
                        ImeModeConversion conversionEntry = (ImeModeConversion)ImeModeConversion.ImeModeConversionBits[imeMode];

                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")");
                        IntPtr inputContext = Imm32.ImmGetContext(handle);
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext);
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetConversionStatus(" + inputContext + ", conversion, sentence)");
                        Imm32.ImmGetConversionStatus(inputContext, out Imm32.IME_CMODE conversion, out Imm32.IME_SMODE sentence);

                        conversion |= conversionEntry.setBits;
                        conversion &= ~conversionEntry.clearBits;

                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmSetConversionStatus(" + inputContext + ", conversion, sentence)");
                        Imm32.ImmSetConversionStatus(inputContext, conversion, sentence);
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")");
                        Imm32.ImmReleaseContext(handle, inputContext);
                    }

                    break;
            }

        cleanup:
            ImeContext.TraceImeStatus(handle);
            Debug.Unindent();
        }

        /// <summary>
        ///  Opens or closes the IME context.
        /// </summary>
        public static void SetOpenStatus(bool open, IntPtr handle)
        {
            Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside SetOpenStatus(open=[{0}], handle=[{1}])", open, handle));
            Debug.Indent();

            if (ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable)
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")");
                IntPtr inputContext = Imm32.ImmGetContext(handle);
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext);

                if (inputContext != IntPtr.Zero)
                {
                    Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmSetOpenStatus(" + inputContext + ", " + open + ")");
                    bool succeeded = Imm32.ImmSetOpenStatus(inputContext, open.ToBOOL()).IsTrue();
                    Debug.Assert(succeeded, "Could not set the IME open status.");

                    if (succeeded)
                    {
                        Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")");
                        succeeded = Imm32.ImmReleaseContext(handle, inputContext).IsTrue();
                        Debug.Assert(succeeded, "Could not release IME context.");
                    }
                }
            }

            ImeContext.TraceImeStatus(handle);
            Debug.Unindent();
        }
    }// end ImeContext class

    /////////////////////////////////////////////////////////  ImeModeConversion structure /////////////////////////////////////////////////////////
    /// <summary>
    ///  Helper class that provides information about IME convertion mode.  Convertion mode refers to how IME interprets input like
    ///  ALPHANUMERIC or HIRAGANA and depending on its value the IME enables/disables the IME convertion window appropriately.
    /// </summary>
    public struct ImeModeConversion
    {
        private static Dictionary<ImeMode, ImeModeConversion>? imeModeConversionBits;

        internal Imm32.IME_CMODE setBits;
        internal Imm32.IME_CMODE clearBits;

        // Tables of conversions from IME context bits to IME mode
        //
        //internal const int ImeNotAvailable = 0;
        internal const int ImeDisabled = 1;
        internal const int ImeDirectInput = 2;
        internal const int ImeClosed = 3;
        internal const int ImeNativeInput = 4;
        internal const int ImeNativeFullHiragana = 4; // Index of Native Input Mode.
        internal const int ImeNativeHalfHiragana = 5;
        internal const int ImeNativeFullKatakana = 6;
        internal const int ImeNativeHalfKatakana = 7;
        internal const int ImeAlphaFull = 8;
        internal const int ImeAlphaHalf = 9;

        /// <summary>
        ///  Supported input language ImeMode tables.
        ///     WARNING: Do not try to map 'active' IME modes from one table to another since they can have a different
        ///              meaning depending on the language; for instance ImeMode.Off means 'disable' or 'alpha' to Chinese
        ///              but to Japanese it is 'alpha' and to Korean it has no meaning.
        /// </summary>
        private static readonly ImeMode[] japaneseTable =
        {
            ImeMode.Inherit,
            ImeMode.Disable,
            ImeMode.Off,
            ImeMode.Off,
            ImeMode.Hiragana,
            ImeMode.Hiragana,
            ImeMode.Katakana,
            ImeMode.KatakanaHalf,
            ImeMode.AlphaFull,
            ImeMode.Alpha
        };

        private static readonly ImeMode[] koreanTable =
        {
            ImeMode.Inherit,
            ImeMode.Disable,
            ImeMode.Alpha,
            ImeMode.Alpha,
            ImeMode.HangulFull,
            ImeMode.Hangul,
            ImeMode.HangulFull,
            ImeMode.Hangul,
            ImeMode.AlphaFull,
            ImeMode.Alpha
        };

        private static readonly ImeMode[] chineseTable =
        {
            ImeMode.Inherit,
            ImeMode.Disable,
            ImeMode.Off,
            ImeMode.Close,
            ImeMode.On,
            ImeMode.OnHalf,
            ImeMode.On,
            ImeMode.OnHalf,
            ImeMode.Off,
            ImeMode.Off
        };

        private static readonly ImeMode[] unsupportedTable = Array.Empty<ImeMode>();

        internal static ImeMode[] ChineseTable
        {
            get
            {
                return chineseTable;
            }
        }

        internal static ImeMode[] JapaneseTable
        {
            get
            {
                return japaneseTable;
            }
        }

        internal static ImeMode[] KoreanTable
        {
            get
            {
                return koreanTable;
            }
        }

        internal static ImeMode[] UnsupportedTable
        {
            get
            {
                return unsupportedTable;
            }
        }

        /// <summary>
        ///  Gets the ImeMode table of the current input language.
        ///  Although this property is per-thread based we cannot cache it and share it among controls running in the same thread
        ///  for two main reasons: we still have some controls that don't handle IME properly (TabControl, ComboBox, TreeView...)
        ///  and would render it invalid and since the IME API is not public third party controls would not have a way to update
        ///  the cached value.
        /// </summary>
        internal static ImeMode[] InputLanguageTable
        {
            get
            {
                Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_InputLanguageTable(), Input Language = " + InputLanguage.CurrentInputLanguage.Culture.DisplayName + ", Thread = " + System.Threading.Thread.CurrentThread.ManagedThreadId);
                InputLanguage inputLanguage = InputLanguage.CurrentInputLanguage;

                int lcid = (int)((long)inputLanguage.Handle & (long)0xFFFF);

                switch (lcid)
                {
                    case 0x0404:
                    case 0x0804:
                    case 0x0c04:
                    case 0x1004:
                    case 0x1404:
                        return chineseTable;

                    case 0x0412:    // Korean
                    case 0x0812:    // Korean (Johab)
                        return koreanTable;

                    case 0x0411:    // Japanese
                        return japaneseTable;

                    default:
                        return unsupportedTable;
                }
            }
        }

        /// <summary>
        ///  Dictionary of ImeMode and corresponding convertion flags.
        /// </summary>
        public static Dictionary<ImeMode, ImeModeConversion> ImeModeConversionBits
        {
            get
            {
                if (imeModeConversionBits is null)
                {
                    // Create ImeModeConversionBits dictionary
                    imeModeConversionBits = new Dictionary<ImeMode, ImeModeConversion>(7);
                    ImeModeConversion conversion;

                    // Hiragana, On
                    //
                    conversion.setBits = Imm32.IME_CMODE.FULLSHAPE | Imm32.IME_CMODE.NATIVE;
                    conversion.clearBits = Imm32.IME_CMODE.KATAKANA;
                    imeModeConversionBits.Add(ImeMode.Hiragana, conversion);

                    // Katakana
                    //
                    conversion.setBits = Imm32.IME_CMODE.FULLSHAPE | Imm32.IME_CMODE.KATAKANA | Imm32.IME_CMODE.NATIVE;
                    conversion.clearBits = 0;
                    imeModeConversionBits.Add(ImeMode.Katakana, conversion);

                    // KatakanaHalf
                    //
                    conversion.setBits = Imm32.IME_CMODE.KATAKANA | Imm32.IME_CMODE.NATIVE;
                    conversion.clearBits = Imm32.IME_CMODE.FULLSHAPE;
                    imeModeConversionBits.Add(ImeMode.KatakanaHalf, conversion);

                    // AlphaFull
                    //
                    conversion.setBits = Imm32.IME_CMODE.FULLSHAPE;
                    conversion.clearBits = Imm32.IME_CMODE.KATAKANA | Imm32.IME_CMODE.NATIVE;
                    imeModeConversionBits.Add(ImeMode.AlphaFull, conversion);

                    // Alpha
                    //
                    conversion.setBits = 0;
                    conversion.clearBits = Imm32.IME_CMODE.FULLSHAPE | Imm32.IME_CMODE.KATAKANA | Imm32.IME_CMODE.NATIVE;
                    imeModeConversionBits.Add(ImeMode.Alpha, conversion);

                    // HangulFull
                    //
                    conversion.setBits = Imm32.IME_CMODE.FULLSHAPE | Imm32.IME_CMODE.NATIVE;
                    conversion.clearBits = 0;
                    imeModeConversionBits.Add(ImeMode.HangulFull, conversion);

                    // Hangul
                    //
                    conversion.setBits = Imm32.IME_CMODE.NATIVE;
                    conversion.clearBits = Imm32.IME_CMODE.FULLSHAPE;
                    imeModeConversionBits.Add(ImeMode.Hangul, conversion);

                    // OnHalf
                    //
                    conversion.setBits = Imm32.IME_CMODE.NATIVE;
                    conversion.clearBits = Imm32.IME_CMODE.KATAKANA | Imm32.IME_CMODE.FULLSHAPE;
                    imeModeConversionBits.Add(ImeMode.OnHalf, conversion);
                }

                return imeModeConversionBits;
            }
        }

        public static bool IsCurrentConversionTableSupported
        {
            get
            {
                return InputLanguageTable != UnsupportedTable;
            }
        }
    }// end ImeModeConversion struct.
} // end namespace System.Windows.Forms
