/*
 * Copyright 2013 - Scriptel Corporation
 */
package com.scriptel.easyscript;
import static com.scriptel.easyscript.EasyScriptStreamingState.*;
import com.scriptel.easyscript.cardswipeprotocols.STNCardSwipeProtocol;
import com.scriptel.easyscript.signatureprotocols.STNSignatureProtocol;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * This is a utility class that can be used to interact with Scriptel ScripTouch
 * EasyScript products. It is capable of parsing and rendering keyboard
 * strings generated when the digitizer is signed.
 */
public final class EasyScript {
     /**
     * This contains the current version of the library.
     */
    public static final String LIBRARY_VERSION = "3.5.29";
    /**
     * This contains the date on which the library was built (YYYY-MM-DD HH:MM:SS).
     */
    public static final String LIBRARY_BUILD_DATE = "2022-08-30 17:12:23-0400";
    /**
     * The protocol by which to decode incoming signature strings.
     */
    private SignatureProtocol signatureProtocol;
    /**
     * This is the protocol by which we'll decode incoming card swipe strings.
     */
    private CardSwipeProtocol cardProtocol;
    /**
     * Contains the incoming signature.
     */
    private final StringBuilder signatureBuffer = new StringBuilder();
    /**
     * Contains the header of the signature.
     */
    private SignatureMetaData metadata;
    /**
     * An instance EasyScriptDecoder interface.
     */
    private EasyScriptDecoder decoder;
    /**
     * A list of coordinate listeners.
     */
    private final List<EasyScriptEventListener> eventListeners = new ArrayList<EasyScriptEventListener>();
    /**
     * The current state of the streaming parser.
     */
    private EasyScriptStreamingState currentState = UNKNOWN;
    /**
     * The current position in the stream.
     */
    private int currentPosition = 0;
    /**
     * Waits for our decoders to let us know when the signature is over.
     */
    private final EndOfStreamListener endListener = new EndOfStreamListener();
    /**
     * Keeps track of the current card state.
     */
    private EasyScriptCardState cardState = EasyScriptCardState.UNKNOWN;
    /**
     * Keeps a card buffer we'll use to check to make sure we're looking at a card.
     */
    private final StringBuilder cardBuffer = new StringBuilder();
    /**
     * Whether or not we're currently intercepting (blocking) key presses in anticipation of a signature.
     */
    private boolean intercepting = false;
    
    /**
     * Constructor, creates a new instance of this type.
     */
    public EasyScript() {
        this(new STNSignatureProtocol(),new STNCardSwipeProtocol());
    }
    
    /**
     * Constructor, creates a new instance of this type. Pass in the protocol
     * of the device that you're using.
     * @param signatureProtocol Protocol for the device you're trying to decode signatures for.
     * @param cardProtocol Protocol for the device you're trying to decode card swipes for.
     */
    public EasyScript(SignatureProtocol signatureProtocol, CardSwipeProtocol cardProtocol) {
        this.signatureProtocol = signatureProtocol;
        this.cardProtocol = cardProtocol;
        
        if(this.signatureProtocol == null) {
            this.signatureProtocol = new STNSignatureProtocol();
        }
        
        if(this.cardProtocol == null) {
            this.cardProtocol = new STNCardSwipeProtocol();
        }
        
        this.addListener(endListener);
    }
    
    /**
     * This method will take a signature and will attempt to render it onto a
     * buffered image object.
     * @param s Signature to render
     * @param foreColor Foreground color (signature color) to use.
     * @param backColor Background color, null for transparent.
     * @param lineThickness The thickness of each stroke in pixels (not scaled).
     * @param scale How much to scale the resulting image.
     * @return Buffered Image containing the rendered signature.
     */
    public BufferedImage renderSignature(Signature s, Color foreColor, Color backColor, int lineThickness, float scale) {
        int width = (int)(signatureProtocol.getWidth()*scale);
        int height = (int)(signatureProtocol.getHeight()*scale);
        
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = (Graphics2D)image.getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setStroke(new BasicStroke(lineThickness));
        
        //Fill with background color
        if(backColor!=null) {
            g.setColor(backColor);
            g.fillRect(0, 0, width, height);
        }
        
        g.setColor(foreColor);
        if (s != null){
        for(int i=0;i<s.getStrokes().size();i++) {
            List<Coordinate> strokeList = s.getStrokes().get(i);
            for(int j=1;j<strokeList.size();j++) {
                Coordinate c1 = strokeList.get(j-1);
                Coordinate c2 = strokeList.get(j);
                
                int x1 = (int)Math.round(c1.getX()*scale);
                int y1 = (int)Math.round(c1.getY()*scale);
                int x2 = (int)Math.round(c2.getX()*scale);
                int y2 = (int)Math.round(c2.getY()*scale);
                
                g.drawLine(x1,y1,x2,y2);
            }
        }
        }
        
        return image;
    }
    
