/*
 * 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.game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import kinugasa.contents.graphics.ImageUtil;
import kinugasa.contents.resource.ContentsFileNotFoundException;
import kinugasa.contents.resource.ContentsIOException;
import kinugasa.contents.graphics.RenderingPolicy;
import kinugasa.util.annotations.Internal;

/**
 * ゲームの初期設定を指定するためのビルダです.
 * <br>
 * このクラスはゲームの起動時に様々な設定を行うために使用します。<br>
 * ゲーム起動時の設定が非常に複雑な場合には、このクラスを継承することで
 * 適切な値を返す専用クラスを作成できます。<br>
 * <br>
 * <br>
 * 設定可能なオプションは以下の通りです。<br>
 * <table border="1">
 * <tr>
 * <th>オプション</th>
 * <th>説明</th>
 * <th>デフォルト値</th>
 * <th>nullの許可</th>
 * <th>ゲーム開始後の設定変更</th>
 * </tr>
 *
 * <tr>
 * <td>title</td>
 * <td>ゲームウインドウのタイトルおよびゲームの名前</td>
 * <td>"My Game"</td>
 * <td>yes</td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>location</td>
 * <td>ゲームウインドウが出現する位置<br>スクリーン左上からのピクセル単位の距離</td>
 * <td>x=0, y=0</td>
 * <td></td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>size</td>
 * <td>ゲームウインドウのサイズ</td>
 * <td>width=600, height=400</td>
 * <td></td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>backColor</td>
 * <td>ゲームウインドウの描画可能領域の背景色</td>
 * <td>R=68, G=98, B=162(#4462A2)</td>
 * <td></td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>icon</td>
 * <td>ゲームウインドウのアイコン</td>
 * <td>null</td>
 * <td>yes</td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>fps</td>
 * <td>動作FPS</td>
 * <td>60</td>
 * <td></td>
 * <td>kinugasa.game.GameTimeManager</td>
 * </tr>
 *
 * <tr>
 * <td>renderingHints</td>
 * <td>レンダリングの品質</td>
 * <td>kinugasa.contents.graphics.RenderingPolicy.DEFAULT</td>
 * <td></td>
 * <td>変更不能</td>
 * </tr>
 *
 * <tr>
 * <td>useMouseCursor</td>
 * <td>ゲームウインドウ上でマウスカーソルを表示する</td>
 * <td>false</td>
 * <td></td>
 * <td>変更不能</td>
 * </tr>
 *
 * <tr>
 * <td>useGamePad</td>
 * <td>DLLをロードし、ゲームパッドを使用可能にする</td>
 * <td>true</td>
 * <td></td>
 * <td>変更不能</td>
 * </tr>
 *
 * <tr>
 * <td>useKeyboard</td>
 * <td>キーリスナをゲームウインドウに登録し、キー状態を検査可能にする</td>
 * <td>true</td>
 * <td></td>
 * <td>変更不能</td>
 * </tr>
 *
 * <tr>
 * <td>closeEvetList</td>
 * <td>ゲームウインドウを閉じる際のイベント</td>
 * <td>空のリスト</td>
 * <td></td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * </table>
 *
 * @version 1.0.0 - 2013/01/13_20:55:27<br>
 * @author Dra0211<br>
 */
public class GameConfig {

	/** ゲームウインドウのタイトルバーの文字列です. */
	private String title;
	/** ゲームウインドウが表示される位置です. */
	private Point location = new Point(0, 0);
	/** ゲームウインドウのサイズです. */
	private Dimension size = new Dimension(600, 480);
	/** ゲームウインドウの背景色です. */
	private Color backColor = new Color(68, 98, 162);
	/** ゲームウインドウのアイコンです. */
	private Image icon;
	/** FPSです. */
	private int fps = 60;
	/** レンダリングの品質です. */
	private RenderingHints renderingHints = RenderingPolicy.DEFAULT.getRenderingHints();
	/** マウスカーソルをゲームウインドウ上に表示するかを決定します. */
	private boolean useMouseCursor = false;
	/** ゲームパッドのDLLロードを行うかを決定します. */
	private boolean useGamePad = true;
	/** キー入力を検査するかを決定します. */
	private boolean useKeyboard = true;
	/** ウインドウを閉じた際のイベントです. */
	private List<WindowCloseEvent> closeEvetList = new ArrayList<WindowCloseEvent>(3);

	/**
	 * 適当なタイトルでゲーム設定を作成します.
	 */
	public GameConfig() {
		this("My Game");
	}

