package net.flashpunk.utils
{
	import flash.display.Stage;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.ui.Keyboard;
	import net.flashpunk.*;
	
	/**
	 * Static class updated by Engine. Use for defining and checking keyboard/mouse input.
	 */
	public class Input
	{
		/**
		 * An updated string containing the last 100 characters pressed on the keyboard.
		 * Useful for creating text input fields, such as highscore entries, etc.
		 */
		public static var keyString:String = "";
		
		/**
		 * The last key pressed.
		 */
		public static var lastKey:int;
		
		/**
		 * If the mouse button is down.
		 */
		public static var mouseDown:Boolean = false;
		
		/**
		 * If the mouse button is up.
		 */
		public static var mouseUp:Boolean = true;
		
		/**
		 * If the mouse button was pressed this frame.
		 */
		public static var mousePressed:Boolean = false;
		
		/**
		 * If the mouse button was released this frame.
		 */
		public static var mouseReleased:Boolean = false;
		
		/**
		 * If the mouse wheel was moved this frame.
		 */
		public static var mouseWheel:Boolean = false; 
		
		/**
		 * If the mouse wheel was moved this frame, this was the delta.
		 */
		public static function get mouseWheelDelta():int
		{
			if (mouseWheel)
			{
				mouseWheel = false;
				return _mouseWheelDelta;
			}
			return 0;
		}  
		
		/**
		 * X position of the mouse on the screen.
		 */
		public static function get mouseX():int
		{
			return FP.screen.mouseX;
		}
		
		/**
		 * Y position of the mouse on the screen.
		 */
		public static function get mouseY():int
		{
			return FP.screen.mouseY;
		}
		
		/**
		 * The absolute mouse x position on the screen (unscaled).
		 */
		public static function get mouseFlashX():int
		{
			return FP.stage.mouseX;
		}
		
		/**
		 * The absolute mouse y position on the screen (unscaled).
		 */
		public static function get mouseFlashY():int
		{
			return FP.stage.mouseY;
		}
		
		/**
		 * Defines a new input.
		 * @param	name		String to map the input to.
		 * @param	...keys		The keys to use for the Input.
		 */
		public static function define(name:String, ...keys):void
		{
			_control[name] = Vector.<int>(keys);
		}
		
		/**
		 * If the input or key is held down.
		 * @param	input		An input name or key to check for.
		 * @return	True or false.
		 */
		public static function check(input:*):Boolean
		{
			if (input is String)
			{
				var v:Vector.<int> = _control[input],
					i:int = v.length;
				while (i --)
				{
					if (v[i] < 0)
					{
						if (_keyNum > 0) return true;
						continue;
					}
					if (_key[v[i]]) return true;
				}
				return false;
			}
			return input < 0 ? _keyNum > 0 : _key[input];
		}
		
		/**
		 * If the input or key was pressed this frame.
		 * @param	input		An input name or key to check for.
		 * @return	True or false.
		 */
		public static function pressed(input:*):Boolean
		{
			if (input is String)
			{
				var v:Vector.<int> = _control[input],
					i:int = v.length;
				while (i --)
				{
					if ((v[i] < 0) ? _pressNum : _press.indexOf(v[i]) >= 0) return true;
				}
				return false;
			}
			return (input < 0) ? _pressNum : _press.indexOf(input) >= 0;
		}
		
		/**
		 * If the input or key was released this frame.
		 * @param	input		An input name or key to check for.
		 * @return	True or false.
		 */
		public static function released(input:*):Boolean
		{
			if (input is String)
			{
				var v:Vector.<int> = _control[input],
					i:int = v.length;
				while (i --)
				{
					if ((v[i] < 0) ? _releaseNum : _release.indexOf(v[i]) >= 0) return true;
				}
				return false;
			}
			return (input < 0) ? _releaseNum : _release.indexOf(input) >= 0;
		}
		
		/**
		 * Returns the keys mapped to the input name.
		 * @param	name		The input name.
		 * @return	A Vector of keys.
		 */
		public static function keys(name:String):Vector.<int>
		{
			return _control[name] as Vector.<int>;
		}
		
		/** @private Called by Engine to enable keyboard input on the stage. */
		public static function enable():void
		{
			if (!_enabled && FP.stage)
			{
				FP.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
				FP.stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
				FP.stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
				FP.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
				FP.stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
				_enabled = true;
			}
		}
		
		/** @private Called by Engine to update the input. */
		public static function update():void
		{
			while (_pressNum --) _press[_pressNum] = -1;
			_pressNum = 0;
			while (_releaseNum --) _release[_releaseNum] = -1;
			_releaseNum = 0;
			if (mousePressed) mousePressed = false;
			if (mouseReleased) mouseReleased = false;
		}
		
		/**
		 * Clears all input states.
		 */
		public static function clear():void
		{
			_press.length = _pressNum = 0;
			_release.length = _releaseNum = 0;
			var i:int = _key.length;
			while (i --) _key[i] = false;
			_keyNum = 0;
		}
		
		/** @private Event handler for key press. */
		private static function onKeyDown(e:KeyboardEvent = null):void
		{
			// get the keycode
			var code:int = lastKey = e.keyCode;
			
			// update the keystring
			if (code == Key.BACKSPACE) keyString = keyString.substring(0, keyString.length - 1);
			else if ((code > 47 && code < 58) || (code > 64 && code < 91) || code == 32)
			{
				if (keyString.length > KEYSTRING_MAX) keyString = keyString.substring(1);
				var char:String = String.fromCharCode(code);
				if (e.shiftKey || Keyboard.capsLock) char = char.toLocaleUpperCase();
				else char = char.toLocaleLowerCase();
				keyString += char;
			}
			
			// update the keystate
			if (!_key[code])
			{
				_key[code] = true;
				_keyNum ++;
				_press[_pressNum ++] = code;
			}
		}
		
		/** @private Event handler for key release. */
		private static function onKeyUp(e:KeyboardEvent):void
		{
			// get the keycode and update the keystate
			var code:int = e.keyCode;
			if (_key[code])
			{
				_key[code] = false;
				_keyNum --;
				_release[_releaseNum ++] = code;
			}
		}
		
		/** @private Event handler for mouse press. */
		private static function onMouseDown(e:MouseEvent):void
		{
			if (!mouseDown)
			{
				mouseDown = true;
				mouseUp = false;
				mousePressed = true;
			}
		}
		
		/** @private Event handler for mouse release. */
		private static function onMouseUp(e:MouseEvent):void
		{
			mouseDown = false;
			mouseUp = true;
			mouseReleased = true;
		}
		
		/** @private Event handler for mouse wheel events */
		private static function onMouseWheel(e:MouseEvent):void
		{
		    mouseWheel = true;
		    _mouseWheelDelta = e.delta;
		}
		
		// Max amount of characters stored by the keystring.
		/** @private */ private static const KEYSTRING_MAX:uint = 100;
		
		// Input information.
		/** @private */ private static var _enabled:Boolean = false;
		/** @private */ private static var _key:Vector.<Boolean> = new Vector.<Boolean>(256);
		/** @private */ private static var _keyNum:int = 0;
		/** @private */ private static var _press:Vector.<int> = new Vector.<int>(256);
		/** @private */ private static var _release:Vector.<int> = new Vector.<int>(256);
		/** @private */ private static var _pressNum:int = 0;
		/** @private */ private static var _releaseNum:int = 0;
		/** @private */ private static var _control:Object = {};
		/** @private */ private static var _mouseWheelDelta:int = 0;
	}
}