    /**
     * This method attempts to parse a magnetic card swipe from a ScripTouch
     * device with a magnetic strip reader.
     * @param swipe ScripTouch magnetic strip data.
     * @return Parsed card swipe.
     * @throws CardSwipeInvalidException Thrown in the event the card couldn't be properly parsed.
     */
    public CardSwipe parseCardSwipe(String swipe) throws CardSwipeInvalidException {
        if(swipe==null || swipe.isEmpty()) {
            throw new CardSwipeInvalidException("Card swipe stream is empty.",0);
        } else if(swipe.charAt(0) != getCardProtocol().getStartStream()) {
            throw new CardSwipeInvalidException("Card swipe stream doesn't start with the correct character ("+swipe.charAt(0)+" != "+getCardProtocol().getStartStream()+")",0);
        } else if(swipe.charAt(swipe.length()-1) != getCardProtocol().getEndStream()) {
            throw new CardSwipeInvalidException("Card swipe stream doesn't end with the correct character ("+swipe.charAt(swipe.length()-1)+" != "+getCardProtocol().getEndStream()+")",0);
        } else if(!(swipe.substring(1,getCardProtocol().getSentinel().length()+1).equalsIgnoreCase(getCardProtocol().getSentinel()))) {
            throw new CardSwipeInvalidException("Card swipe stream doesn't start with the correct sentinel.",1);
	}
        
        CardSwipe result = new CardSwipe();
        result.setProtocolVersion(swipe.substring(8,9));
        result.setCardData(swipe.substring(10,swipe.length()-1));
        
        result.setFinancialCard(FinancialCard.parse(result.getCardData()));
        result.setIdentificationCard(IdentificationCard.parse(result.getCardData()));
        
        return result;
    }
    
    /**
     * This method takes an EasyScript string and attempts to parse it into
     * a signature object.
     * @param sig Signature string to parse.
     * @return Parsed signature, unless the signature was canceled, then null.
     * @throws SignatureInvalidException Thrown in the event there is something wrong with the string.
     */
    public Signature parseSignature(String sig) throws SignatureInvalidException {
        if (sig == null) {
            throw new SignatureInvalidException("Signature was null", 0);
        }
        if (sig.isEmpty()) {
            throw new SignatureInvalidException("Signature is empty", 0);
        }
        if (sig.charAt(0) != signatureProtocol.getStartStream()) {
            throw new SignatureInvalidException("Signature does not start with the start sentinal", 0);
        }
        BatchListener listener = new BatchListener();
        addListener(listener);
        for (int i = 0; i < sig.length(); i++) {
            parse(sig.charAt(i));
        }
        removeListener(listener);
        if (!listener.isDone()) {
            throw new SignatureInvalidException("No end of signature sentinal found", 0);
        } else if(listener.isCanceled()) {
            return null;
        }
        return new Signature(metadata, listener.getStrokes());
    }         
    