	/**
	 * ゲーム設定を作成します.
	 * @param title タイトルバーに表示する文字列を指定します。<br>
	 */
	public GameConfig(String title) {
		this.title = title;
		icon = new ImageIcon(getClass().getResource("icon.png")).getImage();
	}

	/**
	 * ゲームウインドウのタイトルバーの文字列を設定します.
	 * @param title タイトルバーに表示する文字列を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setTitle(String title) {
		this.title = title;
		return this;
	}

	/**
	 * ウインドウを閉じる際のイベントを追加します.
	 * @param event 新しいイベントを追加します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig addCloseEvent(WindowCloseEvent event) {
		closeEvetList.add(event);
		return this;
	}

	/**
	 * ゲームウインドウが出現する位置を指定します.
	 * @param location 位置を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setLocation(Point location) {
		this.location = location;
		return this;
	}

	/**
	 * ゲームウインドウが出現する位置を指定します.
	 * @param x X方向の位置を指定します。<br>
	 * @param y Y方向の位置を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setLocation(int x, int y) {
		this.location.x = x;
		this.location.y = y;
		return this;
	}

	/**
	 * ゲームウインドウが出現する位置を指定します.
	 * @param x X方向の位置を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setX(int x) {
		this.location.x = x;
		return this;
	}

	/**
	 * ゲームウインドウが出現する位置を指定します.
	 * @param y Y方向の位置を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setY(int y) {
		this.location.y = y;
		return this;
	}

	/**
	 * 現在のウインドウサイズを使用して、ウインドウがスクリーンの中央に出現するように
	 * 位置を調節します.
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setCenterOfScreen() {
		Point centerOfScreen = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
		this.location.x = centerOfScreen.x - size.width / 2;
		this.location.y = centerOfScreen.y - size.height / 2;
		if (location.x < 0) {
			location.x = 0;
		}
		if (location.y < 0) {
			location.y = 0;
		}
		return this;
	}

	/**
	 * ウインドウのサイズを変更します.
	 * このメソッドでの”サイズ”とは、描画可能な領域のサイズです。
	 * したがって、ウインドウ自体のサイズは、指定されたサイズよりも若干大きくなります。<br>
	 * @param size サイズを指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setSize(Dimension size) {
		this.size = size;
		return this;
	}

	/**
	 * ウインドウのサイズを変更します.
	 * このメソッドでの”サイズ”とは、描画可能な領域のサイズです。
	 * したがって、ウインドウ自体のサイズは、指定されたサイズよりも若干大きくなります。<br>
	 * @param width 幅を指定します。<br>
	 * @param height 高さを指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setSize(int width, int height) {
		this.size.width = width;
		this.size.height = height;
		return this;
	}

	/**
	 * ウインドウのサイズを変更します.
	 * このメソッドでの”サイズ”とは、描画可能な領域のサイズです。
	 * したがって、ウインドウ自体のサイズは、指定されたサイズよりも若干大きくなります。<br>
	 * @param width 幅を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setWidth(int width) {
		this.size.width = width;
		return this;
	}

	/**
	 * ウインドウのサイズを変更します.
	 * このメソッドでの”サイズ”とは、描画可能な領域のサイズです。
	 * したがって、ウインドウ自体のサイズは、指定されたサイズよりも若干大きくなります。<br>
	 * @param height 高さを指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setHeight(int height) {
		this.size.height = height;
		return this;
	}

	/**
	 * ウインドウの描画可能領域の背景色を指定します.
	 * @param backColor 背景色を指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setBackColor(Color backColor) {
		this.backColor = backColor;
		return this;
	}

	/**
	 * ゲームウインドウのアイコンを指定します.
	 * @param icon ウインドウのアイコンを指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setIcon(Image icon) {
		this.icon = icon;
		return this;
	}

	/**
	 * ゲームウインドウのアイコンを指定します.
	 * @param filePath ウインドウのアイコンのファイルパスを指定します。<br>
	 * @return thisインスタンスを返します。<br>
	 * @throws ContentsFileNotFoundException 画像ファイルが存在しないときに投げられます。<br>
	 * @throws ContentsIOException 画像がロードできない場合に投げられます。<br>
	 */
	public GameConfig setIcon(String filePath)
			throws ContentsFileNotFoundException, ContentsIOException {
		if (filePath == null) {
			setIcon((Image) null);
		} else {
			setIcon(ImageUtil.load(filePath));
		}
		return this;
	}

