function Keyboard(keyMapper, language, offsetY) {
	this.keyMapper = keyMapper;
	this.language = language;

	this.keyHandlerMapper = {};

	this.keyIndex = 0;
	this.keyDataList = [];
	this.keyList = [];

	this.allKeyHash = {};
	this.normalKeyHash = {};

	this.keyUpReservations = [];

	this.highlightKey = null;
	this.highlightShiftKey = null;


	game.input.keyboard.addCallbacks(this, this.keyDown, this.keyUp, null);
	// game.input.keyboard.processKeyDown = this.keyDown;
	// game.input.keyboard.processKeyUp = this.keyUp;


	var shiftKey = game.input.keyboard.addKey(Phaser.KeyCode.SHIFT);
	shiftKey.onDown.add(this.shifted, this);
	shiftKey.onUp.add(this.unshifted, this);

	this.initKeyData();

	for(var i = 0; i < this.keyDataList.length; i++) {
		this.keyList[i] = new KeyButton(
			this.keyDataList[i].x,
			this.keyDataList[i].y + offsetY,
			this.keyDataList[i].width,
			this.keyDataList[i].height,
			this.keyDataList[i].normalText,
			this.keyDataList[i].shiftText,
			this.keyDataList[i].handSide,
			this.keyDataList[i].row,
			this.keyDataList[i].fingerType,
			this.keyDataList[i].alignType
		);

		this.allKeyHash[this.keyDataList[i].keyID] = this.keyList[i];
	}
}

Keyboard.prototype.addCallback = function(keyCode, keyDownHandler, keyUpHandler, callerClassName) {
	if(this.keyHandlerMapper[keyCode] != undefined) {
		this.keyHandlerMapper[keyCode] = null;
	}

	this.keyHandlerMapper[keyCode] = {
		"keyDownHandler": keyDownHandler,
		"keyUpHandler": keyUpHandler,
		"callerClassName": callerClassName
	}

	// console.log(this.keyHandlerMapper);
}

Keyboard.prototype.hideHighlight = function(text) {
	var keyID = this.keyMapper.getKeyIDOfText(text);
	this.allKeyHash[keyID].hideHighlight();

	if(!this.keyMapper.isShiftText(text))
		return;

	var handSide = this.allKeyHash[keyID].handSide;
	if(handSide === KeyButton.LEFT_HAND)
		this.allKeyHash["ShiftRight"].hideHighlight();
	else
		this.allKeyHash["ShiftLeft"].hideHighlight();
}

Keyboard.prototype.showHighlight = function(text) {
	var keyID = this.keyMapper.getKeyIDOfText(text);
	var key = this.allKeyHash[keyID];
	key.showHighlight();
}

Keyboard.prototype.getKeyOfText = function(text) {
	var keyID = this.keyMapper.getKeyIDOfText(text);
	return this.allKeyHash[keyID];
}

Keyboard.prototype.getKey = function(keyID) {
	return this.allKeyHash[keyID];
}

Keyboard.prototype.keyDown = function(char) {
	var keyCode = char.code;
	this.setPressedSprite(keyCode);

	if(this.keyHandlerMapper[char.keyCode] == undefined) {
		// console.log(keyCode + " is down but not registered to KeyMapper");
		return;
	}

	var keyMapper = this.keyHandlerMapper[char.keyCode];
	var keyDownHandler = keyMapper["keyDownHandler"];
	if(keyDownHandler == undefined)
		return;

	if(isDebugMode())
		console.log(keyCode + " key is down (" + keyMapper["callerClassName"] + ")");
	keyDownHandler();
}

Keyboard.prototype.setPressedSprite = function(keyCode) {
	var keyID = this.keyMapper.getKeyIDOfText(keyCode);
	var key = this.allKeyHash[keyID];
	if(key === undefined) {
		console.log(keyCode + " is pressed but not registered to KeyMapper");
		return;
	}

	switch(keyCode) {
		case "Tab":
		case "CapsLock":
		case "MetaLeft":
		case "MetaRight":
		console.log(keyCode + " is pressed but not show");
		return;
	}

	key.setTargetColor();
}

Keyboard.prototype.keyUp = function(char) {
	var keyCode = char.code;
	this.setNormalSprite(keyCode);

	if(this.keyHandlerMapper[char.keyCode] == undefined) {
		// console.log(keyCode + " is up but not registered to KeyMapper");
		return;
	}

	var keyMapper = this.keyHandlerMapper[char.keyCode];
	var keyUpHandler = keyMapper["keyUpHandler"];
	if(keyUpHandler == undefined)
		return;

	if(isDebugMode())
		console.log(keyCode + " key is up (" + keyMapper["callerClassName"] + ")");
	keyUpHandler();
}