    /**
     * This method allows you to stream in a set of characters and the library
     * will attempt to decode signatures and card swipes as the characters are
     * passed in. To use this method be sure to implement your own EasyScriptEventListener
     * and pass it to addListener() prior to passing characters to this function.
     * This listener will be called when specific events are generated by decoding
     * the stream.
     * @param chr Next character in the stream to parse.
     * @throws SignatureInvalidException Thrown in the event an unexpected condition occurs while parsing the stream.
     */
    public void parse(char chr) throws SignatureInvalidException {    
        try {
            currentPosition++;
            switch(currentState) {
                case UNKNOWN:
                    if(chr == signatureProtocol.getStartStream()) {
                        currentState = SIGNATURE_SENTINEL;
                        currentPosition = 1;
                        signatureBuffer.setLength(0);
                        intercepting = true;
                    } else if(chr == cardProtocol.getStartStream()) {
                        currentState = CARD_SENTINEL;
                        currentPosition = 1;
                        cardBuffer.setLength(0);
                        cardBuffer.append(chr);
                        cardState = EasyScriptCardState.UNKNOWN;
                    }
                    break;
                case CARD_SENTINEL:
                    EasyScriptCardState state = parseCard(chr);
                    if(state == EasyScriptCardState.CARD_PROCESSED || state == EasyScriptCardState.NOT_A_CARD) {
                        cardState = EasyScriptCardState.UNKNOWN;
                        currentState = UNKNOWN;
                        intercepting = false;
                    }
                    break;
                case SIGNATURE_SENTINEL:
                    if(chr == signatureProtocol.getPenUp()) {
                        if(signatureBuffer.toString().equals(signatureProtocol.getSentinel())) {
                            currentState = PROTOCOL_VERSION;
                            signatureBuffer.setLength(0);
                            metadata = new SignatureMetaData();
                        } else {
                            intercepting = false;
                            throw new SignatureInvalidException("Signature sentinel doesn't appear to be correct: "+signatureBuffer.toString(), currentPosition);
                        }
                    } else {
                        signatureBuffer.append(chr);
                    }
                    break;
                case PROTOCOL_VERSION:
                    if(chr == signatureProtocol.getPenUp()) {
                        metadata.setProtocolVersion(signatureBuffer.toString());
                        currentState = MODEL;
                        signatureBuffer.setLength(0);
                    } else {
                        signatureBuffer.append(chr);
                    }
                    break;
                case MODEL:
                    if(chr == signatureProtocol.getPenUp()) {
                        metadata.setModel(signatureBuffer.toString());
                        signatureBuffer.setLength(0);
                        currentState = FIRMWARE_VERSION;
                    } else {
                        signatureBuffer.append(chr);
                    }
                    break;
                case FIRMWARE_VERSION:
                    if(chr == signatureProtocol.getPenUp() || chr == signatureProtocol.getCancel() || chr == signatureProtocol.getEndStream()) {
                        metadata.setVersion(signatureBuffer.toString());
                        signatureBuffer.setLength(0);
                        
                        //We could have a beacon here, strip the protocol version
                        //down to just one character.
                        if(!metadata.getProtocolVersion().isEmpty()) {
                            metadata.setProtocolVersion(metadata.getProtocolVersion().substring(0, 1));
                        }

                        for(EasyScriptEventListener r : eventListeners) {
                            r.signatureMetaData(metadata);
                        }
                        
                        if (chr == signatureProtocol.getEndStream()) {
                            for (EasyScriptEventListener r : eventListeners) {
                                r.endOfSignature();
                            }                            
                        } else if (chr == signatureProtocol.getCancel()) {
                            for (EasyScriptEventListener r : eventListeners) {
                                r.cancel();
                            }                            
                        } else if("A".equals(metadata.getProtocolVersion()) || "B".equals(metadata.getProtocolVersion()) || "C".equals(metadata.getProtocolVersion())) {
                            //Uncompressed
                            currentState = SIGNATURE_UNCOMPRESSED;
                            decoder = new EasyScriptUncompressedDecoder(signatureProtocol, currentPosition, eventListeners);
                        } else if("D".equals(metadata.getProtocolVersion()) || "E".equals(metadata.getProtocolVersion())) {
                            //Compressed
                            currentState = SIGNATURE_COMPRESSED;
                            decoder = new EasyScriptCompressedDecoder(signatureProtocol, currentPosition, eventListeners);
                        } else {
                            intercepting = false;
                            throw new SignatureInvalidException("Unrecognized protocol version: "+signatureBuffer, currentPosition);
                        }
                    } else {
                        signatureBuffer.append(chr);
                    }
                    break;
                case SIGNATURE_UNCOMPRESSED:
                case SIGNATURE_COMPRESSED:
                    if(chr == signatureProtocol.getStartStream()) {
                        //Appears to be a new signature, assume the previous one was canceled.
                        for(EasyScriptEventListener r : eventListeners) {
                            r.cancel();
                        }
                        currentState = UNKNOWN;
                        signatureBuffer.setLength(0);
                        //Re-enter this function.
                        parse(chr);
                        break;
                    }
                    
                    if(chr == cardProtocol.getStartStream() && cardState != EasyScriptCardState.NOT_A_CARD) {
                        //This might be a card, give the card parser a swing at this.
                        currentState = (currentState == SIGNATURE_UNCOMPRESSED) ? CARD_INTERRUPTING_UNCOMPRESSED : CARD_INTERRUPTING_COMPRESSED;
                        cardState = EasyScriptCardState.UNKNOWN;
                        cardBuffer.setLength(0);
                        cardBuffer.append(chr);
                    } else {
                        decoder.parseSignature(chr);
                    }
                    break;
                case CARD_INTERRUPTING_UNCOMPRESSED:
                case CARD_INTERRUPTING_COMPRESSED:
                    state = parseCard(chr);
                    if(state == EasyScriptCardState.NOT_A_CARD) {
                        //We were mistaken, this wasn't a card swipe after all.
                        currentState = (currentState == CARD_INTERRUPTING_UNCOMPRESSED) ? SIGNATURE_UNCOMPRESSED : SIGNATURE_COMPRESSED;
                        for(int i = 0; i < cardBuffer.length(); i++) {
                            parse(cardBuffer.charAt(i));
                        }
                        cardState = EasyScriptCardState.UNKNOWN;
                        cardBuffer.setLength(0);
                    } else if(state == EasyScriptCardState.CARD_PROCESSED) {
                        currentState = (currentState == CARD_INTERRUPTING_UNCOMPRESSED) ? SIGNATURE_UNCOMPRESSED : SIGNATURE_COMPRESSED;
                    }
                    break;
                default:
                    //We're just going to ignore this character.
                    break;
            }
        } catch (SignatureInvalidException e) {
            currentState = UNKNOWN;
            throw e;
        }
    }
    