	/**
	 * FPSの最大値を設定します.
	 * @param fps FPSを指定します。通常は30?60です。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setFps(int fps) {
		this.fps = fps;
		return this;
	}

	/**
	 * 描画品質を設定します.
	 * 描画の品質を高く設定すると、画像やプリミティブ図名がきれいに表示できますが、
	 * 描画速度が下がります。<br>
	 * ゲームデザインを考慮し、品質と速度のバランスをとった設定を決める必要があります。
	 * @param renderingHints レンダリングの設定を送信します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setRenderingHints(RenderingHints renderingHints) {
		this.renderingHints = renderingHints;
		return this;
	}

	/**
	 * 描画品質を設定します.
	 * 描画の品質を高く設定すると、画像やプリミティブ図名がきれいに表示できますが、
	 * 描画速度が下がります。<br>
	 * ゲームデザインを考慮し、品質と速度のバランスをとった設定を決める必要があります。
	 * @param policy レンダリングの設定を送信します。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setRenderingHints(RenderingPolicy policy) {
		this.renderingHints = policy.getRenderingHints();
		return this;
	}

	/**
	 * ゲームウインドウ上でマウスカーソルを使用するかを決定します.
	 * @param use trueを指定すると有効化されます。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig useMouse(boolean use) {
		this.useMouseCursor = use;
		return this;
	}

	/**
	 * DLLをロードし、ゲームパッドを使用可能にするかを決定します.
	 * @param use trueを指定すると有効化されます。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig useGamePad(boolean use) {
		this.useGamePad = use;
		return this;
	}

	/**
	 * キーリスナをゲームウインドウに登録し、キー状態を検査可能にするかを決定します.
	 * @param use trueを指定すると有効化されます。<br>
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig useKeyboard(boolean use) {
		this.useKeyboard = use;
		return this;
	}

	/**
	 * ゲームウインドウの背景色を取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return 背景色を返します。<br>
	 */
	@Internal
	protected Color getBackColor() {
		return backColor;
	}

	/**
	 * FPSの最大値を取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return FPSの最大値を返します。<br>
	 */
	@Internal
	protected int getFps() {
		return fps;
	}

	/**
	 * ゲームウインドウのアイコンを取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return アイコンを返します。<br>
	 */
	@Internal
	protected Image getIcon() {
		return icon;
	}

	/**
	 * ゲームウインドウの出現位置を取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return ゲームウインドウの出現位置を返します。<br>
	 */
	@Internal
	protected Point getLocation() {
		return location;
	}

	/**
	 * 描画品質を取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return 描画品質の設定を返します。<br>
	 */
	@Internal
	protected RenderingHints getRenderingHints() {
		return renderingHints;
	}

	/**
	 * ゲームウインドウのサイズを取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return ゲームウインドウのサイズを返します。<br>
	 */
	@Internal
	protected Dimension getSize() {
		return size;
	}

	/**
	 * ゲームウインドウのタイトルを取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return ゲームウインドウのタイトルとゲームクラスの名前となる
	 * 文字列を返します。<br>
	 */
	@Internal
	protected String getTitle() {
		return title;
	}

	/**
	 * マウスカーソルを使用するかを決定します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return マウスカーソルを使用する場合はtrueを返します。<br>
	 */
	@Internal
	protected boolean isUseMouseCursor() {
		return useMouseCursor;
	}

	/**
	 * ゲームパッドを使用するかを決定します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return ゲームパッドを使用する場合はtrueを返します。<br>
	 */
	@Internal
	protected boolean isUseGamePad() {
		return useGamePad;
	}

	/**
	 * キーボードを使用するかを決定します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return キーボードを使用する場合はtrueを返します。<br>
	 */
	@Internal
	protected boolean isUseKeyboard() {
		return useKeyboard;
	}

	/**
	 * 追加されたウインドウクローズイベントを取得します.
	 * このメソッドはKinugasaGameクラスから使用されます。<br>
	 * @return このゲーム設定に追加されているウインドウクローズイベントを追加された順の
	 * リストとして取得します。<br>
	 */
	@Internal
	protected List<WindowCloseEvent> getCloseEvetList() {
		return closeEvetList;
	}

	@Override
	public String toString() {
		return "GameConfig{" + "title=" + title + ", location=" + location + ", size=" + size
				+ ", backColor=" + backColor + ", icon=" + icon + ", fps=" + fps + ", useMouseCursor="
				+ useMouseCursor + ", renderingHints=" + renderingHints + '}';
	}
}
