/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef __nsGdkKeyUtils_h__
#define __nsGdkKeyUtils_h__

#include "nsTArray.h"
#include "mozilla/EventForwards.h"

#include <gdk/gdk.h>
#include <X11/XKBlib.h>

namespace mozilla {
namespace widget {

/**
 *  KeymapWrapper is a wrapper class of GdkKeymap.  GdkKeymap doesn't support
 *  all our needs, therefore, we need to access lower level APIs.
 *  But such code is usually complex and might be slow.  Against such issues,
 *  we should cache some information.
 *
 *  This class provides only static methods.  The methods is using internal
 *  singleton instance which is initialized by default GdkKeymap.  When the
 *  GdkKeymap is destroyed, the singleton instance will be destroyed.
 */

class KeymapWrapper
{
public:
    /**
     * Compute an our DOM keycode from a GDK keyval.
     */
    static uint32_t ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent);

    /**
     * Compute a DOM key name index from aGdkKeyEvent.
     */
    KeyNameIndex ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent);

    /**
     * Compute a DOM code name index from aGdkKeyEvent.
     */
    static CodeNameIndex ComputeDOMCodeNameIndex(
                           const GdkEventKey* aGdkKeyEvent);

    /**
     * Modifier is list of modifiers which we support in widget level.
     */
    enum Modifier {
        NOT_MODIFIER       = 0x0000,
        CAPS_LOCK          = 0x0001,
        NUM_LOCK           = 0x0002,
        SCROLL_LOCK        = 0x0004,
        SHIFT              = 0x0008,
        CTRL               = 0x0010,
        ALT                = 0x0020,
        META               = 0x0040,
        SUPER              = 0x0080,
        HYPER              = 0x0100,
        LEVEL3             = 0x0200,
        LEVEL5             = 0x0400
    };

    /**
     * Modifiers is used for combination of Modifier.
     * E.g., |Modifiers modifiers = (SHIFT | CTRL);| means Shift and Ctrl.
     */
    typedef uint32_t Modifiers;

    /**
     * GetCurrentModifierState() returns current modifier key state.
     * The "current" means actual state of hardware keyboard when this is
     * called.  I.e., if some key events are not still dispatched by GDK,
     * the state may mismatch with GdkEventKey::state.
     *
     * @return                  Current modifier key state.
     */
    static guint GetCurrentModifierState();

    /**
     * AreModifiersCurrentlyActive() checks the "current" modifier state
     * on aGdkWindow with the keymap of the singleton instance.
     *
     * @param aModifiers        One or more of Modifier values except
     *                          NOT_MODIFIER.
     * @return                  TRUE if all of modifieres in aModifiers are
     *                          active.  Otherwise, FALSE.
     */
    static bool AreModifiersCurrentlyActive(Modifiers aModifiers);

    /**
     * AreModifiersActive() just checks whether aModifierState indicates
     * all modifiers in aModifiers are active or not.
     *
     * @param aModifiers        One or more of Modifier values except
     *                          NOT_MODIFIER.
     * @param aModifierState    GDK's modifier states.
     * @return                  TRUE if aGdkModifierType indecates all of
     *                          modifiers in aModifier are active.
     *                          Otherwise, FALSE.
     */
    static bool AreModifiersActive(Modifiers aModifiers,
                                   guint aModifierState);

    /**
     * InitInputEvent() initializes the aInputEvent with aModifierState.
     */
    static void InitInputEvent(WidgetInputEvent& aInputEvent,
                               guint aModifierState);

    /**
     * InitKeyEvent() intializes aKeyEvent's modifier key related members
     * and keycode related values.
     *
     * @param aKeyEvent         It's an WidgetKeyboardEvent which needs to be
     *                          initialized.
     * @param aGdkKeyEvent      A native GDK key event.
     */
    static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
                             GdkEventKey* aGdkKeyEvent);

    /**
     * WillDispatchKeyboardEvent() is called via
     * TextEventDispatcherListener::WillDispatchKeyboardEvent().
     *
     * @param aKeyEvent         An instance of KeyboardEvent which will be
     *                          dispatched.  This method should set charCode
     *                          and alternative char codes if it's necessary.
     * @param aGdkKeyEvent      A GdkEventKey instance which caused the
     *                          aKeyEvent.
     */
    static void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
                                          GdkEventKey* aGdkKeyEvent);

    /**
     * Destroys the singleton KeymapWrapper instance, if it exists.
     */
    static void Shutdown();

