/*
 * The MIT License
 *
 * Copyright 2013 Dra0211.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package kinugasa.inputDevice;

import kinugasa.game.PlayerConstants;
import kinugasa.object.FourDirection;

/**
 * 入力デバイスの状態を格納します.
 * <br>
 * このクラスは、プレイヤーが使用できるデバイスの状態を一元管理します。<br>
 * このクラスを使用することで、入力状態を簡単に検査できます。<br>
 * <br>
 * 入力状態の更新には、それなりの時間的コストがかかります。<br>
 * getInstanceの使用は、1ループ中1回にとどめてください。<br>
 * <br>
 * <br>
 * ゲーム起動時の設定によって、使用しないデバイスがある場合、
 * そのデバイスの状態は常にnullです。<br>
 * <br>
 * @version 1.0.0 - 2013/01/14_14:55:04<br>
 * @author Dra0211<br>
 */
public final class InputState extends InputDeviceState {

	private static final long serialVersionUID = 2349722781837661059L;
	/** 現在のゲームパッドの状態. */
	private GamePadState gamePadState = null;
	/** 前回のゲームパッドの状態. */
	private GamePadState prevGamePadState = null;
	/** 現在のキーボードの状態. */
	private KeyState keyState = null;
	/** 前回のキーボードの状態. */
	private KeyState prevKeyState = null;
	/** 現在のマウスの状態. */
	private MouseState mouseState = null;
	/** 前回のマウスの状態. */
	private MouseState prevMouseState = null;
	//
	/** 唯一のインスタンスです. */
	private static final InputState INSTANCE = new InputState();

	/**
	 * シングルトンクラスです.
	 * getInstanceを使用してください。<br>
	 */
	private InputState() {
		if (PlayerConstants.getInstance().isUsingGamepad()) {
			gamePadState = GamePadConnection.createClearState();
		}
		if (PlayerConstants.getInstance().isUsingKeyboard()) {
			keyState = KeyConnection.createClearState();
		}
		if (PlayerConstants.getInstance().isUsingMouse()) {
			mouseState = MouseConnection.createClearState();
		}
	}

	/**
	 * インスタンスを取得します.
	 * このメソッドを呼び出すと、現在の入力状態が検査されます。<br>
	 * @return InputStateのインスタンスを返します。<br>
	 */
	public static InputState getInstance() {
		INSTANCE.refresh();
		return INSTANCE;
	}

	/**
	 * 入力状態を更新します.
	 */
	private void refresh() {
		if (PlayerConstants.getInstance().isUsingGamepad()) {
			prevGamePadState = gamePadState;
			gamePadState = GamePadConnection.getState(0);
		}
		if (PlayerConstants.getInstance().isUsingMouse()) {
			prevMouseState = mouseState;
			mouseState = MouseConnection.getState();
		}
		if (PlayerConstants.getInstance().isUsingKeyboard()) {
			prevKeyState = keyState;
			keyState = KeyConnection.getState();
		}
	}

	@Override
	public boolean isAnyInput() {
		boolean result = false;
		if (PlayerConstants.getInstance().isUsingGamepad()) {
			result |= gamePadState.isAnyInput();
		}
		if (PlayerConstants.getInstance().isUsingMouse()) {
			result |= mouseState.isAnyInput();
		}
		return result | keyState.isAnyInput();
	}

	@Override
	public boolean isAnyButtonInput() {
		boolean result = false;
		if (PlayerConstants.getInstance().isUsingGamepad()) {
			result |= gamePadState.isAnyButtonInput();
		}
		if (PlayerConstants.getInstance().isUsingMouse()) {
			result |= mouseState.isAnyButtonInput();
		}
		return result | keyState.isAnyButtonInput();
	}

	@Override
	public boolean isEmptyInput() {
		boolean result = true;
		if (PlayerConstants.getInstance().isUsingGamepad()) {
			result &= gamePadState.isEmptyInput();
		}
		if (PlayerConstants.getInstance().isUsingMouse()) {
			result &= mouseState.isEmptyInput();
		}
		return result & keyState.isEmptyInput();
	}