Keyboard.prototype.setNormalSprite = function(keyCode) {
	var keyID = this.keyMapper.getKeyIDOfText(keyCode);
	var key = this.allKeyHash[keyID];
	if(key !== undefined)
		key.setNormalColor();
}



Keyboard.prototype.shifted = function() {
	for(var i = 0; i < this.keyDataList.length; i++) {
		this.keyList[i].onShiftPressed();
	}
}

Keyboard.prototype.unshifted = function() {
	for(var i = 0; i < this.keyDataList.length; i++) {
		this.keyList[i].onShiftUnpressed();
	}
}


Keyboard.prototype.keyPress = function(char) {
	// self.checkTypingContents(event);
	// console.log(char);
	if(game.input.keyboard.isDown(Phaser.Keyboard.SHIFT))
		console.log("shift is pressed");
}


Keyboard.prototype.makeKeyDataObject = function(keyID, normalText, shiftText, handSide, row, fingerType, x, y, width, height, alignType) {
	var keyDataObject = {};
	keyDataObject.keyID = keyID;
	keyDataObject.normalText = normalText;
	keyDataObject.shiftText = shiftText;
	keyDataObject.handSide = handSide;
	keyDataObject.row = row;
	keyDataObject.fingerType = fingerType;
	keyDataObject.x = x;
	keyDataObject.y = y;
	keyDataObject.width = width;
	keyDataObject.height = height;
	keyDataObject.alignType = alignType;

	return keyDataObject;
}

Keyboard.prototype.makeNextNormalKeyDataObject = function(keyID, prevKeyData, normalText, shiftText, handSide, fingerType) {
	return this.makeKeyDataObject(
		keyID,
		normalText,
		shiftText,
		handSide,
		prevKeyData.row,
		fingerType,
		prevKeyData.x + prevKeyData.width + Keyboard.KEY_GAP_PX,
		prevKeyData.y,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.NORMAL_KEY
	);
}


Keyboard.prototype.addFunctionKeyData = function(keyID, handSide, row, fingerType, x, y, width, height, type) {
	var normalText = this.keyMapper.getNormalText(keyID, this.language);
	var shiftText = this.keyMapper.getShiftText(keyID, this.language);

	var newKey = this.makeKeyDataObject(
		keyID,
		normalText, shiftText,
		handSide, row, fingerType,
		x, y,
		width, height,
		type
	);
	this.keyDataList[this.keyIndex] = newKey;
	this.keyIndex++;
}

Keyboard.prototype.addNextFunctionKeyData = function(keyID, handSide, fingerType, width, height, type) {
	var prevKey = this.keyDataList[this.keyIndex - 1];
	var normalText = this.keyMapper.getNormalText(keyID, this.language);
	var shiftText = this.keyMapper.getShiftText(keyID, this.language);

	var newKey = this.makeKeyDataObject(
		keyID,
		normalText, shiftText,
		handSide, prevKey.row, fingerType,
		prevKey.x + prevKey.width + Keyboard.KEY_GAP_PX, prevKey.y,
		width, height,
		type
	);
	this.keyDataList[this.keyIndex] = newKey;
	this.keyIndex++;

	// this.normalKeyHash[keyID] = newKey;
}

Keyboard.prototype.addNextNormalKeyData = function(keyID, handSide, fingerType) {
	var prevKey = this.keyDataList[this.keyIndex - 1];
	var normalText = this.keyMapper.getNormalText(keyID, this.language);
	var shiftText = this.keyMapper.getShiftText(keyID, this.language);

	var newKey = this.makeNextNormalKeyDataObject(
		keyID,
		prevKey,
		normalText, shiftText,
		handSide, fingerType
	);
	this.keyDataList[this.keyIndex] = newKey;
	this.keyIndex++;

	this.normalKeyHash[keyID] = newKey;
}

Keyboard.prototype.getKeyPosX = function(text) {
	var keyID = this.keyMapper.getKeyIDOfText(text);
	var key = this.allKeyHash[keyID];
	if(key !== undefined)
		return key.setting.x;
}

Keyboard.prototype.getKeyPosY = function(text) {
	var keyID = this.keyMapper.getKeyIDOfText(text);
	var key = this.allKeyHash[keyID];
	if(key !== undefined)
		return key.setting.y;
}


