/*
 * 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 java.awt.geom.Point2D;
import static kinugasa.inputDevice.GamePadConnection.*;

/**
 * ゲームパッドの入力状態を格納します.
 * <br>
 *
 * <br>
 *
 * @version 1.0.0 - 2013/01/14_13:18:37<br>
 * @author Dra0211<br>
 */
public class GamePadState extends InputDeviceState {

	private static final long serialVersionUID = -3599701264472336389L;

	/**
	 * 状態を作成します。パッケージプライベートです。
	 *
	 * @param values 整形済みのデータ。デジタルボタンはNATIVE_FALSEまたはそれ以外の値、
	 * トリガーは0fから1fまでのfloat、スティックは-1fから1fまでのfloatとする。データの並びと要素数は
	 * GamePadConnectionの定数に従う。<br>
	 *
	 */
	GamePadState(float[] values) {
		buttons = new Buttons(values);
		leftTrigger = new Trigger(values[TRIGGER_LEFT]);
		rightTrigger = new Trigger(values[TRIGGER_RIGHT]);
		leftStick = new ThumbStick(values[THUMB_STICK_LEFT_X], values[THUMB_STICK_LEFT_Y]);
		rightStick = new ThumbStick(values[THUMB_STICK_RIGHT_X], values[THUMB_STICK_RIGHT_Y]);
		connection = values[CONNECTION] != NATIVE_FALSE;
	}
	/** ゲームパッドの接続状態です. */
	private boolean connection;
	/** ゲームパッドのデジタルボタンです. */
	public Buttons buttons;
	/** ゲームパッドの左スティックです. */
	public ThumbStick leftStick;
	/** ゲームパッドの右スティックです. */
	public ThumbStick rightStick;
	/** ゲームパッドの左トリガーです. */
	public Trigger leftTrigger;
	/** ゲームパッドの右トリガーです. */
	public Trigger rightTrigger;

	/**
	 * このゲームパッドが接続されているかを調べます.
	 *
	 * @return 接続されている場合はtrueを返し、そうでない場合はfalseを返します。<br>
	 */
	public boolean isConnected() {
		return connection;
	}

	@Override
	public boolean isAnyInput() {
		return buttons.isAnyInput()
				|| leftStick.isAnyInput()
				|| rightStick.isAnyInput()
				|| leftTrigger.isAnyInput()
				|| rightTrigger.isAnyInput();
	}

	@Override
	public boolean isAnyButtonInput() {
		return buttons.isAnyButtonInput()
				|| leftStick.isAnyButtonInput()
				|| rightStick.isAnyButtonInput()
				|| leftTrigger.isAnyButtonInput()
				|| rightTrigger.isAnyButtonInput();
	}

	@Override
	public boolean isEmptyInput() {
		return buttons.isEmptyInput()
				&& leftStick.isEmptyInput()
				&& rightStick.isEmptyInput()
				&& leftTrigger.isEmptyInput()
				&& rightTrigger.isEmptyInput();
	}

	/**
	 * ゲームパッドのボタンの状態を保持する構造体です.
	 * <br>
	 *
	 * @author dra0211 : <a href="mailto:d0211@live.jp">d0211@live.jp</a> <br>
	 * @version 1.0.0 - 2012/10/06_8:32:26<br>
	 * <br>
	 */
	public final class Buttons extends InputDeviceState {

		private static final long serialVersionUID = 1L;
		/** ゲームパッドのAボタンです. */
		public final boolean A;
		/** ゲームパッドのBボタンです. */
		public final boolean B;
		/** ゲームパッドのXボタンです. */
		public final boolean X;
		/** ゲームパッドのYボタンです. */
		public final boolean Y;
		/** ゲームパッドのLBボタンです. */
		public final boolean LEFT_SHOULDER;
		/** ゲームパッドのRBボタンです. */
		public final boolean RIGHT_SHOULDER;
		/** ゲームパッドの左スティックボタンです. */
		public final boolean LEFT_STICK;
		/** ゲームパッドの右スティックボタンです. */
		public final boolean RIGHT_STICK;
		/** ゲームパッドのパッド上ボタンです. */
		public final boolean PAD_UP;
		/** ゲームパッドのパッド下ボタンです. */
		public final boolean PAD_DOWN;
		/** ゲームパッドのパッド左ボタンです. */
		public final boolean PAD_LEFT;
		/** ゲームパッドのパッド右ボタンです. */
		public final boolean PAD_RIGHT;
		/** ゲームパッドのスタートボタンです. */
		public final boolean START;
		/** ゲームパッドのバックボタンです. */
		public final boolean BACK;