	/**
	 * キーボードのキーが押されているかを検査します.
	 * @param key 判定するキーを指定します。<br>
	 * @param type 前回の入力状態を検査するかを決定します。<br>
	 * @return 指定されたキーが現在押されている場合、trueを返します。<br>
	 */
	public boolean isPressed(Keys key, InputType type) {
		return type == InputType.CONTINUE
				? keyState.isPressed(key)
				: keyState.isPressed(key) && !prevKeyState.isPressed(key);
	}

	/**
	 * ゲームパッドのボタンが押されているかを検査します.
	 * @param button 判定するボタンを指定します。<br>
	 * @param type 前回の入力状態を検査するかを決定します。<br>
	 * @return 指定されたボタンが現在押されている場合、trueを返します。<br>
	 */
	public boolean isPressed(GamePadButtons button, InputType type) {
		return type == InputType.CONTINUE
				? gamePadState.buttons.isPressed(button)
				: gamePadState.buttons.isPressed(button) && !prevGamePadState.buttons.isPressed(button);
	}

	/**
	 * マウスのボタンが押されているかを検査します.
	 * @param button 判定するボタンを指定します。<br>
	 * @param type 前回の入力状態を検査するかを決定します。<br>
	 * @return 指定されたボタンが現在押されている場合、trueを返します。<br>
	 */
	public boolean isPressed(MouseButtons button, InputType type) {
		if (!PlayerConstants.getInstance().isUsingMouse()) {
			return false;
		}
		return type == InputType.CONTINUE
				? mouseState.isPressed(button)
				: mouseState.isPressed(button) && !prevMouseState.isPressed(button);
	}

