/////////////////////////////////////////////////////////////////////////
// Copyright 2011-2018 Simon Schneegans
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////

namespace GnomePie {

/////////////////////////////////////////////////////////////////////////
/// A class which represents a key stroke. It can be used to "press"
/// the associated keys.
/////////////////////////////////////////////////////////////////////////

public class Key : GLib.Object {

    /////////////////////////////////////////////////////////////////////
    /// Some static members, which are often used by this class.
    /////////////////////////////////////////////////////////////////////

    private static X.Display display;

    private static int shift_l_code;
    private static int shift_r_code;
    private static int ctrl_l_code;
    private static int ctrl_r_code;
    private static int alt_l_code;
    private static int alt_r_code;
    private static int super_l_code;
    private static int super_r_code;

    /////////////////////////////////////////////////////////////////////
    /// A human readable form of the Key's accelerator.
    /////////////////////////////////////////////////////////////////////

    public string label { get; private set; }

    /////////////////////////////////////////////////////////////////////
    /// The accelerator of the Key.
    /////////////////////////////////////////////////////////////////////

    public string accelerator { get; private set; }

    /////////////////////////////////////////////////////////////////////
    /// Keycode and modifiers of this stroke.
    /////////////////////////////////////////////////////////////////////

    private int key_code;
    private Gdk.ModifierType modifiers;

    /////////////////////////////////////////////////////////////////////
    /// C'tor, initializes all members to defaults.
    /////////////////////////////////////////////////////////////////////

    public Key() {
        this.accelerator = "";
        this.modifiers = 0;
        this.key_code = 0;
        this.label = _("Not bound");
    }

    /////////////////////////////////////////////////////////////////////
    /// C'tor, initializes all members.
    /////////////////////////////////////////////////////////////////////

    public Key.from_string(string stroke) {
        this.accelerator = stroke;

        uint keysym;
        Gtk.accelerator_parse(stroke, out keysym, out this.modifiers);
        this.key_code = display.keysym_to_keycode(keysym);
        this.label = Gtk.accelerator_get_label(keysym, this.modifiers);
    }

    /////////////////////////////////////////////////////////////////////
    /// C'tor, initializes all members.
    /////////////////////////////////////////////////////////////////////

    public Key.from_values(uint keysym, Gdk.ModifierType modifiers) {
        this.accelerator = Gtk.accelerator_name(keysym, modifiers);
        this.label = Gtk.accelerator_get_label(keysym, modifiers);
        this.key_code = display.keysym_to_keycode(keysym);
        this.modifiers = modifiers;
    }

    /////////////////////////////////////////////////////////////////////
    /// Initializes static members.
    /////////////////////////////////////////////////////////////////////

    static construct {
        display = new X.Display();

        shift_l_code = display.keysym_to_keycode(Gdk.keyval_from_name("Shift_L"));
        shift_r_code = display.keysym_to_keycode(Gdk.keyval_from_name("Shift_R"));
        ctrl_l_code =  display.keysym_to_keycode(Gdk.keyval_from_name("Control_L"));
        ctrl_r_code =  display.keysym_to_keycode(Gdk.keyval_from_name("Control_R"));
        alt_l_code =   display.keysym_to_keycode(Gdk.keyval_from_name("Alt_L"));
        alt_r_code =   display.keysym_to_keycode(Gdk.keyval_from_name("Alt_R"));
        super_l_code = display.keysym_to_keycode(Gdk.keyval_from_name("Super_L"));
        super_r_code = display.keysym_to_keycode(Gdk.keyval_from_name("Super_R"));
    }

    /////////////////////////////////////////////////////////////////////
    /// Simulates the pressing of the Key .
    /////////////////////////////////////////////////////////////////////

    public void press() {
        // store currently pressed modifier keys
        Gdk.ModifierType current_modifiers = get_modifiers();

        // release them and press the desired ones
        release_modifiers(current_modifiers);
        press_modifiers(this.modifiers);

        // send events to X
        display.flush();

        // press and release the actual key
        XTest.fake_key_event(display, this.key_code, true, 0);
        XTest.fake_key_event(display, this.key_code, false, 0);

        // release the pressed modifiers and re-press the keys hold down by the user
        release_modifiers(this.modifiers);
        // press_modifiers(current_modifiers);

        // send events to X
        display.flush();
    }

    /////////////////////////////////////////////////////////////////////
    /// Helper method returning currently hold down modifier keys.
    /////////////////////////////////////////////////////////////////////

    public static Gdk.ModifierType get_modifiers() {
        return (Gdk.ModifierType)Gdk.Keymap.get_for_display(
                Gdk.Display.get_default()).get_modifier_state();
    }

    /////////////////////////////////////////////////////////////////////
    /// Helper method which 'releases' the desired modifier keys.
    /////////////////////////////////////////////////////////////////////

    private void release_modifiers(Gdk.ModifierType modifiers) {
        // since we do not know whether left or right version of each key
        // is pressed, we release both...
        if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0) {
            XTest.fake_key_event(display, ctrl_l_code, false, 0);
            XTest.fake_key_event(display, ctrl_r_code, false, 0);
        }

        if ((modifiers & Gdk.ModifierType.SHIFT_MASK) > 0) {
            XTest.fake_key_event(display, shift_l_code, false, 0);
            XTest.fake_key_event(display, shift_r_code, false, 0);
        }

        if ((modifiers & Gdk.ModifierType.MOD1_MASK) > 0) {
            XTest.fake_key_event(display, alt_l_code, false, 0);
            XTest.fake_key_event(display, alt_r_code, false, 0);
        }

        if ((modifiers & Gdk.ModifierType.SUPER_MASK) > 0) {
            XTest.fake_key_event(display, super_l_code, false, 0);
            XTest.fake_key_event(display, super_r_code, false, 0);
        }
    }

    /////////////////////////////////////////////////////////////////////
    /// Helper method which 'presses' the desired modifier keys.
    /////////////////////////////////////////////////////////////////////

    private void press_modifiers(Gdk.ModifierType modifiers) {
        if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0) {
            XTest.fake_key_event(display, ctrl_l_code, true, 0);
        }

        if ((modifiers & Gdk.ModifierType.SHIFT_MASK) > 0) {
            XTest.fake_key_event(display, shift_l_code, true, 0);
        }

        if ((modifiers & Gdk.ModifierType.MOD1_MASK) > 0) {
            XTest.fake_key_event(display, alt_l_code, true, 0);
        }

        if ((modifiers & Gdk.ModifierType.SUPER_MASK) > 0) {
            XTest.fake_key_event(display, super_l_code, true, 0);
        }
    }
}

}
