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

/**
 * ゲームの初期設定を指定するためのビルダです.
 * <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>ゲームウインドウのタイトルおよびゲームの名前<br>（"ゲームの名前"の用途は特にありませんが、Gameクラスから取得できます）</td>
 * <td>"My Game"</td>
 * <td>yes</td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>logPath</td>
 * <td>ログファイルのパス（ロガーのグローバルログ）<br>※関連機能kinugasa.util.StdOutTracerも参照のこと。</td>
 * <td>null</td>
 * <td>yes<br>（nullの場合：ログを使用しない）</td>
 * <td>変更不能</td>
 * </tr>
 *
 * <tr>
 * <td>location</td>
 * <td>ゲームウインドウが出現する位置<br>スクリーン左上からのピクセル単位の距離です。<br>
 * setCenterOfScreenメソッドにより、画面中央に設定できます。<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=48, G=68, B=142(#30448E)</td>
 * <td></td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>icon</td>
 * <td>ゲームウインドウのアイコン</td>
 * <td>KGFのアイコン</td>
 * <td>yes<br>（nullの場合：javaデフォルトアイコン）</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>レンダリングの品質<br>詳細はRenderingPolicyを確認してください。テキストのアンチエイリアス設定などができます。</td>
 * <td>kinugasa.contents.graphics.RenderingPolicy.SPEED</td>
 * <td></td>
 * <td>変更不能</td>
 * </tr>
 *
 * <tr>
 * <td>useMouseCursor</td>
 * <td>ゲームウインドウ上でマウスカーソルを表示する<br>※注意：このオプションをtrueにしない限り、マウス入力値の取得ができません</td>
 * <td>false</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>ゲームウインドウを閉じる際のイベント<br>詳細はkinugasa.game.WindowCloseEventを確認してください。</td>
 * <td>空のリスト</td>
 * <td></td>
 * <td>kinugasa.game.GameWindow</td>
 * </tr>
 *
 * <tr>
 * <td>lock</td>
 * <td>ゲームの多重起動を許可しない</td>
 * <td>true</td>
 * <td></td>
 * <td>変更不可</td>
 * </tr>
 *
 * <tr>
 * <td>updateIfNotActive</td>
 * <td>ゲームのメインウインドウがアクティブでない場合、updateもrepaintも実行しないようにする</td>
 * <td>true</td>
 * <td></td>
 * <td>変更不可</td>
 * </tr>
 *
 * </table>
 * <br>
 * ゲーム起動後に変更できない項目をゲーム中のオプションで変更する場合には、ゲームの再起動をして反映してください。<br>
 *
 *
 * @version 1.0.0 - 2013/01/13_20:55:27<br>
 * @version 1.1.0 - 2015/01/03_07:19<br>
 * @author Dra0211<br>
 */
public class GameConfig {

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

	/**
	 * ゲームの多重起動を防止するためのフラグです.
	 */
	private boolean lock = true;
	private boolean updateIfNotActive = true;

	/**
	 * 適当なタイトルでゲーム設定を作成します.
	 */
	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;
	}

	/**
	 * ログファイルパスを設定します. 設定された値はロガーのグローバルパスとして使用され、フレームワーク全体のログ出力に使用されます。<br>
	 *
	 * @param logPath ログファイルのパス。nullの場合はログファイルを使用しません。
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setLogPath(String logPath) {
		this.logPath = logPath;
		return this;
	}

	/**
	 * ログファイルパスを設定します. 設定された値はロガーのグローバルパスとして使用され、フレームワーク全体のログ出力に使用されます。<br>
	 *
	 * @param logPath ログファイルのパス。nullの場合はログファイルを使用しません。
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig setLogPathAndMKDIR(String logPath) {
		File dir = new File(logPath).getParentFile();
		if (!dir.exists()) {
			dir.mkdir();
		}
		this.logPath = logPath;
		return this;
	}

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

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

	/**
	 * 多重起動を容認します。<br>
	 *
	 * @return thisインスタンスを返します。<br>
	 */
	public GameConfig noLock() {
		this.lock = false;
		return this;
	}

	public GameConfig updateIfNotActive(boolean updateIfNotActive) {
		this.updateIfNotActive = updateIfNotActive;
		return this;
	}

	public boolean isLock() {
		return lock;
	}

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

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

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

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

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

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

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

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

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

	/**
	 * ログファイルパスを取得します. このメソッドはKinugasaGameクラスから使用されます。<br>
	 *
	 * @return ログファイルのパス。使用しない場合null。
	 */
	protected String getLogPath() {
		return logPath;
	}

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

	public boolean isUpdateIfNotActive() {
		return updateIfNotActive;
	}

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

}