		/**
		 * ゲームパッドの状態から、ボタンの状態を検査して論理型に変換します.プライベートアクセスです.
		 *
		 * @param data ゲームパッドの状態を格納している配列.<br>
		 */
		private Buttons(float[] data) {
			A = data[BUTTON_A] != NATIVE_FALSE;
			B = data[BUTTON_B] != NATIVE_FALSE;
			X = data[BUTTON_X] != NATIVE_FALSE;
			Y = data[BUTTON_Y] != NATIVE_FALSE;
			LEFT_SHOULDER = data[BUTTON_LB] != NATIVE_FALSE;
			RIGHT_SHOULDER = data[BUTTON_RB] != NATIVE_FALSE;
			LEFT_STICK = data[BUTTON_LEFT_STICK] != NATIVE_FALSE;
			RIGHT_STICK = data[BUTTON_RIGHT_STICK] != NATIVE_FALSE;
			PAD_UP = data[BUTTON_PAD_UP] != NATIVE_FALSE;
			PAD_DOWN = data[BUTTON_PAD_DOWN] != NATIVE_FALSE;
			PAD_LEFT = data[BUTTON_PAD_LEFT] != NATIVE_FALSE;
			PAD_RIGHT = data[BUTTON_PAD_RIGHT] != NATIVE_FALSE;
			START = data[BUTTON_START] != NATIVE_FALSE;
			BACK = data[BUTTON_BACK] != NATIVE_FALSE;
		}

		/**
		 * 指定されたボタンの状態を返します.
		 *
		 * @param b 検査するボタン.<br>
		 *
		 * @return 指定したボタンが押されている場合はtrue、押されていない場合はfalseを返す.<br>
		 */
		public boolean isPressed(GamePadButtons b) {
			return isPressed(b.getValue());
		}

		/**
		 * 指定されたインデックスを持つフィールドのボタンの状態を検査します.
		 * このメソッドは通常、isPressed(GamePadButtons)と同等か、それよりも高速に動作します.<br>
		 * ただし、不正なインデックスが送信されてもfalseを戻し、処理を継続することに注意してください.<br>
		 *
		 * @param field GamePadConnectionの定数を使用してフィールド番号を送ります.<br>
		 *
		 * @return 指定したフィールドのボタンが押されている場合はtrue、押されていない場合はfalseを返す.
		 * 不正なインデックスであった場合もfalseを返す.<br>
		 */
		public boolean isPressed(int field) {
			switch (field) {
				case BUTTON_A:
					return A;
				case BUTTON_B:
					return B;
				case BUTTON_X:
					return X;
				case BUTTON_Y:
					return Y;
				case BUTTON_LB:
					return LEFT_SHOULDER;
				case BUTTON_RB:
					return RIGHT_SHOULDER;
				case BUTTON_LEFT_STICK:
					return LEFT_STICK;
				case BUTTON_RIGHT_STICK:
					return RIGHT_STICK;
				case BUTTON_PAD_UP:
					return PAD_UP;
				case BUTTON_PAD_DOWN:
					return PAD_DOWN;
				case BUTTON_PAD_LEFT:
					return PAD_LEFT;
				case BUTTON_PAD_RIGHT:
					return PAD_RIGHT;
				case BUTTON_START:
					return START;
				case BUTTON_BACK:
					return BACK;
				default:
					return false;
			}//sw
		}

