/*
 * Copyright (c) 2002-2016, the original author or authors.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * http://www.opensource.org/licenses/bsd-license.php
 */
package jline.console;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.HashMap;
import java.util.Map;

import jline.TerminalFactory;
import jline.WindowsTerminal;
import jline.console.completer.AggregateCompleter;
import jline.console.completer.ArgumentCompleter;
import jline.console.completer.Completer;
import jline.console.completer.NullCompleter;
import jline.console.completer.StringsCompleter;
import jline.console.history.History;
import jline.console.history.MemoryHistory;
import jline.internal.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static jline.console.ConsoleReaderTest.WindowsKey.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;

/**
 * Tests for the {@link ConsoleReader}.
 */
public class ConsoleReaderTest
{
    
    private ByteArrayOutputStream output;
    
    @Before
    public void setUp() throws Exception {
        TerminalFactory.configure(TerminalFactory.AUTO);
        TerminalFactory.reset();
        System.setProperty(Configuration.JLINE_CONFIGURATION, "/no-such-file");
        System.setProperty(WindowsTerminal.DIRECT_CONSOLE, "false");
        System.setProperty(ConsoleReader.JLINE_INPUTRC, "/no/such/file");
        Configuration.reset();
    }

    @After
    public void tearDown() throws Exception {
        TerminalFactory.get().restore();
        TerminalFactory.reset();
    }

    private void assertWindowsKeyBehavior(String expected, char[] input) throws Exception {
        StringBuilder buffer = new StringBuilder();
        buffer.append(input);
        ConsoleReader reader = createConsole(buffer.toString());
        assertNotNull(reader);
        String line = reader.readLine();
        assertEquals(expected, line);
    }

    private ConsoleReader createConsole() throws Exception {
        return createConsole("");
    }

    private ConsoleReader createConsole(String chars) throws Exception {
        return createConsole(chars.getBytes(Configuration.getEncoding()));
    }

    private ConsoleReader createConsole(byte[] bytes) throws Exception {
        return createConsole(null, bytes);
    }

    private ConsoleReader createConsole(String appName, byte[] bytes) throws Exception {
        InputStream in = new ByteArrayInputStream(bytes);
        output = new ByteArrayOutputStream();
        ConsoleReader reader = new ConsoleReader(appName, in, output, null);
        reader.setHistory(createSeededHistory());
        return reader;
    }

    private History createSeededHistory() {
        History history = new MemoryHistory();
        history.add("dir");
        history.add("cd c:\\");
        history.add("mkdir monkey");
        return history;
    }

    @Test
    public void testReadline() throws Exception {
        ConsoleReader consoleReader = createConsole("Sample String\r\n");
        assertNotNull(consoleReader);
        String line = consoleReader.readLine();
        assertEquals("Sample String", line);
    }

    @Test
    public void testReadlineOutputsThePlatformLineSeparator() throws Exception {
        ConsoleReader consoleReader = createConsole("Sample String\r\n");
        assertNotNull(consoleReader);
        String line = consoleReader.readLine();
        String out = output.toString();
        if (TerminalFactory.get() instanceof WindowsTerminal) {
            assertEquals("Sample String\r\n", out);
        } else {
            assertEquals("Sample String\n", out);
        }
    }

    @Test
    public void testReadlineWithUnicode() throws Exception {
        System.setProperty("input.encoding", "UTF-8");
        ConsoleReader consoleReader = createConsole("\u6771\u00E9\u00E8\r\n");
        assertNotNull(consoleReader);
        String line = consoleReader.readLine();
        assertEquals("\u6771\u00E9\u00E8", line);
    }
    
    @Test
    public void testReadlineWithMask() throws Exception {
        ConsoleReader consoleReader = createConsole("Sample String\r\n");
        assertNotNull(consoleReader);
        String line = consoleReader.readLine('*');
        assertEquals("Sample String", line);
        assertEquals("*************", output.toString().trim());
    }