	/**
	 * ゲームパッドの左スティックが、指定された方向に倒されているかを検査します.
	 * @param absBorderLine 判定の基準となる割合です。通常は0fから1.0fを送信します。<br>
	 * @param dir 判定する方向です。<br>
	 * @param type 前回の入力状態を検査するかを決定します。<br>
	 * @return 指定された方向に、absBorderLine*100%以上倒されている場合はtrueを返します。<br>
	 */
	public boolean leftStickOperation(float absBorderLine, FourDirection dir, InputType type) {
		switch (dir) {
			case EAST: {
				switch (type) {
					case SINGLE: {
						return gamePadState.leftStick.getX() < -Math.abs(absBorderLine)
								&& prevGamePadState.leftStick.getX() >= -Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.leftStick.getX() < -Math.abs(absBorderLine)
								&& gamePadState.leftStick.getX() < 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}
			}
			case WEST: {
				switch (type) {
					case SINGLE: {
						return Math.abs(gamePadState.leftStick.getX()) > Math.abs(absBorderLine)
								&& prevGamePadState.leftStick.getX() <= Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.leftStick.getX() > Math.abs(absBorderLine)
								&& gamePadState.leftStick.getX() > 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}
			}
			case NORTH: {
				switch (type) {
					case SINGLE: {
						return gamePadState.leftStick.getY() > Math.abs(absBorderLine)
								&& prevGamePadState.leftStick.getY() <= Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.leftStick.getY() > Math.abs(absBorderLine)
								&& gamePadState.leftStick.getY() > 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}
			}
			case SOUTH: {
				switch (type) {
					case SINGLE: {
						return gamePadState.leftStick.getY() < -Math.abs(absBorderLine)
								&& prevGamePadState.leftStick.getY() >= -Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.leftStick.getY() < -Math.abs(absBorderLine)
								&& gamePadState.leftStick.getY() < 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}

			}
			default: {
				throw new AssertionError("unuse default : dir=[" + dir + "]");
			}
		}
	}

	/**
	 * ゲームパッドの右スティックが、指定された方向に倒されているかを検査します.
	 * @param absBorderLine 判定の基準となる割合です。通常は0fから1.0fを送信します。<br>
	 * @param dir 判定する方向です。<br>
	 * @param type 前回の入力状態を検査するかを決定します。<br>
	 * @return 指定された方向に、absBorderLine*100%以上倒されている場合はtrueを返します。<br>
	 */
	public boolean rightStickOperation(float absBorderLine, FourDirection dir, InputType type) {
		switch (dir) {
			case EAST: {
				switch (type) {
					case SINGLE: {
						return gamePadState.rightStick.getX() < -Math.abs(absBorderLine)
								&& prevGamePadState.rightStick.getX() >= -Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.rightStick.getX() < -Math.abs(absBorderLine)
								&& gamePadState.rightStick.getX() < 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}
			}
			case WEST: {
				switch (type) {
					case SINGLE: {
						return gamePadState.rightStick.getX() > Math.abs(absBorderLine)
								&& prevGamePadState.rightStick.getX() <= Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.rightStick.getX() > Math.abs(absBorderLine)
								&& gamePadState.rightStick.getX() > 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}
			}
			case NORTH: {
				switch (type) {
					case SINGLE: {
						return gamePadState.rightStick.getY() > Math.abs(absBorderLine)
								&& prevGamePadState.rightStick.getY() <= Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.rightStick.getY() > Math.abs(absBorderLine)
								&& gamePadState.rightStick.getY() > 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}
			}
			case SOUTH: {
				switch (type) {
					case SINGLE: {
						return gamePadState.rightStick.getY() < -Math.abs(absBorderLine)
								&& prevGamePadState.rightStick.getY() >= -Math.abs(absBorderLine);
					}
					case CONTINUE: {
						return gamePadState.rightStick.getY() < -Math.abs(absBorderLine)
								&& gamePadState.rightStick.getY() > 0;
					}
					default: {
						throw new AssertionError("unuse default : type=[" + type + "]");
					}
				}

			}
			default: {
				throw new AssertionError("! > InputState rightStickOperation : unuse default : dir=[" + dir + "]");
			}
		}
	}

	/**
	 * 現在のゲームパッドの状態を取得します.
	 * ゲームパッドが使用できない場合、nullを返します。<br>
	 * @return ゲームパッドの状態を返します。<br>
	 */
	public GamePadState getGamePadState() {
		return gamePadState;
	}

	/**
	 * 前回のゲームパッドの状態を取得します.
	 * ゲームパッドが使用できない場合、nullを返します。<br>
	 * @return ゲームパッドの状態を返します。<br>
	 */
	public GamePadState getPrevGamePadState() {
		return prevGamePadState;
	}

	/**
	 * 現在のキーボードの状態を取得します.
	 * キーボードが使用できない場合、nullを返します。<br>
	 * @return キーボードの状態を返します。<br>
	 */
	public KeyState getKeyState() {
		return keyState;
	}

	/**
	 * 前回のキーボードの状態を取得します.
	 * キーボードが使用できない場合、nullを返します。<br>
	 * @return キーボードの状態を返します。<br>
	 */
	public KeyState getPrevKeyState() {
		return prevKeyState;
	}

	/**
	 * 現在のマウスの状態を取得します.
	 * マウスが使用できない場合、nullを返します。<br>
	 * @return マウスの状態を返します。<br>
	 */
	public MouseState getPrevMouseState() {
		return prevMouseState;

	}

	/**
	 * 前回のマウスの状態を取得します.
	 * マウスが使用できない場合、nullを返します。<br>
	 * @return マウスの状態を返します。<br>
	 */
	public MouseState getMouseState() {
		return mouseState;
	}

	@Override
	public InputState clone() {
		InputState result = (InputState) super.clone();

		if (this.gamePadState != null) {
			result.gamePadState = this.gamePadState.clone();
		}
		if (this.prevGamePadState != null) {
			result.prevGamePadState = this.prevGamePadState.clone();
		}
		if (this.mouseState != null) {
			result.mouseState = this.mouseState.clone();
		}
		if (this.prevMouseState != null) {
			result.prevMouseState = this.prevMouseState.clone();
		}
		if (this.keyState != null) {
			result.keyState = this.keyState.clone();
		}
		if (this.prevKeyState != null) {
			result.prevKeyState = this.prevKeyState.clone();
		}
		return result;
	}

	@Override
	public String toString() {
		return "InputState{" + "gamePadState=" + gamePadState + ", keyState=" + keyState + ", mouseState=" + mouseState + '}';
	}
}