		@Override
		public String toString() {
			return "A=" + A + ", B=" + B + ", X=" + X + ", Y=" + Y + ", LEFT_SHOULDER=" + LEFT_SHOULDER + ", RIGHT_SHOULDER=" + RIGHT_SHOULDER + ", LEFT_STICK=" + LEFT_STICK + ", RIGHT_STICK=" + RIGHT_STICK + ", PAD_UP=" + PAD_UP + ", PAD_DOWN=" + PAD_DOWN + ", PAD_LEFT=" + PAD_LEFT + ", PAD_RIGHT=" + PAD_RIGHT + ", START=" + START + ", BACK=" + BACK;
		}

		@Override
		public GamePadState.Buttons clone() {
			return (Buttons) super.clone();
		}

		@Override
		public boolean isAnyInput() {
			return A
					|| B
					|| BACK
					|| LEFT_SHOULDER
					|| LEFT_STICK
					|| PAD_DOWN
					|| PAD_LEFT
					|| PAD_RIGHT
					|| PAD_UP
					|| RIGHT_SHOULDER
					|| RIGHT_STICK
					|| START
					|| X
					|| Y;
		}

		@Override
		public boolean isAnyButtonInput() {
			return A
					|| B
					|| BACK
					|| LEFT_SHOULDER
					|| LEFT_STICK
					|| PAD_DOWN
					|| PAD_LEFT
					|| PAD_RIGHT
					|| PAD_UP
					|| RIGHT_SHOULDER
					|| RIGHT_STICK
					|| START
					|| X
					|| Y;
		}

		@Override
		public boolean isEmptyInput() {
			return A
					&& !B
					&& !BACK
					&& !LEFT_SHOULDER
					&& !LEFT_STICK
					&& !PAD_DOWN
					&& !PAD_LEFT
					&& !PAD_RIGHT
					&& !PAD_UP
					&& !RIGHT_SHOULDER
					&& !RIGHT_STICK
					&& !START
					&& !X
					&& !Y;
		}
	}

	/**
	 * ゲームパッドのトリガーの状態を保持する構造体です.
	 * <br>
	 *
	 * @author dra0211 : <a href="mailto:d0211@live.jp">d0211@live.jp</a> <br>
	 * @version 1.0.0 - 2012/10/06_8:32:26<br>
	 * <br>
	 */
	public final class Trigger extends InputDeviceState {

		private static final long serialVersionUID = 1L;
		/** トリガーの値. */
		public final float VALUE;

		/**
		 * トリガーの値を格納します.
		 *
		 * @param value トリガーの値.<br>
		 */
		private Trigger(float value) {
			this.VALUE = value < triggerDeadZone ? 0f : value;
		}

		@Override
		public String toString() {
			return Float.toString(VALUE);
		}

		@Override
		public GamePadState.Trigger clone() {
			return (Trigger) super.clone();
		}

		@Override
		public boolean isAnyInput() {
			return VALUE != 0.f;
		}

		@Override
		public boolean isAnyButtonInput() {
			return VALUE != 0.f;
		}

		@Override
		public boolean isEmptyInput() {
			return VALUE == 0.f;
		}
	}

	/**
	 * ゲームパッドのスティックの状態を保持する構造体です.
	 * <br>
	 *
	 * @author dra0211 : <a href="mailto:d0211@live.jp">d0211@live.jp</a> <br>
	 * @version 1.0.0 - 2012/10/06_8:32:26<br>
	 * <br>
	 */
	public final class ThumbStick extends InputDeviceState {

		private static final long serialVersionUID = 1L;
		/** スティックの状態を格納するPoint. */
		private Point2D.Float value;

		/**
		 * スティックの状態を格納します.
		 *
		 * @param x X軸の値.<br>
		 * @param y Y軸の値.<br>
		 */
		private ThumbStick(float x, float y) {
			this.value = new Point2D.Float(
					Math.abs(x) < thumbStickDeadZone ? 0f : x,
					Math.abs(y) < thumbStickDeadZone ? 0f : y);
		}