    @Test
    public void testDeleteOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            'S', 's',
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) LEFT_ARROW_KEY.code,
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) DELETE_KEY.code, '\r', 'n'
        };
        assertWindowsKeyBehavior("S", characters);
    }

    @Test
    public void testNumpadDeleteOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            'S', 's',
            (char) NUMPAD_KEY_INDICATOR.code,
            (char) LEFT_ARROW_KEY.code,
            (char) NUMPAD_KEY_INDICATOR.code,
            (char) DELETE_KEY.code, '\r', 'n'
        };
        assertWindowsKeyBehavior("S", characters);
    }

    @Test
    public void testHomeKeyOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            'S', 's',
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) HOME_KEY.code, 'x', '\r', '\n'
        };
        assertWindowsKeyBehavior("xSs", characters);

    }

    @Test
    public void testEndKeyOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            'S', 's',
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) HOME_KEY.code, 'x',
            (char) SPECIAL_KEY_INDICATOR.code, (char) END_KEY.code,
            'j', '\r', '\n'
        };
        assertWindowsKeyBehavior("xSsj", characters);
    }

    @Test
    public void testPageUpOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) PAGE_UP_KEY.code, '\r', '\n'
        };
        assertWindowsKeyBehavior("dir", characters);
    }

    @Test
    public void testPageDownOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) PAGE_DOWN_KEY.code, '\r', '\n'
        };
        assertWindowsKeyBehavior("mkdir monkey", characters);
    }

    @Test
    public void testEscapeOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            's', 's', 's',
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) ESCAPE_KEY.code, '\r', '\n'
        };
        assertWindowsKeyBehavior("", characters);
    }

    @Test
    public void testInsertOnWindowsTerminal() throws Exception {
        // test only works on Windows
        assumeTrue(TerminalFactory.get() instanceof WindowsTerminal);

        char[] characters = new char[]{
            'o', 'p', 's',
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) HOME_KEY.code,
            (char) SPECIAL_KEY_INDICATOR.code,
            (char) INSERT_KEY.code, 'o', 'o', 'p', 's', '\r', '\n'
        };
        assertWindowsKeyBehavior("oops", characters);
    }

    @Test
    public void testExpansion() throws Exception {
        ConsoleReader reader = createConsole();
        MemoryHistory history = new MemoryHistory();
        history.setMaxSize(3);
        history.add("foo");
        history.add("dir");
        history.add("cd c:\\");
        history.add("mkdir monkey");
        reader.setHistory(history);

        assertEquals("echo a!", reader.expandEvents("echo a!"));
        assertEquals("mkdir monkey ; echo a!", reader.expandEvents("!! ; echo a!"));
        assertEquals("echo ! a", reader.expandEvents("echo ! a"));
        assertEquals("echo !\ta", reader.expandEvents("echo !\ta"));

        assertEquals("mkdir barey", reader.expandEvents("^monk^bar^"));
        assertEquals("mkdir barey", reader.expandEvents("^monk^bar"));
        assertEquals("a^monk^bar", reader.expandEvents("a^monk^bar"));

        assertEquals("mkdir monkey", reader.expandEvents("!!"));
        assertEquals("echo echo a", reader.expandEvents("echo !#a"));

        assertEquals("mkdir monkey", reader.expandEvents("!mk"));
        try {
            reader.expandEvents("!mz");
            fail("expected IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            assertEquals("!mz: event not found", e.getMessage());
        }

        assertEquals("mkdir monkey", reader.expandEvents("!?mo"));
        assertEquals("mkdir monkey", reader.expandEvents("!?mo?"));

        assertEquals("mkdir monkey", reader.expandEvents("!-1"));
        assertEquals("cd c:\\", reader.expandEvents("!-2"));
        assertEquals("cd c:\\", reader.expandEvents("!3"));
        assertEquals("mkdir monkey", reader.expandEvents("!4"));
        try {
            reader.expandEvents("!20");
            fail("expected IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            assertEquals("!20: event not found", e.getMessage());
        }
        try {
            reader.expandEvents("!-20");
            fail("expected IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            assertEquals("!-20: event not found", e.getMessage());
        }
    }

    @Test
    public void testNumericExpansions() throws Exception {
        ConsoleReader reader = createConsole();
        MemoryHistory history = new MemoryHistory();
        history.setMaxSize(3);

        // Seed history with three entries:
        // 1 history1
        // 2 history2
        // 3 history3
        history.add("history1");
        history.add("history2");
        history.add("history3");
        reader.setHistory(history);

        // Validate !n
        assertExpansionIllegalArgumentException(reader, "!0");
        assertEquals("history1", reader.expandEvents("!1"));
        assertEquals("history2", reader.expandEvents("!2"));
        assertEquals("history3", reader.expandEvents("!3"));
        assertExpansionIllegalArgumentException(reader, "!4");

        // Validate !-n
        assertExpansionIllegalArgumentException(reader, "!-0");
        assertEquals("history3", reader.expandEvents("!-1"));
        assertEquals("history2", reader.expandEvents("!-2"));
        assertEquals("history1", reader.expandEvents("!-3"));
        assertExpansionIllegalArgumentException(reader, "!-4");

        // Validate !!
        assertEquals("history3", reader.expandEvents("!!"));

        // Add two new entries. Because maxSize=3, history is:
        // 3 history3
        // 4 history4
        // 5 history5
        history.add("history4");
        history.add("history5");

        // Validate !n
        assertExpansionIllegalArgumentException(reader, "!0");
        assertExpansionIllegalArgumentException(reader, "!1");
        assertExpansionIllegalArgumentException(reader, "!2");
        assertEquals("history3", reader.expandEvents("!3"));
        assertEquals("history4", reader.expandEvents("!4"));
        assertEquals("history5", reader.expandEvents("!5"));
        assertExpansionIllegalArgumentException(reader, "!6");

        // Validate !-n
        assertExpansionIllegalArgumentException(reader, "!-0");
        assertEquals("history5", reader.expandEvents("!-1"));
        assertEquals("history4", reader.expandEvents("!-2"));
        assertEquals("history3", reader.expandEvents("!-3"));
        assertExpansionIllegalArgumentException(reader, "!-4");

        // Validate !!
        assertEquals("history5", reader.expandEvents("!!"));
    }

    @Test
    public void testArgsExpansion() throws Exception {
        ConsoleReader reader = createConsole();
        MemoryHistory history = new MemoryHistory();
        history.setMaxSize(3);
        reader.setHistory(history);

        // we can't go back to previous arguments if there are none
        try {
            reader.expandEvents("!$");
            fail("expected IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            assertEquals("!$: event not found", e.getMessage());
        }

        // if no arguments were given, it should expand to the command itself
        history.add("ls");
        assertEquals("ls", reader.expandEvents("!$"));

        // now we can expand to the last argument
        history.add("ls /home");
        assertEquals("/home", reader.expandEvents("!$"));

        //we always take the last argument
        history.add("ls /home /etc");
        assertEquals("/etc", reader.expandEvents("!$"));

        //make sure we don't add spaces accidentally
        history.add("ls /home  /foo ");
        assertEquals("/foo", reader.expandEvents("!$"));
    }

	/**
	 * Validates that an 'event not found' IllegalArgumentException is thrown
	 * for the expansion event.
	 */
    protected void assertExpansionIllegalArgumentException(ConsoleReader reader, String event) throws Exception {
        try {
            reader.expandEvents(event);
            fail("Expected IllegalArgumentException for " + event);
        } catch (IllegalArgumentException e) {
            assertEquals(event + ": event not found", e.getMessage());
        }
    }

    @Test
    public void testIllegalExpansionDoesntCrashReadLine() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        InputStream in = new ByteArrayInputStream("!f\r\n".getBytes());
        ConsoleReader reader = new ConsoleReader(in, baos);
        reader.setExpandEvents(true);
        reader.setBellEnabled(true);
        MemoryHistory history = new MemoryHistory();
        reader.setHistory(history);

        String line = reader.readLine();

        assertEquals("", line);
        assertEquals(0, history.size());
    }

    @Test
    public void testStoringHistory() throws Exception {
        ConsoleReader reader = createConsole("foo ! bar\r\n");
        MemoryHistory history = new MemoryHistory();
        reader.setHistory(history);
        reader.setExpandEvents(true);

        String line = reader.readLine();
        assertEquals("foo ! bar", line);

        history.previous();
        assertEquals("foo \\! bar", history.current());

        reader = createConsole("cd c:\\docs\r\n");
        history = new MemoryHistory();
        reader.setHistory(history);
        reader.setExpandEvents(true);

        line = reader.readLine();
        assertEquals("cd c:\\docs", line);

        history.previous();
        assertEquals("cd c:\\docs", history.current());
    }

    @Test
    public void testExpansionAndHistoryWithEscapes() throws Exception {

        /*
         * Tests the results of the ConsoleReader.readLine() call and the line
         * stored in history. For each input, it tests the with-expansion and
         * without-expansion case.
         */

        ConsoleReader reader = null;

        // \! (escaped expansion v1)
        reader = createConsole("echo ab\\!ef", true, "cd");
        assertReadLine("echo ab!ef", reader);
        assertHistory("echo ab\\!ef", reader);

        reader = createConsole("echo ab\\!ef", false, "cd");
        assertReadLine("echo ab\\!ef", reader);
        assertHistory("echo ab\\!ef", reader);

        // \!\! (escaped expansion v2)
        reader = createConsole("echo ab\\!\\!ef", true, "cd");
        assertReadLine("echo ab!!ef", reader);
        assertHistory("echo ab\\!\\!ef", reader);

        reader = createConsole("echo ab\\!\\!ef", false, "cd");
        assertReadLine("echo ab\\!\\!ef", reader);
        assertHistory("echo ab\\!\\!ef", reader);

        // !! (expansion)
        reader = createConsole("echo ab!!ef", true, "cd");
        assertReadLine("echo abcdef", reader);
        assertHistory("echo abcdef", reader);

        reader = createConsole("echo ab!!ef", false, "cd");
        assertReadLine("echo ab!!ef", reader);
        assertHistory("echo ab!!ef", reader);

        // \G (backslash no expansion)
        reader = createConsole("echo abc\\Gdef", true, "cd");
        assertReadLine("echo abc\\Gdef", reader);
        assertHistory("echo abc\\Gdef", reader);

        reader = createConsole("echo abc\\Gdef", false, "cd");
        assertReadLine("echo abc\\Gdef", reader);
        assertHistory("echo abc\\Gdef", reader);

        // \^ (escaped expansion)
        reader = createConsole("\\^abc^def", true, "echo abc");
        assertReadLine("^abc^def", reader);
        assertHistory("\\^abc^def", reader);

        reader = createConsole("\\^abc^def", false, "echo abc");
        assertReadLine("\\^abc^def", reader);
        assertHistory("\\^abc^def", reader);

        // ^^ (expansion)
        reader = createConsole("^abc^def", true, "echo abc");
        assertReadLine("echo def", reader);
        assertHistory("echo def", reader);

        reader = createConsole("^abc^def", false, "echo abc");
        assertReadLine("^abc^def", reader);
        assertHistory("^abc^def", reader);
    }

    private ConsoleReader createConsole(String input, boolean expandEvents, String... historyItems) throws Exception {
        ConsoleReader consoleReader = createConsole(input + "\r\n");
        MemoryHistory history = new MemoryHistory();
        if (historyItems != null) {
            for (String historyItem : historyItems) {
                history.add(historyItem);
            }
        }
        consoleReader.setHistory(history);
        consoleReader.setExpandEvents(expandEvents);
        return consoleReader;
    }

    private void assertReadLine(String expected, ConsoleReader consoleReader) throws Exception {
        assertEquals(expected, consoleReader.readLine());
    }

    private void assertHistory(String expected, ConsoleReader consoleReader) {
        History history = consoleReader.getHistory();
        history.previous();
        assertEquals(expected, history.current());
    }

    @Test
    public void testStoringHistoryWithExpandEventsOff() throws Exception {
        ConsoleReader reader = createConsole("foo ! bar\r\n");
        MemoryHistory history = new MemoryHistory();
        reader.setHistory(history);
        reader.setExpandEvents(false);

        String line = reader.readLine();
        assertEquals("foo ! bar", line);

        history.previous();
        assertEquals("foo ! bar", history.current());
    }

    @Test
    public void testMacro() throws Exception {
        ConsoleReader consoleReader = createConsole("\u0018(foo\u0018)\u0018e\r\n");
        assertNotNull(consoleReader);
        String line = consoleReader.readLine();
        assertEquals("foofoo", line);
    }

    @Test
    public void testInput() throws Exception {
        System.setProperty(ConsoleReader.JLINE_INPUTRC, getClass().getResource("/jline/internal/config1").toExternalForm());
        try {
            ConsoleReader consoleReader = createConsole("\u0018(foo\u0018)\u0018e\r\n");
            assertNotNull(consoleReader);

            assertEquals(Operation.UNIVERSAL_ARGUMENT, consoleReader.getKeys().getBound("" + ((char)('U' - 'A' + 1))));
            assertEquals("Function Key \u2671", consoleReader.getKeys().getBound("\u001b[11~"));
            assertEquals(null, consoleReader.getKeys().getBound(((char)('X' - 'A' + 1)) + "q"));

            consoleReader = createConsole("bash", new byte[0]);
            assertNotNull(consoleReader);
            assertEquals("\u001bb\"\u001bf\"", consoleReader.getKeys().getBound(((char)('X' - 'A' + 1)) + "q"));
        } finally {
            System.clearProperty(ConsoleReader.JLINE_INPUTRC);
        }
    }

    @Test
    public void testInput2() throws Exception {
        System.setProperty(ConsoleReader.JLINE_INPUTRC, getClass().getResource("/jline/internal/config2").toExternalForm());
        try {
            ConsoleReader consoleReader = createConsole("Bash", new byte[0]);
            assertNotNull(consoleReader);
            assertNotNull(consoleReader.getKeys().getBound("\u001b" + ((char) ('V' - 'A' + 1))));

        } finally {
            System.clearProperty(ConsoleReader.JLINE_INPUTRC);
        }
    }

    @Test
    public void testInputBadConfig() throws Exception {
        System.setProperty(ConsoleReader.JLINE_INPUTRC, getClass().getResource("/jline/internal/config-bad").toExternalForm());
        try {
            ConsoleReader consoleReader = createConsole("Bash", new byte[0]);
            assertNotNull(consoleReader);
            assertEquals("\u001bb\"\u001bf\"", consoleReader.getKeys().getBound(((char) ('X' - 'A' + 1)) + "q"));
        } finally {
            System.clearProperty(ConsoleReader.JLINE_INPUTRC);
        }
    }

    @Test
    public void testBell() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ConsoleReader consoleReader = new ConsoleReader(System.in, baos);

        assertFalse("default bell should be disabled", consoleReader.getBellEnabled());

        consoleReader.beep();

        assertEquals("out should not have received bell", 0, baos.toByteArray().length);

        consoleReader.setBellEnabled(true);

        assertTrue("bell should have been enabled", consoleReader.getBellEnabled());

        consoleReader.beep();

        assertEquals("out should have received bell", 1, baos.toByteArray().length);
        assertEquals("out should have received bell", ConsoleReader.KEYBOARD_BELL, baos.toByteArray()[0]);
    }

    @Test
    public void testCallbacks() throws Exception {
        final ConsoleReader consoleReader = createConsole("sample stringx\r\n");
        consoleReader.addTriggeredAction('x', new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                consoleReader.getCursorBuffer().clear();
            }
        });
        String line = consoleReader.readLine();
        // The line would have "sample stringx" in it, if a callback to clear it weren't mapped to the 'x' key:
        assertEquals("", line);
    }

    @Test
    public void testComplete() throws Exception {
        PipedInputStream in = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream(in);
        output = new ByteArrayOutputStream();

        ConsoleReader console = new ConsoleReader(in, output);
        Completer nil = new NullCompleter();
        Completer read = new StringsCompleter("read");
        Completer and = new StringsCompleter("and");
        Completer save = new StringsCompleter("save");
        Completer aggregator = new AggregateCompleter(
                new ArgumentCompleter(read, and, save, nil)
        );
        console.addCompleter(aggregator);

        out.write("read an\t\n".getBytes());

        assertEquals("read and ", console.readLine());

        out.write("read and\033[D\033[D\t\n".getBytes());

        assertEquals("read andnd", console.readLine());

        out.close();
    }

    @Test
    public void testDefaultBuffer() throws Exception {
        ConsoleReader consoleReader = createConsole("\r\n");
        assertNotNull(consoleReader);
        String line = consoleReader.readLine(null, null, "foo");
        assertEquals("foo", line);
    }

    @Test
    public void testReadBinding() throws Exception {
        ConsoleReader consoleReader = createConsole("abcde");
        assertNotNull(consoleReader);

        KeyMap map = new KeyMap("custom");
        map.bind("bc", 1l);
        map.bind("e", 2l);

        Object b = consoleReader.readBinding(map);
        assertEquals(1l, b);
        assertEquals("bc", consoleReader.getLastBinding());
        b = consoleReader.readBinding(map);
        assertEquals(2l, b);
        assertEquals("e", consoleReader.getLastBinding());
        b = consoleReader.readBinding(map);
        assertNull(b);
    }

  /**
     * Windows keys.
     * <p/>
     * Constants copied <tt>wincon.h</tt>.
     */
    public static enum WindowsKey
    {
        /**
         * On windows terminals, this character indicates that a 'special' key has
         * been pressed. This means that a key such as an arrow key, or delete, or
         * home, etc. will be indicated by the next character.
         */
        SPECIAL_KEY_INDICATOR(224),

        /**
         * On windows terminals, this character indicates that a special key on the
         * number pad has been pressed.
         */
        NUMPAD_KEY_INDICATOR(0),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR,
         * this character indicates an left arrow key press.
         */
        LEFT_ARROW_KEY(75),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates an
         * right arrow key press.
         */
        RIGHT_ARROW_KEY(77),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates an up
         * arrow key press.
         */
        UP_ARROW_KEY(72),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates an
         * down arrow key press.
         */
        DOWN_ARROW_KEY(80),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates that
         * the delete key was pressed.
         */
        DELETE_KEY(83),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates that
         * the home key was pressed.
         */
        HOME_KEY(71),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates that
         * the end key was pressed.
         */
        END_KEY(79),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates that
         * the page up key was pressed.
         */
        PAGE_UP_KEY(73),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates that
         * the page down key was pressed.
         */
        PAGE_DOWN_KEY(81),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR
         * this character indicates that
         * the insert key was pressed.
         */
        INSERT_KEY(82),

        /**
         * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR,
         * this character indicates that the escape key was pressed.
         */
        ESCAPE_KEY(0),;

        public final int code;

        WindowsKey(final int code) {
            this.code = code;
        }

        private static final Map<Integer, WindowsKey> codes;

        static {
            Map<Integer, WindowsKey> map = new HashMap<Integer, WindowsKey>();

            for (WindowsKey key : WindowsKey.values()) {
                map.put(key.code, key);
            }

            codes = map;
        }

        public static WindowsKey valueOf(final int code) {
            return codes.get(code);
        }
    }
}