    private EasyScriptCardState parseCard(char chr) throws CardSwipeInvalidException {
        cardBuffer.append(chr);
        
        switch(cardState) {
            case UNKNOWN:
                if(chr == cardProtocol.getSentinel().charAt(cardBuffer.length() - 2)) {
                    cardState = EasyScriptCardState.CARD_DATA;
                    return EasyScriptCardState.CARD_DATA;
                }
                cardState = EasyScriptCardState.NOT_A_CARD;
                return EasyScriptCardState.NOT_A_CARD;
            case CARD_DATA:
                if(chr == cardProtocol.getEndStream()) {
                    CardSwipe swipe = this.parseCardSwipe(cardBuffer.toString());
                    for(EasyScriptEventListener r : eventListeners) {
                        r.cardSwipe(swipe);
                    }
                    cardState = EasyScriptCardState.CARD_PROCESSED;
                    return EasyScriptCardState.CARD_PROCESSED;
                }
                break;
            default:
                return EasyScriptCardState.UNKNOWN;
        }
        return EasyScriptCardState.UNKNOWN;
    }

    /**
     * This method attempts to parse a stream of keyboard events coming into a
     * JFrame and turning them into characters which then get passed to parse(char c).
     * @param evt Keyboard event to evaluate.
     * @throws SignatureInvalidException Thrown in the event of an unexpected condition.
     */
    public void parse(KeyEvent evt) throws SignatureInvalidException {
        if(evt.getID() == KeyEvent.KEY_PRESSED) {   // only interested in key downs
            Character c = keyboardEventToChar(evt);
            if(c != null) {
                parse(c);
            }
        }
    }
    
    /**
     * Gets the current signature protocol.
     * @return the signature protocol
     */
    public SignatureProtocol getSignatureProtocol() {
        return signatureProtocol;
    }

    /**
     * Gets the current card protocol.
     * @return the cardProtocol
     */
    public CardSwipeProtocol getCardProtocol() {
        return cardProtocol;
    }
    
    /**
     * Adds a listener interested in EasyScript events to the list of listeners
     * to notify.
     * @param cr The listener to add to the list.
     */
    public void addListener(EasyScriptEventListener cr) {
        eventListeners.add(cr);
    }
    