		/**
		 * このスティックの値をPoint2D.Floatとして取得します.
		 *
		 * @return このスティックの値のクローンが返される.<br>
		 */
		public Point2D.Float getValue() {
			return (Point2D.Float) value.clone();
		}

		/**
		 * このスティックのX軸の値を取得します.
		 *
		 * @return このスティックのX軸の値.-1から1のfloat型として取得できる.<br>
		 */
		public float getX() {
			return value.x;
		}

		/**
		 * このスティックのY軸の値を取得します.
		 *
		 * @return このスティックのY軸の値.-1から1のfloat型として取得できる.<br>
		 */
		public float getY() {
			return value.y;
		}

		@Override
		public String toString() {
			return "x=" + value.x + ", y=" + value.y;
		}

		@Override
		public GamePadState.ThumbStick clone() {
			ThumbStick result = (ThumbStick) super.clone();
			result.value = (Point2D.Float) this.value.clone();
			return result;
		}

		@Override
		public boolean isAnyInput() {
			return getX() != 0.f || getY() != 0.f;
		}

		@Override
		public boolean isAnyButtonInput() {
			return getX() != 0.f || getY() != 0.f;
		}

		@Override
		public boolean isEmptyInput() {
			return getX() == 0.f && getY() == 0.f;
		}
	}
	/** サムスティックのデッドゾーンです.入力値がこの値に満たない場合は入力は0として扱われます.
	 * デフォルトでは0.189くらいに設定されています.<br>
	 * この値は左右両方のスティックに適用されます.<br> */
	private static float thumbStickDeadZone = 0.18921477f;//=6200 / Short.MAX_VALUE

	/**
	 * スティックのデッドゾーンを取得します.
	 *
	 * @return スティックのデッドゾーン.左右共通で、この値に満たない入力値は0として扱われる.<br>
	 */
	public static float getThumbStickDeadZone() {
		return thumbStickDeadZone;
	}

	/**
	 * スティックのデッドゾーンを設定します.
	 *
	 * @param thumbStickDeadZone スティックのデッドゾーン.左右共通で、この値に満たない入力値は0として扱われる.<br>
	 */
	public static void setThumbStickDeadZone(float thumbStickDeadZone) {
		GamePadState.thumbStickDeadZone = thumbStickDeadZone;
	}
	/** トリガーのデッドゾーンです.入力値がこの値に満たない場合は入力は0として扱われます.
	 * デフォルトでは0に設定されています.<br>
	 * この値は左右両方のトリガーに適用されます.<br> */
	private static float triggerDeadZone = 0f;

	/**
	 * トリガーのデッドゾーンを設定します.
	 *
	 * @param triggerDeadZone トリガーのデッドゾーン.左右共通で、この値に満たない入力値は0として扱われる.<br>
	 */
	public static void setTriggerDeadZone(float triggerDeadZone) {
		GamePadState.triggerDeadZone = triggerDeadZone;
	}

	/**
	 * トリガーのデッドゾーンを取得します.
	 *
	 * @return トリガーのデッドゾーン.左右共通で、この値に満たない入力値は0として扱われる.<br>
	 */
	public static float getTriggerDeadZone() {
		return triggerDeadZone;
	}

	@Override
	public String toString() {
		return "GamePadState{" + "connection:{" + connection + "}, buttons:{"
				+ buttons + "}, leftStick:{" + leftStick + "}, rightStick:{"
				+ rightStick + "}, leftTrigger:{" + leftTrigger + "}, rightTrigger:{"
				+ rightTrigger + "}}";
	}

	@Override
	public GamePadState clone() {
		GamePadState result = (GamePadState) super.clone();
		result.buttons = this.buttons.clone();
		result.leftTrigger = this.leftTrigger.clone();
		result.rightTrigger = this.rightTrigger.clone();
		result.leftStick = this.leftStick.clone();
		result.rightStick = this.rightStick.clone();
		return result;
	}
}
