/*
 * 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.EventQueue;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import kinugasa.contents.graphics.ImageUtil;
import kinugasa.contents.resource.TempFileStorage;
import kinugasa.inputDevice.KeyConnection;
import kinugasa.inputDevice.MouseConnection;
import kinugasa.util.Versions;

/**
 * 衣笠ゲームフレームワークの核となるゲームクラスの基本の実装です.
 * <br>
 * 衣笠フレームワークを使用してゲームを構築するには、このクラスを継承したゲームクラスを用意します。<br>
 * <br>
 *
 * @version 1.0.0 - 2013/04/20_18:54:20<br>
 * @author Dra0211<br>
 */
public abstract class KinugasaGame extends Game {

	/**
	 * ゲームを表示するウインドウです.
	 */
	private AWTGameWindow window;
	/**
	 * グラフィックスコンテキストのバッファです.
	 */
	private BufferStrategy graphicsBuffer;
	/**
	 * あるフレームにおいてウインドウに描画するための グラフィックスコンテキストです.
	 */
	private Graphics2D graphicsContext;
	/**
	 * レンダリングの品質です.
	 */
	private RenderingHints renderingHints;
	/**
	 * ゲームを実行するスレッドです.
	 */
	private GameLoop loop;
	/**
	 * ゲームのFPSを管理するクラスです.
	 */
	private GameTimeManager gameTimeManager;
	/**
	 * 描画時にグラフィックスコンテキストをクリッピングするための ウインドウの内部領域です.
	 */
	private Rectangle clippingRectangle;