     /**
     * Removes a listener interested in EasyScript events from the list of listeners
     * to notify.
     * @param cr The listener to remove from the list.
     */
    public void removeListener(EasyScriptEventListener cr) {
        eventListeners.remove(cr);
    }
    
    /**
     * This listener is used to allow the decoder to implement end of stream and
     * cancel in their own way without requiring the outer state machine to
     * necessarily be aware of what the decoder is doing or why.
     */
    private class EndOfStreamListener implements EasyScriptEventListener {
        @Override
        public void receiveCoordinate(Coordinate coordinate) {
            //Not required
        }

        @Override
        public void newStroke() {
            //Not required
        }

        @Override
        public void cancel() {
            currentState = UNKNOWN;
            intercepting = false;
        }

        @Override
        public void endOfSignature() {
            currentState = UNKNOWN;
            intercepting = false;
        }

        @Override
        public void signatureMetaData(SignatureMetaData header) {
            //Not required
        }

        @Override
        public void cardSwipe(CardSwipe swipe) {
            //Not required
        }
    }
    
    /**
     * This class is used to collect and store events so that we can give an
     * entire signature at once without callers needing to implement an event
     * listener.
     */
    private class BatchListener implements EasyScriptEventListener {
        /**
         * This is the list of strokes that are lists of coordinates.
         */
        private final List<List<Coordinate>> strokes = new LinkedList<List<Coordinate>>();
        /**
         * The current stroke.
         */
        private List<Coordinate> stroke;
        /**
         * A card swipe, if one is detected.
         */
        private CardSwipe swipe;
        /**
         * Whether or not this signature is complete (canceled or otherwise).
         */
        private boolean done = false;
        /**
         * Whether or not this signature was canceled.
         */
        private boolean canceled = false;
        
        /**
         * Gets the list of strokes and coordinates that have been captured.
         * @return List of strokes with coordinates.
         */
        public List<List<Coordinate>> getStrokes() {
            return strokes;
        }
        
        /**
         * Gets a card swipe if one was found.
         * @return 
         */
        public CardSwipe getSwipe() {
            return swipe;
        }
        
        /**
         * Gets whether or not this signature is complete (canceled or otherwise).
         * @return Whether this signature is complete.
         */
        public boolean isDone() {
            return done;
        }
        
        /**
         * Gets whether or not this signature was canceled.
         * @return Whether or not this signature was canceled.
         */
        public boolean isCanceled() {
            return canceled;
        }
        
        /**
         * Gets a coordinate that will be added to the current stroke.
         * @param coordinate Coordinate to be added to the current stroke.
         */
        @Override
        public void receiveCoordinate(Coordinate coordinate) {
            stroke.add(coordinate);
        }

        /**
         * Adds a new stroke to the stack.
         */
        @Override
        public void newStroke() {
            stroke = new LinkedList<Coordinate>();
            strokes.add(stroke);
        }

        /**
         * Marks a signature canceled and done.
         */
        @Override
        public void cancel() {
            canceled = true;
            done = true;
        }

        /**
         * Marks a signature complete.
         */
        @Override
        public void endOfSignature() {
            done = true;
        }

        /**
         * Gets the signature metadata.
         * @param md 
         */
        @Override
        public void signatureMetaData(SignatureMetaData md) {
            metadata = md;
        }

        /**
         * Captures a card swipe.
         * @param swipe 
         */
        @Override
        public void cardSwipe(CardSwipe swipe) {
            this.swipe = swipe;
        }
    }
    
    /**
     * This method converts the evt to a scancode if possible.
     * It will not be possible if the OS or JVM in use does not support it.
     * @param evt
     * @return the scancode. 0 if the scancode could not be found.
     */
    private long getScancode(KeyEvent evt) {

        String s = evt.toString();
        final String scancodeConst = "scancode=";
        int begin = s.indexOf(scancodeConst);
        if (begin < 0) {
            return 0;
        }
        begin += scancodeConst.length();
        int end = s.indexOf(',', begin);
        if (end < 0) {
            end = s.length();
        }
        s = s.substring(begin, end);
        return Long.parseLong(s);
        
    }
    