protected:

    /**
     * GetInstance() returns a KeymapWrapper instance.
     *
     * @return                  A singleton instance of KeymapWrapper.
     */
    static KeymapWrapper* GetInstance();

    KeymapWrapper();
    ~KeymapWrapper();

    bool mInitialized;

    /**
     * Initializing methods.
     */
    void Init();
    void InitXKBExtension();
    void InitBySystemSettings();

    /**
     * mModifierKeys stores each hardware key information.
     */
    struct ModifierKey {
        guint mHardwareKeycode;
        guint mMask;

        explicit ModifierKey(guint aHardwareKeycode) :
          mHardwareKeycode(aHardwareKeycode), mMask(0)
        {
        }
    };
    nsTArray<ModifierKey> mModifierKeys;

    /**
     * GetModifierKey() returns modifier key information of the hardware
     * keycode.  If the key isn't a modifier key, returns nullptr.
     */
    ModifierKey* GetModifierKey(guint aHardwareKeycode);

    /**
     * mModifierMasks is bit masks for each modifier.  The index should be one
     * of ModifierIndex values.
     */
    enum ModifierIndex {
        INDEX_NUM_LOCK,
        INDEX_SCROLL_LOCK,
        INDEX_ALT,
        INDEX_META,
        INDEX_SUPER,
        INDEX_HYPER,
        INDEX_LEVEL3,
        INDEX_LEVEL5,
        COUNT_OF_MODIFIER_INDEX
    };
    guint mModifierMasks[COUNT_OF_MODIFIER_INDEX];

    guint GetModifierMask(Modifier aModifier) const;

    /**
     * @param aGdkKeyval        A GDK defined modifier key value such as
     *                          GDK_Shift_L.
     * @return                  Returns Modifier values for aGdkKeyval.
     *                          If the given key code isn't a modifier key,
     *                          returns NOT_MODIFIER.
     */
    static Modifier GetModifierForGDKKeyval(guint aGdkKeyval);

    static const char* GetModifierName(Modifier aModifier);

    /**
     * mGdkKeymap is a wrapped instance by this class.
     */
    GdkKeymap* mGdkKeymap;

    /**
     * The base event code of XKB extension.
     */
    int mXKBBaseEventCode;

    /**
     * Only auto_repeats[] stores valid value.  If you need to use other
     * members, you need to listen notification events for them.
     * See a call of XkbSelectEventDetails() with XkbControlsNotify in
     * InitXKBExtension().
     */
    XKeyboardState mKeyboardState;

    /**
     * Pointer of the singleton instance.
     */
    static KeymapWrapper* sInstance;

    /**
     * Auto key repeat management.
     */
    static guint sLastRepeatableHardwareKeyCode;
    enum RepeatState
    {
        NOT_PRESSED,
        FIRST_PRESS,
        REPEATING
    };
    static RepeatState sRepeatState;

    /**
     * IsAutoRepeatableKey() returns true if the key supports auto repeat.
     * Otherwise, false.
     */
    bool IsAutoRepeatableKey(guint aHardwareKeyCode);

    /**
     * Signal handlers.
     */
    static void OnKeysChanged(GdkKeymap* aKeymap, KeymapWrapper* aKeymapWrapper);
    static void OnDirectionChanged(GdkKeymap *aGdkKeymap,
                                   KeymapWrapper* aKeymapWrapper);

    /**
     * GetCharCodeFor() Computes what character is inputted by the key event
     * with aModifierState and aGroup.
     *
     * @param aGdkKeyEvent      Native key event, must not be nullptr.
     * @param aModifierState    Combination of GdkModifierType which you
     *                          want to test with aGdkKeyEvent.
     * @param aGroup            Set group in the mGdkKeymap.
     * @return                  charCode which is inputted by aGdkKeyEvent.
     *                          If failed, this returns 0.
     */
    static uint32_t GetCharCodeFor(const GdkEventKey *aGdkKeyEvent);
    uint32_t GetCharCodeFor(const GdkEventKey *aGdkKeyEvent,
                            guint aModifierState,
                            gint aGroup);

    /**
     * GetUnmodifiedCharCodeFor() computes what character is inputted by the
     * key event without Ctrl/Alt/Meta/Super/Hyper modifiers.
     * If Level3 or Level5 Shift causes no character input, this also ignores
     * them.
     *
     * @param aGdkKeyEvent      Native key event, must not be nullptr.
     * @return                  charCode which is computed without modifiers
     *                          which prevent text input.
     */
    uint32_t GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent);

    /**
     * GetKeyLevel() returns level of the aGdkKeyEvent in mGdkKeymap.
     *
     * @param aGdkKeyEvent      Native key event, must not be nullptr.
     * @return                  Using level.  Typically, this is 0 or 1.
     *                          If failed, this returns -1.
     */
    gint GetKeyLevel(GdkEventKey *aGdkKeyEvent);

    /**
     * GetFirstLatinGroup() returns group of mGdkKeymap which can input an
     * ASCII character by GDK_A.
     *
     * @return                  group value of GdkEventKey.
     */
    gint GetFirstLatinGroup();

    /**
     * IsLatinGroup() checkes whether the keyboard layout of aGroup is
     * ASCII alphabet inputtable or not.
     *
     * @param aGroup            The group value of GdkEventKey.
     * @return                  TRUE if the keyboard layout can input
     *                          ASCII alphabet.  Otherwise, FALSE.
     */
    bool IsLatinGroup(guint8 aGroup);

    /**
     * IsBasicLatinLetterOrNumeral() Checks whether the aCharCode is an
     * alphabet or a numeric character in ASCII.
     *
     * @param aCharCode         Charcode which you want to test.
     * @return                  TRUE if aCharCode is an alphabet or a numeric
     *                          in ASCII range.  Otherwise, FALSE.
     */
    static bool IsBasicLatinLetterOrNumeral(uint32_t aCharCode);

    /**
     * GetGDKKeyvalWithoutModifier() returns the keyval for aGdkKeyEvent when
     * ignoring the modifier state except NumLock. (NumLock is a key to change
     * some key's meaning.)
     */
    static guint GetGDKKeyvalWithoutModifier(const GdkEventKey *aGdkKeyEvent);

    /**
     * GetDOMKeyCodeFromKeyPairs() returns DOM keycode for aGdkKeyval if
     * it's in KeyPair table.
     */
    static uint32_t GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval);

    /**
     * FilterEvents() listens all events on all our windows.
     * Be careful, this may make damage to performance if you add expensive
     * code in this method.
     */
    static GdkFilterReturn FilterEvents(GdkXEvent* aXEvent,
                                        GdkEvent* aGdkEvent,
                                        gpointer aData);

    /**
     * See the document of WillDispatchKeyboardEvent().
     */
    void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
                                           GdkEventKey* aGdkKeyEvent);
};

} // namespace widget
} // namespace mozilla

#endif /* __nsGdkKeyUtils_h__ */