	/**
	 * ゲームを作成します.このコンストラクタを呼び出しただけでは、ゲームは起動しません.
	 * <br>
	 * ただし、ロックファイルは生成されます。（ロック機能を使用する場合）<br>
	 * ロックファイルを使用する場合のみ、ロックファイルの有無が検査され、 すでにロックファイルが存在している場合は例外を発生します。<br>
	 * 多重起動を検地するには「AlreadyExecutedException」をキャッチしてください。<br>
	 * <br>
	 *
	 * @param conf ゲームの設定を送信します。ウインドウのサイズやFPSの設定が含まれます。<br>
	 */
	protected KinugasaGame(GameConfig conf) {
		super(conf.getTitle());
		if (conf.getLogPath() != null) {
			GameLog.usingLog(conf.getLogPath());
			try {
				FileHandler handler = new FileHandler(conf.getLogPath());
				handler.setLevel(Level.ALL);
				handler.setFormatter(new Formatter() {

					final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

					@Override
					public String format(LogRecord record) {
						StringBuilder line = new StringBuilder();
						line.append(DATE_FORMAT.format(new Date(record.getMillis()))).append(' ');
						line.append(record.getLevel().getName()).append(' ');
						if (record.getThrown() != null) {
							line.append("（例外）").append(record.getThrown().getMessage());
						} else {
							line.append(record.getMessage());
						}
						line.append(System.lineSeparator());
						return line.toString();
					}
				});

				Logger.getGlobal().addHandler(handler);
			} catch (IOException ex) {
				GameLog.print(Level.WARNING, ex);
			} catch (SecurityException ex) {
				GameLog.print(Level.WARNING, ex);
			}
			GameLog.printInfo("ログファイル[" + conf.getLogPath() + "]を使用します");
		}

		if (conf.isLock()) {
			if (LockUtil.isExistsLockFile()) {
				throw new AlreadyExecutedException(LockUtil.listLockFile());
			}
			LockUtil.createLockFile();
		}

		window = new AWTGameWindow();
		renderingHints = conf.getRenderingHints();

		window.getCloseEventStorage().addAll(conf.getCloseEvetList());
		window.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				List<WindowCloseEvent> eventList = window.getCloseEventStorage().asSortedList();
				for (int i = 0, size = eventList.size(); i < size; i++) {
					if (!eventList.get(i).close()) {
						return;
					}
				}
				gameExit();
			}
		});

		window.setTitle(conf.getTitle());
		window.setIconImage(conf.getIcon());
		window.setBackground(conf.getBackColor());
		window.setLocation(conf.getLocation());
		window.setSize(conf.getSize());
		window.setResizable(false);

		PlayerConstants playerConstants = PlayerConstants.getInstance();

		playerConstants.setUsingKeyboard(conf.isUseKeyboard());
		if (playerConstants.isUsingKeyboard()) {
			KeyConnection.setListener(window);
		}

		playerConstants.setUsingMouse(conf.isUseMouseCursor());
		if (playerConstants.isUsingMouse()) {
			MouseConnection.setListener(window);
		} else {
			BufferedImage cursorImage = ImageUtil.newImage(16, 16);
			window.setCursor(Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(), "game cursor"));
		}
		this.fps = conf.getFps();
		this.updateIfNotActive = conf.isUpdateIfNotActive();
	}
	private boolean updateIfNotActive;


	/**
	 * 簡易コンストラクタです. ウインドウサイズや背景色はstartUpメソッドで設定する必要があります。<br>
	 *
	 * @param name ゲームウインドウのタイトルとなる文字列です。ゲームの名前としても設定されます。<br>
	 */
	public KinugasaGame(String name) {
		this(new GameConfig(name));
	}
	/**
	 * gameTimeMangerに設定するFPSの最大値です.
	 */
	private int fps;
	/**
	 * ゲームの起動と終了を1回に制限するためのフラグです.
	 */
	private boolean started = false;

	@Override
	protected final void gameStart(String[] args) throws IllegalStateException {
		if (started) {
			throw new IllegalStateException("このゲームはすでに開始しています [" + name + "]");
		}
		CMDArgs.getInstance().setArgs(args);
		loop = new GameLoop(this, gameTimeManager = new GameTimeManager(fps), updateIfNotActive);
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				try {
					startUp();
				} catch (Throwable ex) {
					GameLog.print(Level.WARNING, "ERROR : " + ex);
					System.exit(1);
				}
				window.setVisible(true);
				window.createBufferStrategy(2);
				graphicsBuffer = window.getBufferStrategy();
				clippingRectangle = window.getInternalBounds();
				started = true;
				GameLog.printInfo("---- gameStartが完了、loopを開始します");
				loop.start();
			}
		});
		GameLog.printInfoIfUsing(Versions.PROJECT_NAME + "-" + Versions.getLatest().getValue() + " [" + getName() + "]を開始しました");
	}

	@Override
	public final void gameExit() throws IllegalStateException {
		if (!started) {
			throw new IllegalStateException("[" + name + "]は開始していません");
		}
		if (loop != null && loop.isStarted()) {
			loop.end();
		}
		try {
			dispose();
		} catch (Throwable ex) {
			GameLog.print(Level.WARNING, "ERROR : " + ex);
			System.exit(1);
		}
		GameLog.printInfoIfUsing("----gameExitを開始します");
		window.dispose();
		TempFileStorage.getInstance().deleteAll();
		LockUtil.deleteLockFile();
		GameLog.printInfoIfUsing(Versions.PROJECT_NAME + "-" + Versions.getLatest().getValue() + " [" + getName() + "]を終了します");
		System.exit(0);
	}

	@Override
	final void repaint() {
		graphicsContext = (Graphics2D) graphicsBuffer.getDrawGraphics();
		graphicsContext.setClip(clippingRectangle);
		graphicsContext.clearRect(clippingRectangle.x, clippingRectangle.y,
				clippingRectangle.width, clippingRectangle.height);
		graphicsContext.setRenderingHints(renderingHints);
		draw(graphicsContext);
		graphicsContext.dispose();
		if (graphicsBuffer.contentsRestored()) {
			repaint();
		}
		graphicsBuffer.show();
		if (graphicsBuffer.contentsLost()) {
			repaint();
		}
	}

	@Override
	protected abstract void startUp();

	@Override
	protected abstract void dispose();

	@Override
	protected abstract void update();

	@Override
	protected abstract void draw(Graphics2D g);

	@Override
	public final GameWindow getWindow() {
		return window;
	}

	@Override
	public final GameTimeManager getGameTimeManager() {
		return gameTimeManager;
	}

	@Override
	public String toString() {
		return "kinugasaGame : name=[" + name + "]";
	}

	/**
	 * バージョン情報を出力します.
	 *
	 * @param args コマンドライン引数は無視されます。<br>
	 */
	public static void main(String[] args) {
		GameLog.printInfoIfUsing(Versions.PROJECT_NAME + " " + Versions.getLatest());
	}
}