Keyboard.prototype.initKeyData = function(keyMapper) {
	var row1offsetY = 0;
	if(isTypingTestApp())
		row1offsetY = 80;

	// row 1
	this.addFunctionKeyData(
		"Backquote",
		KeyButton.LEFT_HAND,
		Keyboard.ROW_1,
		KeyButton.LITTLE_FINGER,
		Keyboard.KEYBOARD_OFFSET_POS_X,
		Keyboard.ROW_1_POS_Y + row1offsetY,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.NORMAL_FUNCTION_KEY
	);
	this.addNextNormalKeyData("Digit1", KeyButton.LEFT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("Digit2", KeyButton.LEFT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("Digit3", KeyButton.LEFT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("Digit4", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("Digit5", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("Digit6", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("Digit7", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("Digit8", KeyButton.RIGHT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("Digit9", KeyButton.RIGHT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("Digit0", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("Minus", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("Equal", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextFunctionKeyData(
		"Backspace",
		KeyButton.RIGHT_HAND,
		KeyButton.LITTLE_FINGER,
		Keyboard.BACKSPACE_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.RIGHT_FUNCTION_KEY
	);

	// row 2
	this.addFunctionKeyData(
		"Tab",
		KeyButton.LEFT_HAND,
		Keyboard.ROW_2,
		KeyButton.NONE_FINGER,
		Keyboard.KEYBOARD_OFFSET_POS_X,
		Keyboard.ROW_2_POS_Y + row1offsetY,
		Keyboard.TAB_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextNormalKeyData("KeyQ", KeyButton.LEFT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("KeyW", KeyButton.LEFT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("KeyE", KeyButton.LEFT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("KeyR", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyT", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyY", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyU", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyI", KeyButton.RIGHT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("KeyO", KeyButton.RIGHT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("KeyP", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("BracketLeft", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("BracketRight", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("Backslash", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);

	// row 3
	this.addFunctionKeyData(
		"CapsLock",
		KeyButton.LEFT_HAND,
		Keyboard.ROW_3,
		KeyButton.NONE_FINGER,
		Keyboard.KEYBOARD_OFFSET_POS_X,
		Keyboard.ROW_3_POS_Y + row1offsetY,
		Keyboard.CAPS_LOCK_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextNormalKeyData("KeyA", KeyButton.LEFT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("KeyS", KeyButton.LEFT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("KeyD", KeyButton.LEFT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("KeyF", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER_BASELINE);
	this.addNextNormalKeyData("KeyG", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyH", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyJ", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER_BASELINE);
	this.addNextNormalKeyData("KeyK", KeyButton.RIGHT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("KeyL", KeyButton.RIGHT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("Semicolon", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("Quote", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextFunctionKeyData(
		"Enter",
		KeyButton.RIGHT_HAND,
		KeyButton.LITTLE_FINGER,
		Keyboard.ENTER_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.RIGHT_FUNCTION_KEY
	);

	// row 4
	this.addFunctionKeyData(
		"ShiftLeft",
		KeyButton.LEFT_HAND,
		Keyboard.ROW_4,
		KeyButton.LITTLE_FINGER,
		Keyboard.KEYBOARD_OFFSET_POS_X,
		Keyboard.ROW_4_POS_Y + row1offsetY,
		Keyboard.SHIFT_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextNormalKeyData("KeyZ", KeyButton.LEFT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextNormalKeyData("KeyX", KeyButton.LEFT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("KeyC", KeyButton.LEFT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("KeyV", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyB", KeyButton.LEFT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyN", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("KeyM", KeyButton.RIGHT_HAND, KeyButton.INDEX_FINGER);
	this.addNextNormalKeyData("Comma", KeyButton.RIGHT_HAND, KeyButton.MIDDLE_FINGER);
	this.addNextNormalKeyData("Period", KeyButton.RIGHT_HAND, KeyButton.RING_FINGER);
	this.addNextNormalKeyData("Slash", KeyButton.RIGHT_HAND, KeyButton.LITTLE_FINGER);
	this.addNextFunctionKeyData(
		"ShiftRight",
		KeyButton.RIGHT_HAND,
		KeyButton.LITTLE_FINGER,
		Keyboard.SHIFT_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.RIGHT_FUNCTION_KEY
	);

	// row 5
	this.addFunctionKeyData(
		"ControlLeft",
		KeyButton.LEFT_HAND,
		Keyboard.ROW_5,
		KeyButton.NONE_FINGER,
		Keyboard.KEYBOARD_OFFSET_POS_X,
		Keyboard.ROW_5_POS_Y + row1offsetY,
		Keyboard.CTRL_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"window",
		KeyButton.LEFT_HAND,
		KeyButton.NONE_FINGER,
		Keyboard.WINDOW_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"AltLeft",
		KeyButton.LEFT_HAND,
		KeyButton.NONE_FINGER,
		Keyboard.ALT_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"Chinese",
		KeyButton.LEFT_HAND,
		KeyButton.NONE_FINGER,
		Keyboard.CHINESE_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.LEFT_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"Space",
		KeyButton.BOTH_HAND,
		KeyButton.THUMB,
		Keyboard.SPACE_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.CENTER_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"KoreanEnglish",
		KeyButton.RIGHT_HAND,
		KeyButton.NONE_FINGER,
		Keyboard.KOREAN_ENGLISH_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.RIGHT_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"AltRight",
		KeyButton.RIGHT_HAND,
		KeyButton.NONE_FINGER,
		Keyboard.ALT_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.RIGHT_FUNCTION_KEY
	);
	this.addNextFunctionKeyData(
		"ControlRight",
		KeyButton.RIGHT_HAND,
		KeyButton.NONE_FINGER,
		Keyboard.CTRL_KEY_WIDTH_PX,
		Keyboard.DEFAULT_KEY_SIZE_PX,
		KeyButton.RIGHT_FUNCTION_KEY
	);
}


Keyboard.ENGLISH						= "english";
Keyboard.KOREAN							= "korean";

Keyboard.DEFAULT_KEY_SIZE_PX			= 46;
Keyboard.BACKSPACE_KEY_WIDTH_PX			= Keyboard.DEFAULT_KEY_SIZE_PX * 1.5;
Keyboard.TAB_KEY_WIDTH_PX				= Keyboard.DEFAULT_KEY_SIZE_PX * 1.5;
Keyboard.CAPS_LOCK_KEY_WIDTH_PX			= Keyboard.DEFAULT_KEY_SIZE_PX * 1.9;
Keyboard.ENTER_KEY_WIDTH_PX				= Keyboard.DEFAULT_KEY_SIZE_PX * 1.75;
Keyboard.SHIFT_KEY_WIDTH_PX				= Keyboard.DEFAULT_KEY_SIZE_PX * 2.4;
Keyboard.CTRL_KEY_WIDTH_PX				= Keyboard.DEFAULT_KEY_SIZE_PX * 1.7;
Keyboard.WINDOW_KEY_WIDTH_PX			= Keyboard.DEFAULT_KEY_SIZE_PX;
Keyboard.ALT_KEY_WIDTH_PX				= Keyboard.DEFAULT_KEY_SIZE_PX * 1.5;
Keyboard.CHINESE_KEY_WIDTH_PX			= Keyboard.DEFAULT_KEY_SIZE_PX;
Keyboard.SPACE_KEY_WIDTH_PX				= Keyboard.DEFAULT_KEY_SIZE_PX * 5.9;
Keyboard.KOREAN_ENGLISH_KEY_WIDTH_PX	= Keyboard.DEFAULT_KEY_SIZE_PX;

Keyboard.KEY_GAP_PX					= 6;


Keyboard.NO_OFFSET_POS_Y			= 0;
Keyboard.WHAC_A_MOLE_OFFSET_POS_Y	= 70;

Keyboard.KEYBOARD_OFFSET_POS_X		= 150;
Keyboard.ROW_1_POS_Y				= 320;
Keyboard.ROW_2_POS_Y				= Keyboard.ROW_1_POS_Y + Keyboard.DEFAULT_KEY_SIZE_PX + Keyboard.KEY_GAP_PX;
Keyboard.ROW_3_POS_Y				= Keyboard.ROW_2_POS_Y + Keyboard.DEFAULT_KEY_SIZE_PX + Keyboard.KEY_GAP_PX;
Keyboard.ROW_4_POS_Y				= Keyboard.ROW_3_POS_Y + Keyboard.DEFAULT_KEY_SIZE_PX + Keyboard.KEY_GAP_PX;
Keyboard.ROW_5_POS_Y				= Keyboard.ROW_4_POS_Y + Keyboard.DEFAULT_KEY_SIZE_PX + Keyboard.KEY_GAP_PX;


Keyboard.ROW_1		= 1;
Keyboard.ROW_2		= 2;
Keyboard.ROW_3		= 3;
Keyboard.ROW_4		= 4;
Keyboard.ROW_5		= 5;