/**
 * Copyright 2013 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */

package jogamp.newt.driver.linux;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Integer;
import java.lang.Runnable;
import java.lang.String;
import java.lang.Thread;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import jogamp.newt.WindowImpl;
import jogamp.newt.driver.KeyTracker;

import com.jogamp.common.nio.StructAccessor;
import com.jogamp.common.os.Platform;
import com.jogamp.common.util.InterruptSource;
import com.jogamp.common.util.PropertyAccess;
import com.jogamp.newt.Window;
import com.jogamp.newt.event.InputEvent;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowListener;
import com.jogamp.newt.event.WindowUpdateEvent;
import com.jogamp.newt.event.KeyEvent;

/**
 * Native key event tracker thread for GNU/Linux.
 * <p>
 * Implementation attempts to read one of the following input event files
 * <ol>
 * <li>try /dev/input/by-path/*-event-kbd</li>
 * <li>try /dev/input/by-id/*-event-kbd</li>
 * <li>try /dev/input/event* <i>Disabled by default! Warning: Unreliable due to fiddling with all native input events!</i></li>
 * </ol>
 * </p>
 * <p>
 * The first two input event file mappings allow for only utilizing
 * the actual <code>event-kbd</code> keyboard events.
 * </p>
 * <p>
 * The last method is used if all former methods fails and it brute
 * force uses all first 32 event files, which can cause overall stability issues!
 * </p>
 * <p>
 * The last method must be enabled by setting the property <code>newt.enable.LinuxKeyEventTracker.eventx</code>.
 * </p>
 * <p>
 * This tracker can be completely disabled by setting property <code>newt.disable.LinuxKeyEventTracker</code>.
 * </p>
 */
public class LinuxKeyEventTracker implements WindowListener, KeyTracker {

    private static final boolean DISABLE = PropertyAccess.isPropertyDefined("newt.disable.LinuxKeyEventTracker", true);
    private static final boolean ENABLE_PLAIN_EVENTX = PropertyAccess.isPropertyDefined("newt.enable.LinuxKeyEventTracker.eventx", true);

    private static final String linuxDevInputByEventXRoot = "/dev/input/";
    private static final String linuxDevInputByIDRoot = "/dev/input/by-id/";
    private static final String linuxDevInputByPathRoot = "/dev/input/by-path/";

    private static LinuxKeyEventTracker ledt = null;

    public static synchronized LinuxKeyEventTracker getSingleton() {
        if( !DISABLE ) {
            if( null == ledt ) {
                ledt = new LinuxKeyEventTracker();
                final Thread t = new InterruptSource.Thread(null, ledt.eventDeviceManager, "NEWT-LinuxEventDeviceManager");
                t.setDaemon(true);
                t.start();
            }
        }
        return ledt;
    }

    private WindowImpl focusedWindow = null;
    private final EventDeviceManager eventDeviceManager = new EventDeviceManager();

    /**
     * Mapping of input event basename file to spawn of EventDevicePoller.
     * <p>
     * String key is the base filename one of either (in that order):
     * <ol>
     * <li>try /dev/input/by-path/*-event-kbd</li>
     * <li>try /dev/input/by-id/*-event-kbd</li>
     * <li>try /dev/input/event* (Warning: Unreliable due to fiddling with all native input events !)</li>
     * </ol>
     */
    private final Map<String, EventDevicePoller> edpMap = new HashMap<String, EventDevicePoller>();

    private LinuxKeyEventTracker() {}

    @Override
    public void windowResized(final WindowEvent e) { }

    @Override
    public void windowMoved(final WindowEvent e) { }

    @Override
    public void windowDestroyNotify(final WindowEvent e) {
        final Object s = e.getSource();
        if(focusedWindow == s) {
            focusedWindow = null;
        }
    }

    @Override
    public void windowDestroyed(final WindowEvent e) { }

    @Override
    public void windowGainedFocus(final WindowEvent e) {
        final Object s = e.getSource();
        if(s instanceof WindowImpl) {
            focusedWindow = (WindowImpl) s;
        }
    }