    /**
    * This method (in theory) turns a keyboard event into a character in the 
    * US English keyboard. In practice these key events may get translated to
    * their regional counterparts and are not guaranteed to work for all
    * keyboards in every OS and every JVM.
    * @param evt Event to translate into character.
    * @return Character representing the pressed key in the US English keyboard layout.
    * @throws SignatureInvalidException Thrown in the event of a problem.
    */
    private Character keyboardEventToChar(KeyEvent evt) throws SignatureInvalidException {
        boolean shift = evt.isShiftDown();
        
        // see if the actual keyboard scancode is available
        long scancode = getScancode(evt);
        if (scancode != 0) {    // aha! This is great, we can do this right
            if (scancode < Integer.MIN_VALUE || scancode > Integer.MAX_VALUE) {
                return null;
            }
            switch ((int) scancode) {
                case 0x29:  return (!shift) ? '`' : '~';
                case 0x0b:  return (!shift) ? '0' : ')';
                case 0x02:  return (!shift) ? '1' : '!';
                case 0x03:  return (!shift) ? '2' : '@';
                case 0x04:  return (!shift) ? '3' : '#';
                case 0x05:  return (!shift) ? '4' : '$';
                case 0x06:  return (!shift) ? '5' : '%';
                case 0x07:  return (!shift) ? '6' : '^';
                case 0x08:  return (!shift) ? '7' : '&';
                case 0x09:  return (!shift) ? '8' : '*';
                case 0x0a:  return (!shift) ? '9' : '(';
                case 0x0c:  return (!shift) ? '-' : '_';
                case 0x0d:  return (!shift) ? '=' : '+';
                    
                case 0x10:  return (!shift) ? 'q' : 'Q';
                case 0x11:  return (!shift) ? 'w' : 'W';
                case 0x12:  return (!shift) ? 'e' : 'E';
                case 0x13:  return (!shift) ? 'r' : 'R';
                case 0x14:  return (!shift) ? 't' : 'T';
                case 0x15:  return (!shift) ? 'y' : 'Y';
                case 0x16:  return (!shift) ? 'u' : 'U';
                case 0x17:  return (!shift) ? 'i' : 'I';
                case 0x18:  return (!shift) ? 'o' : 'O';
                case 0x19:  return (!shift) ? 'p' : 'P';
                case 0x1a:  return (!shift) ? '[' : '{';
                case 0x1b:  return (!shift) ? ']' : '}';
                case 0x2b:  return (!shift) ? '\\' : '|';

                case 0x1e:  return (!shift) ? 'a' : 'A';
                case 0x1f:  return (!shift) ? 's' : 'S';
                case 0x20:  return (!shift) ? 'd' : 'D';
                case 0x21:  return (!shift) ? 'f' : 'F';
                case 0x22:  return (!shift) ? 'g' : 'G';
                case 0x23:  return (!shift) ? 'h' : 'H';
                case 0x24:  return (!shift) ? 'j' : 'J';
                case 0x25:  return (!shift) ? 'k' : 'K';
                case 0x26:  return (!shift) ? 'l' : 'L';
                case 0x27:  return (!shift) ? ';' : ':';
                case 0x28:  return (!shift) ? '\'' : '\"';
                  
                case 0x2c:  return (!shift) ? 'z' : 'Z';
                case 0x2d:  return (!shift) ? 'x' : 'X';
                case 0x2e:  return (!shift) ? 'c' : 'C';
                case 0x2f:  return (!shift) ? 'v' : 'V';
                case 0x30:  return (!shift) ? 'b' : 'B';
                case 0x31:  return (!shift) ? 'n' : 'N';
                case 0x32:  return (!shift) ? 'm' : 'M';
                case 0x33:  return (!shift) ? ',' : '<';
                case 0x34:  return (!shift) ? '.' : '>';
                case 0x35:  return (!shift) ? '/' : '?';
                    
                case 0x1c:  return '\r';
                case 0x39:  return ' ';
                default:    return null;
            }
        } else {                // Oh boy! This will not work many keyboards other than US-English
            switch(evt.getKeyCode()) {
                case KeyEvent.VK_BACK_QUOTE:    return (!shift) ? '`'  : '~';
                case KeyEvent.VK_1:             return (!shift) ? '1'  : '!';
                case KeyEvent.VK_2:             return (!shift) ? '2'  : '@';
                case KeyEvent.VK_3:             return (!shift) ? '3'  : '#';
                case KeyEvent.VK_4:             return (!shift) ? '4'  : '$';
                case KeyEvent.VK_5:             return (!shift) ? '5'  : '%';
                case KeyEvent.VK_6:             return (!shift) ? '6'  : '^';
                case KeyEvent.VK_7:             return (!shift) ? '7'  : '&';
                case KeyEvent.VK_8:             return (!shift) ? '8'  : '*';
                case KeyEvent.VK_9:             return (!shift) ? '9'  : '(';
                case KeyEvent.VK_0:             return (!shift) ? '0'  : ')';
                case KeyEvent.VK_MINUS:         return (!shift) ? '-'  : '_';
                case KeyEvent.VK_EQUALS:        return (!shift) ? '='  : '+';

                case KeyEvent.VK_Q:             return (!shift) ? 'q'  : 'Q';
                case KeyEvent.VK_W:             return (!shift) ? 'w'  : 'W';
                case KeyEvent.VK_E:             return (!shift) ? 'e'  : 'E';         
                case KeyEvent.VK_R:             return (!shift) ? 'r'  : 'R';
                case KeyEvent.VK_T:             return (!shift) ? 't'  : 'T';
                case KeyEvent.VK_Y:             return (!shift) ? 'y'  : 'Y';
                case KeyEvent.VK_U:             return (!shift) ? 'u'  : 'U';
                case KeyEvent.VK_I:             return (!shift) ? 'i'  : 'I';
                case KeyEvent.VK_O:             return (!shift) ? 'o'  : 'O';
                case KeyEvent.VK_P:             return (!shift) ? 'p'  : 'P';
                case KeyEvent.VK_OPEN_BRACKET:  return (!shift) ? '['  : '{';
                case KeyEvent.VK_CLOSE_BRACKET: return (!shift) ? ']'  : '}';
                case KeyEvent.VK_BACK_SLASH:    return (!shift) ? '\\' : '|';

                case KeyEvent.VK_A:             return (!shift) ? 'a'  : 'A';
                case KeyEvent.VK_S:             return (!shift) ? 's'  : 'S';
                case KeyEvent.VK_D:             return (!shift) ? 'd'  : 'D';         
                case KeyEvent.VK_F:             return (!shift) ? 'f'  : 'F';
                case KeyEvent.VK_G:             return (!shift) ? 'g'  : 'G';
                case KeyEvent.VK_H:             return (!shift) ? 'h'  : 'H';
                case KeyEvent.VK_J:             return (!shift) ? 'j'  : 'J';
                case KeyEvent.VK_K:             return (!shift) ? 'k'  : 'K';
                case KeyEvent.VK_L:             return (!shift) ? 'l'  : 'L';
                case KeyEvent.VK_SEMICOLON:     return (!shift) ? ';'  : ':';
                case KeyEvent.VK_QUOTE:         return (!shift) ? '\'' : '"';

                case KeyEvent.VK_Z:             return (!shift) ? 'z'  : 'Z';
                case KeyEvent.VK_X:             return (!shift) ? 'x'  : 'X';
                case KeyEvent.VK_C:             return (!shift) ? 'c'  : 'C';         
                case KeyEvent.VK_V:             return (!shift) ? 'v'  : 'V';
                case KeyEvent.VK_B:             return (!shift) ? 'b'  : 'B';
                case KeyEvent.VK_N:             return (!shift) ? 'n'  : 'N';
                case KeyEvent.VK_M:             return (!shift) ? 'm'  : 'M';
                case KeyEvent.VK_COMMA:         return (!shift) ? ','  : '<';
                case KeyEvent.VK_PERIOD:        return (!shift) ? '.'  : '>';
                case KeyEvent.VK_SLASH:         return (!shift) ? '/'  : '?';

                case KeyEvent.VK_SPACE:         return ' ';
                case KeyEvent.VK_ENTER:         return '\r';

                default: return null;
            }
        }
    }
    
 }