    @Override
    public void windowLostFocus(final WindowEvent e) {
        final Object s = e.getSource();
        if(focusedWindow == s) {
            focusedWindow = null;
        }
    }

    public static void main(final String[] args ){
        System.setProperty("newt.debug.Window.KeyEvent", "true");
        LinuxKeyEventTracker.getSingleton();
        try {
            while(true) {
                Thread.sleep(1000);
            }
        } catch (final InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void windowRepaint(final WindowUpdateEvent e) { }

    private final void sendKeyEvent(final short eventType, final int modifiers, final short keyCode, final short keySym, final char keyChar) {
        if(null != focusedWindow) {
            focusedWindow.sendKeyEvent(eventType, modifiers, keyCode, keyCode, keyChar);
            // focusedWindow.enqueueKeyEvent(false /* wait */, eventType, modifiers, keyCode, keyCode, keyChar);
        }
    }

    class EventDeviceManager implements Runnable {

        private volatile boolean stop = false;

        private void managePaths(final String rootPath, final String suffix, final String[] subPaths) {
            for(final String path : subPaths ) {
                if( path.endsWith(suffix) ) {
                    final EventDevicePoller edpOld = edpMap.get(path);
                    if( null == edpOld ) {
                        final EventDevicePoller edpNew = new EventDevicePoller( rootPath + path );
                        edpMap.put(path, edpNew);
                        final Thread t = new InterruptSource.Thread(null, edpNew, "NEWT-KeyEventTracker-"+path);
                        t.setDaemon(true);
                        t.start();
                    } else if( edpOld.stop ) {
                        // clear stopped entry, will restart after sleep
                        edpMap.put(path, null);
                    }
                }
            }
            try {
                Thread.sleep(2000);
            } catch (final InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            // 1) try /dev/input/by-path/*-event-kbd
            {
                final File devInputByPath = new File(linuxDevInputByPathRoot);
                final String[] devInputPaths = devInputByPath.list();
                final boolean useDevInputByPath = null != devInputPaths && 0 < devInputPaths.length && devInputByPath.exists() && devInputByPath.isDirectory();
                if( useDevInputByPath ) {
                    while(!stop){
                        managePaths(linuxDevInputByPathRoot, "-event-kbd", devInputPaths);
                    }
                    return;
                }
            }

            // 2) try /dev/input/by-id/*-event-kbd
            {
                final File devInputByID = new File(linuxDevInputByIDRoot);
                final String[] devInputIDs = devInputByID.list();
                final boolean useDevInputByID = null != devInputIDs && 0 < devInputIDs.length && devInputByID.exists() && devInputByID.isDirectory();
                if( useDevInputByID ) {
                    while(!stop){
                        managePaths(linuxDevInputByIDRoot, "-event-kbd", devInputIDs);
                    }
                    return;
                }
            }

            // 3) try /dev/input/event* (Warning: Unreliable due to fiddling with all native input events !)
            if( ENABLE_PLAIN_EVENTX ) {
                final File devInputByEventX = new File(linuxDevInputByEventXRoot);
                while(!stop){
                    for( final String path : devInputByEventX.list() ) {
                        if( path.startsWith("event") ) {
                            final String stringNumber = path.substring(5);
                            final int number = Integer.parseInt(stringNumber);
                            if( number<32 && number>=0 ) {
                                final EventDevicePoller edpOld = edpMap.get(path);
                                if( null == edpOld || edpOld.stop ) {
                                    final EventDevicePoller edpNew = new EventDevicePoller( linuxDevInputByEventXRoot + path );
                                    final Thread t = new InterruptSource.Thread(null, edpNew, "NEWT-KeyEventTracker-"+path);
                                    t.setDaemon(true);
                                    t.start();
                                } else if( edpOld.stop ) {
                                    // clear stopped entry, will restart after sleep
                                    edpMap.put(path, null);
                                }
                            }
                        }
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (final InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    class EventDevicePoller implements Runnable {

        private volatile boolean stop = false;
        private final String eventDeviceFileName;
        public final int inputEventStructSize;

        public EventDevicePoller(final String eventDeviceFileName) {
            this.eventDeviceFileName = eventDeviceFileName;
            this.inputEventStructSize = Platform.is64Bit() ? 24 : 16;
        }

        @Override
        public void run() {
            final File eventDeviceFile = new File(eventDeviceFileName);
            if(Window.DEBUG_KEY_EVENT) {
                System.err.println("LinuxKeyEventTracker: Started "+eventDeviceFile);
            }
            eventDeviceFile.setReadOnly();

            final byte[] b = new byte[inputEventStructSize];
            /**
             * The Linux input event interface.
             * http://www.kernel.org/doc/Documentation/input/input.txt
             *
             * struct input_event {
             *  struct timeval time;
             *	unsigned short type;
             *	unsigned short code;
             *	unsigned int value;
             * };
             */
            final ByteBuffer bb = ByteBuffer.wrap(b);
            final StructAccessor s = new StructAccessor(bb);
            InputStream fis;
            try {
                fis = new FileInputStream(eventDeviceFile);
            } catch (final FileNotFoundException e) {
                stop=true;
                return;
            }

            long timeSeconds;
            long timeSecondFraction;
            short type;
            short code;
            int value;

            short keyCode=KeyEvent.VK_UNDEFINED;
            char keyChar=' ';
            short eventType=0;
            int modifiers=0;

            loop:
                while(!stop) {
                    int remaining = inputEventStructSize;
                    while( remaining > 0 ) {
                        int read = 0;
                        try {
                            read = fis.read(b, 0, remaining);
                        } catch (final IOException e) {
                            stop = true;
                            if(Window.DEBUG_KEY_EVENT) {
                                System.err.println("LinuxKeyEventTracker: [read "+read+", remaining "+remaining+"] "+e.getMessage()+" on "+eventDeviceFile);
                                e.printStackTrace();
                            }
                            break loop;
                        }
                        if(read<0) {
                            stop = true; // EOF of event device file !?
                            if(Window.DEBUG_KEY_EVENT) {
                                System.err.println("LinuxKeyEventTracker: [read "+read+", remaining "+remaining+"] EOF on "+eventDeviceFile);
                            }
                            break loop;
                        } else {
                            remaining -= read;
                        }
                    }

                    if( 16 == inputEventStructSize ) {
                        // 32bit: 16 bytes
                        timeSeconds = s.getIntAt(0);
                        timeSecondFraction = s.getShortAt(4);
                        type = s.getShortAt(8);
                        code = s.getShortAt(10);
                        value = s.getIntAt(12);
                    } else {
                        // 64bit: 24 bytes
                        timeSeconds = s.getLongAt(0);
                        timeSecondFraction = s.getLongAt(8);
                        type = s.getShortAt(16);
                        code = s.getShortAt(18);
                        value = s.getIntAt(20);
                    }

                    /*
                     * Linux sends Keyboard events in the following order:
                     * EV_MSC (optional, contains scancode)
                     * EV_KEY
                     * SYN_REPORT (sent before next key)
                     */

                    switch(type) {
                    case 0: // SYN_REPORT
                        // Clear
                        eventType = 0;
                        keyCode = KeyEvent.VK_UNDEFINED;
                        keyChar = 0; // Print null for unprintable char.
                        if(Window.DEBUG_KEY_EVENT) {
                            System.err.println("[SYN_REPORT----]");
                        }
                        break;
                    case 1: // EV_KEY
                        keyCode = LinuxEVKey2NewtVKey(code); // The device independent code.
                        keyChar = NewtVKey2Unicode(keyCode, modifiers); // The printable character w/ key modifiers.
                        if(Window.DEBUG_KEY_EVENT) {
                            System.err.println("[EV_KEY: [time "+timeSeconds+":"+timeSecondFraction+"] type "+type+" / code "+code+" = value "+value);
                        }

                        switch(value) {
                        case 0:
                            eventType=KeyEvent.EVENT_KEY_RELEASED;

                            switch(keyCode) {
                            case KeyEvent.VK_SHIFT:
                                modifiers &= ~InputEvent.SHIFT_MASK;
                                break;
                            case KeyEvent.VK_ALT:
                                modifiers &= ~InputEvent.ALT_MASK;
                                break;
                            case KeyEvent.VK_ALT_GRAPH:
                                modifiers &= ~InputEvent.ALT_GRAPH_MASK;
                                break;
                            case KeyEvent.VK_CONTROL:
                                modifiers &= ~InputEvent.CTRL_MASK;
                                break;
                            }

                            sendKeyEvent(eventType, modifiers, keyCode, keyCode, keyChar);
                            if(Window.DEBUG_KEY_EVENT) {
                                System.err.println("[event released] keyCode: "+keyCode+" keyChar: "+keyChar+ " modifiers: "+modifiers);
                            }
                            break;
                        case 1:
                            eventType=KeyEvent.EVENT_KEY_PRESSED;

                            switch(keyCode) {
                            case KeyEvent.VK_SHIFT:
                                modifiers |= InputEvent.SHIFT_MASK;
                                break;
                            case KeyEvent.VK_ALT:
                                modifiers |= InputEvent.ALT_MASK;
                                break;
                            case KeyEvent.VK_ALT_GRAPH:
                                modifiers |= InputEvent.ALT_GRAPH_MASK;
                                break;
                            case KeyEvent.VK_CONTROL:
                                modifiers |= InputEvent.CTRL_MASK;
                                break;
                            }

                            sendKeyEvent(eventType, modifiers, keyCode, keyCode, keyChar);
                            if(Window.DEBUG_KEY_EVENT) {
                                System.err.println("[event pressed] keyCode: "+keyCode+" keyChar: "+keyChar+ " modifiers: "+modifiers);
                            }
                            break;
                        case 2:
                            eventType=KeyEvent.EVENT_KEY_PRESSED;
                            modifiers |= InputEvent.AUTOREPEAT_MASK;

                            switch(keyCode) {
                            case KeyEvent.VK_SHIFT:
                                modifiers |= InputEvent.SHIFT_MASK;
                                break;
                            case KeyEvent.VK_ALT:
                                modifiers |= InputEvent.ALT_MASK;
                                break;
                            case KeyEvent.VK_ALT_GRAPH:
                                modifiers |= InputEvent.ALT_GRAPH_MASK;
                                break;
                            case KeyEvent.VK_CONTROL:
                                modifiers |= InputEvent.CTRL_MASK;
                                break;
                            }

                            //Send syntetic autorepeat release
                            sendKeyEvent(KeyEvent.EVENT_KEY_RELEASED, modifiers, keyCode, keyCode, keyChar);
                            sendKeyEvent(eventType, modifiers, keyCode, keyCode, keyChar);
                            if(Window.DEBUG_KEY_EVENT) {
                                System.err.println("[event released auto] keyCode: "+keyCode+" keyChar: "+keyChar+ " modifiers: "+modifiers);
                                System.err.println("[event pressed auto] keyCode: "+keyCode+" keyChar: "+keyChar+ " modifiers: "+modifiers);
                            }
                            modifiers &= ~InputEvent.AUTOREPEAT_MASK;
                            break;
                        }
                        break;
                    case 4: // EV_MSC
                        if(code==4) { // MSC_SCAN
                            // scancode ignore, linux kernel specific
                        }
                        break;
                        // TODO: handle joystick events
                        // TODO: handle mouse events
                        // TODO: handle headphone/hdmi connector events
                    default: // Print number.
                        if(Window.DEBUG_KEY_EVENT) {
                            System.err.println("TODO EventDevicePoller: [time "+timeSeconds+":"+timeSecondFraction+"] type "+type+" / code "+code+" = value "+value);
                        }
                    }
                }

            if(null != fis) {
                try {
                    fis.close();
                } catch (final IOException e) {
                }
            }
            if(Window.DEBUG_KEY_EVENT) {
                System.err.println("LinuxKeyEventTracker: Stopped "+eventDeviceFile);
            }
            stop=true;
        }

        private char NewtVKey2Unicode(final short VK, final int modifiers) {
            if( KeyEvent.isPrintableKey(VK, true) ) {
                if((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
                    return (char)VK;
                } else {
                    return String.valueOf((char)VK).toLowerCase().charAt(0);
                }
            }
            return 0;
        }

        @SuppressWarnings("unused")
        private char LinuxEVKey2Unicode(final short EVKey) {
            // This is the stuff normally mapped by a system keymap

            switch(EVKey) {
            case 17: // w
                return 'w';
            case 31: // s
                return 's';
            case 30: // a
                return 'a';
            case 32: // d
                return 'd';
            case 1: // ESC
                return 27;
            case 28: // Enter
            case 96: // Keypad Enter
                return '\n';
            case 57: // Space
                return ' ';
            case 11: // 0
            case 82: // Numpad 0
                return '0';
            case 2: // 1
            case 79: // Numpad 1
                return '1';
            case 3: // 2
            case 80: // Numpad 1
                return '2';
            case 4: // 3
            case 81: // Numpad 3
                return '3';
            case 5: // 4
            case 75: // Numpad 4
                return '4';
            case 6: // 5
            case 76: // Numpad 5
                return '5';
            case 7: // 6
            case 77: // Numpad 6
                return '6';
            case 8: // 7
            case 71: // Numpad 7
                return '7';
            case 9: // 8
            case 72: // Numpad 8
                return '8';
            case 10: // 9
            case 73: // Numpad 9
                return '9';

            default:
            }

            return 0;
        }

        private short LinuxEVKey2NewtVKey(final short EVKey) {

            switch(EVKey) {
            case 1: // ESC
                return KeyEvent.VK_ESCAPE;
            case 2: // 1
                return KeyEvent.VK_1;
            case 79: // Numpad 1
                return KeyEvent.VK_NUMPAD1;
            case 3: // 2
                return KeyEvent.VK_2;
            case 80: // Numpad 2
                return KeyEvent.VK_NUMPAD2;
            case 4: // 3
                return KeyEvent.VK_3;
            case 81: // Numpad 3
                return KeyEvent.VK_NUMPAD3;
            case 5: // 4
                return KeyEvent.VK_4;
            case 75: // Numpad 4
                return KeyEvent.VK_NUMPAD4;
            case 6: // 5
                return KeyEvent.VK_5;
            case 76: // Numpad 5
                return KeyEvent.VK_NUMPAD5;
            case 7: // 6
                return KeyEvent.VK_6;
            case 77: // Numpad 6
                return KeyEvent.VK_NUMPAD6;
            case 8: // 7
                return KeyEvent.VK_7;
            case 71: // Numpad 7
                return KeyEvent.VK_NUMPAD7;
            case 9: // 8
                return KeyEvent.VK_8;
            case 72: // Numpad 8
                return KeyEvent.VK_NUMPAD8;
            case 10: // 9
                return KeyEvent.VK_9;
            case 73: // Numpad 9
                return KeyEvent.VK_NUMPAD9;
            case 11: // 0
                return KeyEvent.VK_0;
            case 82: // Numpad 0
                return KeyEvent.VK_NUMPAD0;
            case 12:
                return KeyEvent.VK_MINUS;
            case 13:
                return KeyEvent.VK_EQUALS;
            case 14: // Backspace
                return KeyEvent.VK_BACK_SPACE;

            case 15:
                return KeyEvent.VK_TAB;
            case 16:
                return KeyEvent.VK_Q;
            case 17: // w
                return KeyEvent.VK_W;
            case 18:
                return KeyEvent.VK_E;
            case 19:
                return KeyEvent.VK_R;
            case 20:
                return KeyEvent.VK_T;
            case 21:
                return KeyEvent.VK_Y;
            case 22:
                return KeyEvent.VK_U;
            case 23:
                return KeyEvent.VK_I;
            case 24:
                return KeyEvent.VK_O;
            case 25:
                return KeyEvent.VK_P;
            case 26: // left brace
                return KeyEvent.VK_LEFT_PARENTHESIS;
            case 27: // right brace
                return KeyEvent.VK_RIGHT_PARENTHESIS;
            case 28: // Enter
            case 96: // Keypad Enter
                return KeyEvent.VK_ENTER;

            case 29: // left ctrl
                return KeyEvent.VK_CONTROL;
            case 30: // a
                return KeyEvent.VK_A;
            case 31: // s
                return KeyEvent.VK_S;
            case 32: // d
                return KeyEvent.VK_D;
            case 33:
                return KeyEvent.VK_F;
            case 34:
                return KeyEvent.VK_G;
            case 35:
                return KeyEvent.VK_H;
            case 36:
                return KeyEvent.VK_J;
            case 37:
                return KeyEvent.VK_K;
            case 38:
                return KeyEvent.VK_L;
            case 39:
                return KeyEvent.VK_SEMICOLON;
            case 40: // apostrophe
                return KeyEvent.VK_QUOTE;
            case 41: // grave
                return KeyEvent.VK_BACK_QUOTE;

            case 42: // left shift
                return KeyEvent.VK_SHIFT;
            case 43:
                return KeyEvent.VK_BACK_SLASH;
            case 44:
                return KeyEvent.VK_Z;
            case 45:
                return KeyEvent.VK_X;
            case 46:
                return KeyEvent.VK_C;
            case 47:
                return KeyEvent.VK_V;
            case 48:
                return KeyEvent.VK_B;
            case 49:
                return KeyEvent.VK_N;
            case 50:
                return KeyEvent.VK_M;
            case 51:
                return KeyEvent.VK_COMMA;
            case 52: // dot
                return KeyEvent.VK_PERIOD;
            case 53:
                return KeyEvent.VK_SLASH;
            case 54:
                return KeyEvent.VK_SHIFT;
            case 55: // kp asterisk
                return KeyEvent.VK_ASTERISK;
            case 56: // left alt
                return KeyEvent.VK_ALT;
            case 57: // Space
                return KeyEvent.VK_SPACE;
            case 58:
                return KeyEvent.VK_CAPS_LOCK;

            case 59:
                return KeyEvent.VK_F1;
            case 60:
                return KeyEvent.VK_F2;
            case 61:
                return KeyEvent.VK_F3;
            case 62:
                return KeyEvent.VK_F4;
            case 63:
                return KeyEvent.VK_F5;
            case 64:
                return KeyEvent.VK_F6;
            case 65:
                return KeyEvent.VK_F7;
            case 66:
                return KeyEvent.VK_F8;
            case 67:
                return KeyEvent.VK_F9;
            case 68:
                return KeyEvent.VK_F10;

            case 69:
                return KeyEvent.VK_NUM_LOCK;
            case 70:
                return KeyEvent.VK_SCROLL_LOCK;

            case 74: // kp minus
                return KeyEvent.VK_MINUS;
            case 78: // kp plus
                return KeyEvent.VK_PLUS;
            case 83: // kp dot
                return KeyEvent.VK_PERIOD;

            // TODO: add mappings for japanese special buttons
            case 85: // zenkakuhankaku
            case 86: // 102nd
                break; // FIXME

            case 87:
                return KeyEvent.VK_F11;
            case 88:
                return KeyEvent.VK_F12;

            case 89: // ro
                return KeyEvent.VK_ROMAN_CHARACTERS;
            case 90: // Katakana
                return KeyEvent.VK_KATAKANA;
            case 91:
                return KeyEvent.VK_HIRAGANA;

            case 92: // kenkan
                break; // FIXME
            case 93: // katakana hiragana
                break; // FIXME
            case 94: // mu henkan
                break; // FIXME
            case 95: // kp jp comma
                break; // FIXME

            case 97: // right ctrl
                return KeyEvent.VK_CONTROL;
            case 98: // kp slash
                return KeyEvent.VK_SLASH;

            case 99: // sysrq
                break; // FIXME

            case 100: // right alt
                return KeyEvent.VK_ALT;
            case 101: // linefeed
                break; // FIXME
            case 102: // home
                return KeyEvent.VK_HOME;
            case 103: // KEY_UP
                return KeyEvent.VK_UP;
            case 104:
                return KeyEvent.VK_PAGE_UP;
            case 105: // KEY_LEFT
                return KeyEvent.VK_LEFT;
            case 106: // KEY_RIGHT
                return KeyEvent.VK_RIGHT;
            case 107:
                return KeyEvent.VK_END;
            case 108: // KEY_DOWN
                return KeyEvent.VK_DOWN;
            case 109:
                return KeyEvent.VK_PAGE_DOWN;
            case 110:
                return KeyEvent.VK_INSERT;
            case 111: // del
                return KeyEvent.VK_DELETE;

            case 112: // macro
                break; // FIXME DEAD_MACRON?
            case 113: // mute
                break; // FIXME
            case 114: // vol up
                break; // FIXME
            case 115: // vol down
                break; // FIXME
            case 116: // power
                break; // FIXME

            case 117: // kp equals
                return KeyEvent.VK_EQUALS;
            case 118: // kp plus minux
                break; // FIXME
            case 119: // pause
                return KeyEvent.VK_PAUSE;
            case 120: // scale AL compiz scale expose
                break; // FIXME
            case 121: // kp comma
                return KeyEvent.VK_COMMA;
            case 122: // hangeul
                break; // FIXME
            case 123: // hanja
                break; // FIXME
            case 124: // yen
                break; // FIXME

            case 125: // left meta
            case 126: // right meta
                return KeyEvent.VK_META;
            case 127: // compose
                return KeyEvent.VK_COMPOSE;

            case 128: // stop
                return KeyEvent.VK_STOP;
            case 129: // again
                return KeyEvent.VK_AGAIN;
            case 130: // properties
                return KeyEvent.VK_PROPS;
            case 131: // undo
                return KeyEvent.VK_UNDO;
            case 132: // front
                break; // FIXME
            case 133: // copy
                return KeyEvent.VK_COPY;
            case 134: // open
                break; // FIXME
            case 135: // paste
                return KeyEvent.VK_PASTE;
            case 136: // find
                return KeyEvent.VK_FIND;
            case 137: // cut
                return KeyEvent.VK_CUT;
            case 138: // help
                return KeyEvent.VK_HELP;
            case 139: // menu
                break; // FIXME
            case 140: // calc
                break; // FIXME
            case 141: // setup
                break; // FIXME
            case 142: // sleep
                break; // FIXME
            case 143: // wakeup
                break; // FIXME
            case 144: // file
                break; // FIXME
            case 145: // send file
                break; // FIXME
            case 146: // delete file
                break; // FIXME
            case 147: // xfer
                break; // FIXME
            case 148: // prog1
                break; // FIXME
            case 149: // prog2
                break; // FIXME
            case 150: // www
                break; // FIXME
            case 151: // msdos
                break; // FIXME
            case 152: // coffee
                break; // FIXME
            case 153: // direction
                break; // FIXME
            case 154: // cycle windows
                break; // FIXME
            case 155: // mail
                break; // FIXME
            case 156: // bookmarks
                break; // FIXME
            case 157: // computer
                break; // FIXME
            case 158: // back
                break; // FIXME
            case 159: // forward
                break; // FIXME
            case 160: // close cd
                break; // FIXME
            case 161: // eject cd
                break; // FIXME
            case 162: // eject close cd
                break; // FIXME
            case 163: // next song
                break; // FIXME
            case 164: // play pause
                break; // FIXME
            case 165: // previous song
                break; // FIXME
            case 166: // stop cd
                break; // FIXME
            case 167: // record
                break; // FIXME
            case 168: // rewind
                break; // FIXME
            case 169: // phone
                break; // FIXME
            case 170: // ISO
                break; // FIXME
            case 171: // config
                break; // FIXME
            case 172: // home page
                break; // FIXME
            case 173: // refresh
                break; // FIXME
            case 174: // exit
                break; // FIXME
            case 175: // move
                break; // FIXME
            case 176: // edit
                break; // FIXME
            case 177: // scroll up
                break; // FIXME PAGE_UP?
            case 178: // scroll down
                break; // FIXME PAGE_DOWN?
            case 179: // kp left paren
                return KeyEvent.VK_LEFT_PARENTHESIS;
            case 180: // kp right paren
                return KeyEvent.VK_RIGHT_PARENTHESIS;
            case 181: // new
                break; // FIXME
            case 182: // redo
                break; // FIXME

            case 183: // F13
                return KeyEvent.VK_F13;
            case 184: // F14
                return KeyEvent.VK_F14;
            case 185: // F15
                return KeyEvent.VK_F15;
            case 186: // F16
                return KeyEvent.VK_F16;
            case 187: // F17
                return KeyEvent.VK_F17;
            case 188: // F18
                return KeyEvent.VK_F18;
            case 189: // F19
                return KeyEvent.VK_F19;
            case 190: // F20
                return KeyEvent.VK_F20;
            case 191: // F21
                return KeyEvent.VK_F21;
            case 192: // F22
                return KeyEvent.VK_F22;
            case 193: // F23
                return KeyEvent.VK_F23;
            case 194: // F24
                return KeyEvent.VK_F24;

            case 200: // play cd
                break; // FIXME
            case 201: // pause cd
                break; // FIXME
            case 202: // prog 3
                break; // FIXME
            case 203: // prog 4
                break; // FIXME
            case 204: // dashboard
                break; // FIXME
            case 205: // suspend
                break; // FIXME
            case 206: // close
                break; // FIXME
            case 207: // play
                break; // FIXME
            case 208: // fast forward
                break; // FIXME
            case 210: // print
                return KeyEvent.VK_PRINTSCREEN; // FIXME ?
            case 211: // HP
                break; // FIXME
            case 212: // camera
                break; // FIXME
            case 213: // sound
                break; // FIXME
            case 214: // question
                break; // FIXME
            case 215: // email
                break; // FIXME
            case 216: // chat
                break; // FIXME
            case 217: // search
                break; // FIXME
            case 218: // connect
                break; // FIXME
            case 219: // finance
                break; // FIXME
            case 220: // sport
                break; // FIXME
            case 221: // shop
                break; // FIXME
            case 222: // alt erase
                break; // FIXME
            case 223: // cancel
                break; // FIXME
            case 224: // brightness down
                break; // FIXME
            case 225: // brightness up
                break; // FIXME
            case 226: // media
                break; // FIXME
            case 227: // switch video mode
                break; // FIXME
            case 228: // kb dillum toggle
                break; // FIXME
            case 229: // kb dillum down
                break; // FIXME
            case 230: // kb dillum up
                break; // FIXME
            case 231: // send
                break; // FIXME
            case 232: // reply
                break; // FIXME
            case 233: // forward mail
                break; // FIXME
            case 234: // save
                break; // FIXME
            case 235: // documents
                break; // FIXME
            case 236: // battery
                break; // FIXME
            case 237: // bluetooth
                break; // FIXME
            case 238: // wlan
                break; // FIXME
            case 239: // UWB
                break; // FIXME
            case 240: // unknown
                return KeyEvent.VK_UNDEFINED;
            case 241: // video next
                break; // FIXME
            case 242: // video prev
                break; // FIXME
            case 243: // brightness cycle
                break; // FIXME
            case 244: // brightness zero
                break; // FIXME
            case 245: // display off
                break; // FIXME
            case 246: // wimax
                break; // FIXME
            case 247: // rf kill radio off
                break; // FIXME
            case 248: // mic mute
                break; // FIXME

            default:
            }

            if(Window.DEBUG_KEY_EVENT) {
                System.err.println("TODO LinuxEVKey2NewtVKey: Unmapped EVKey "+EVKey);
            }

            return KeyEvent.VK_UNDEFINED;
        }
    }
}
