//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * グラフィックスデバイス実装
 * @author Junpee
 */

#include "LampBasic.h"
#include "Graphics/System/GraphicsDevice.h"
#include "Graphics/System/LampGraphics.h"
#include "Graphics/DeviceSelector/GraphicsDeviceSelector.h"
#include "Graphics/System/GraphicsDeviceSettings.h"
#include "Graphics/Enumeration/GraphicsDeviceInformation.h"

namespace Lamp{

// インスタンス
GraphicsDevice* GraphicsDevice::instance_ = NULL;

//------------------------------------------------------------------------------
// コンストラクタ
GraphicsDevice::GraphicsDevice(){
	Assert(instance_ == NULL);
	instance_ = this;
	direct3DDevice_ = NULL;
	::memset(&presentationParameter_, 0, sizeof(D3DPRESENT_PARAMETERS));
	settings_ = NULL;
	windowHandle_ = NULL;
	windowStyle_ = 0;
	SetRect(&windowBoundsSize_, 0, 0, 0, 0);
	SetRect(&windowClientSize_, 0, 0, 0, 0);
	menuHandle_ = NULL;
	deviceLost_ = false;
	clipCursor_ = true;
	ignoreSizeChange_ = false;
	windowMaximized_ = false;
	windowMinimized_ = false;
}
//------------------------------------------------------------------------------
// デストラクタ
GraphicsDevice::~GraphicsDevice(){
	Assert(instance_ == this);
	instance_ = NULL;
}
//------------------------------------------------------------------------------
// クリア
void GraphicsDevice::clear(Color4c color, float zValue, u_int stencilValue){
	u_int clearFlag = D3DCLEAR_TARGET;
	if(settings_->getDepthStencilEnabled()){ clearFlag |= D3DCLEAR_ZBUFFER; }
	direct3DDevice_->Clear(
		0, NULL, clearFlag, color.getARGB(), zValue, stencilValue);
}
//------------------------------------------------------------------------------
// プレゼンテーション
bool GraphicsDevice::presentation(){
	// デバイスがロストしていないことを確認してからシステムループを実行し、
	// 描画を行うメインルーチンを想定している
	HRESULT result;
	// デバイスロスト処理
	if(deviceLost_){
		result = direct3DDevice_->TestCooperativeLevel();
		if(DirectXFailed(result)){
			// デバイスリセット要求
			if(result == D3DERR_DEVICENOTRESET){
				if(settings_->isWindowed()){
					// デスクトップディスプレイフォーマットが変更されたので
					// プレゼンテーションパラメータを再構築する
					LampGraphics::getDeviceSelector()->findBestWindowedMode(
						windowHandle_, false, false);
					settings_->buildPresentationParameters(
						&presentationParameter_, windowHandle_);
				}
				if(!reset()){
					ErrorOut("GraphicsDevice::presentation() reset()");
				}
			}
		}else{
			// デバイスロスト解除
			deviceLost_ = false;
		}
	}else{
		result = direct3DDevice_->Present(NULL, NULL, NULL, NULL);
		// デバイスロストチェック
		if(result == D3DERR_DEVICELOST){ deviceLost_ = true; }
	}
	return deviceLost_;
}
//------------------------------------------------------------------------------
// フルスクリーンモードとウィンドウモードを切り替える
void GraphicsDevice::toggleFullscreen(){
	// デバイス情報のバックアップ
	GraphicsDeviceInformation* deviceInfoOld =
		settings_->getDeviceInformation();
	int adapterOrdinalOld = deviceInfoOld->getAdapterOrdinal();
	D3DDEVTYPE deviceTypeOld = deviceInfoOld->getDeviceType();
	// サイズ変更を無視する
	ignoreSizeChange_ = true;
	// ウィンドウフラグ反転
	settings_->setWindowed(!settings_->isWindowed());
	// ウィンドウの調整を行う
	adjustWindowForChange();
	// 3D環境の再構築
	bool result;
	GraphicsDeviceInformation* deviceInfo = settings_->getDeviceInformation();
	if((deviceInfo->getAdapterOrdinal() == adapterOrdinalOld) &&
		(deviceInfo->getDeviceType() == deviceTypeOld)){
		// リセットする
		settings_->buildPresentationParameters(
			&presentationParameter_, windowHandle_);
		result = reset();
	}else{
		// 再構築する
		result = rebuild();
	}
	// 切り替え失敗だったら元に戻す
	if(!result){ toggleFullscreen(); }
	// サイズ変更を無視しない
	ignoreSizeChange_ = false;
}
//------------------------------------------------------------------------------
// 強制的にウィンドウモードにする
void GraphicsDevice::forceWindowed(){
	if(settings_->isWindowed()){ return; }
	LampGraphics::getDeviceSelector()->findBestWindowedMode(
		windowHandle_, false, false);
	if(!rebuild()){ ErrorOut("GraphicsDevice::forceWindowed() rebuild()"); }
}
//------------------------------------------------------------------------------
// リビルド
bool GraphicsDevice::rebuild(){
	cleanup();
	return initialize();
}
//------------------------------------------------------------------------------
// ウィンドウハンドルの初期化
void GraphicsDevice::initializeWindowHandle(HWND windowHandle){
	// ウィンドウプロパティの取得
	windowHandle_ = windowHandle;
	windowStyle_ = ::GetWindowLong(windowHandle_, GWL_STYLE);
	::GetWindowRect(windowHandle_, &windowBoundsSize_);
	::GetClientRect(windowHandle_, &windowClientSize_);
	// デバイス設定の取得
	settings_ = GraphicsDeviceSettings::getInstance();
}
//------------------------------------------------------------------------------
// 初期化
bool GraphicsDevice::initialize(){
	// ウィンドウの調整
	adjustWindowForChange();
	// プレゼンテーションパラメータの構築
	settings_->buildPresentationParameters(
		&presentationParameter_, windowHandle_);
	// 頂点プロセスフラグの取得
	u_int vertexProsessingFlag =
		settings_->getVertexProcessingType().getCreateFlag();
	// デバイスの作成
	HRESULT result;
	Direct3D* direct3D = LampGraphics::getDirect3D();
	GraphicsDeviceInformation* deviceInfo = settings_->getDeviceInformation();
	result = direct3D->CreateDevice(
		deviceInfo->getAdapterOrdinal(),	// アダプタ番号
		deviceInfo->getDeviceType(),		// デバイスタイプ
		windowHandle_,						// ウィンドウハンドル
		vertexProsessingFlag,				// 振る舞いフラグ
		&presentationParameter_,			// プレゼンテーションパラメータ
		&direct3DDevice_);					// デバイス
	if(DirectXSucceeded(result)){
		// デバイスリセットを通知する
		LampGraphics::deviceReset();
		// カーソルクリップ処理
		clipCursor();
		// デバイスオブジェクトを再構築する
		if(!LampGraphics::initializeDeviceObjects()){
			LampGraphics::deleteDeviceObjects();
		}else{
			if(!LampGraphics::restoreDeviceObjects()){
				LampGraphics::invalidateDeviceObjects();
			}else{
				return true;
			}
		}
		cleanup();
	}
	// リファレンスラスタライザでの起動を試す
	if(deviceInfo->getDeviceType() == D3DDEVTYPE_HAL){
		if(LampGraphics::getDeviceSelector()->findBestWindowedMode(
			windowHandle_, false, true)){
			adjustWindowForChange();
			AssertMessage(false, "GraphicsDevice::initialize() "
				"Switched to REF");
			return initialize();
		}
	}
	return false;

}
//------------------------------------------------------------------------------
// リセット
bool GraphicsDevice::reset(){
	// デバイスオブジェクトを解放する
	LampGraphics::invalidateDeviceObjects();
	// リセットを行う
	direct3DDevice_->Reset(&presentationParameter_);
	// デバイスリセットを通知する
	LampGraphics::deviceReset();
	// カーソルクリップ処理
	clipCursor();
	// デバイスオブジェクトを再構築する
	if(!LampGraphics::restoreDeviceObjects()){
		LampGraphics::invalidateDeviceObjects();
		return false;
	}
	return true;
}
//------------------------------------------------------------------------------
// カーソルのクリップ
void GraphicsDevice::clipCursor(){
	if(!clipCursor_){ return; }
	if(settings_->isWindowed()){
		ClipCursor(NULL);
	}else{
		RECT clipRect;
		GetWindowRect(windowHandle_, &clipRect);
		ClipCursor(&clipRect);
	}
}
//------------------------------------------------------------------------------
// 後始末
void GraphicsDevice::cleanup(){
	// デバイスオブジェクトの無効化
	LampGraphics::invalidateDeviceObjects();
	// デバイスオブジェクトの破棄
	LampGraphics::deleteDeviceObjects();
	// Direct3DDeviceの解放
	SafeRelease(direct3DDevice_);
}
//------------------------------------------------------------------------------
// ウィンドウプロシージャ
LRESULT GraphicsDevice::windowProcedure(
	HWND windowHandle, u_int message, WPARAM wParam, LPARAM lParam){
	switch(message){
	// サイズ変更
	case WM_SIZE:
		if(settings_->isWindowed() && windowHandle_ != NULL){
			windowStyle_ = GetWindowLong(windowHandle_, GWL_STYLE);
		}
		if(wParam == SIZE_MINIMIZED){
			// カーソルのクリップを無効にする
			if(clipCursor_ && (!settings_->isWindowed())){
				ClipCursor(NULL);
			}
			windowMinimized_ = true;
			windowMaximized_ = false;
		}else if(wParam == SIZE_MAXIMIZED){
			windowMaximized_ = true;
			windowMinimized_ = false;
			handlePossibleSizeChange();
		}else if(wParam == SIZE_RESTORED){
			if(windowMaximized_){
				windowMaximized_ = false;
				handlePossibleSizeChange();
			}else if(windowMinimized_){
				windowMinimized_ = false;
				handlePossibleSizeChange();
			}
			// ドラッグでのサイズ変更はWM_EXITSIZEMOVEで対処する
		}
		break;
	// サイズ変更終了
	case WM_EXITSIZEMOVE:
		handlePossibleSizeChange();
		break;
	// フルスクリーン時にメニュー選択を無効にする
	case WM_NCHITTEST:
		if(!settings_->isWindowed()){ return HTCLIENT; }
		break;
	// システムコマンド
	case WM_SYSCOMMAND:
		switch(wParam){
			// フルスクリーン時に以下のイベントが起こらないようにする
			case SC_MOVE:
			case SC_SIZE:
			case SC_MAXIMIZE:
			case SC_KEYMENU:
			case SC_MONITORPOWER:
				if(!settings_->isWindowed()){ return 1; }
				break;
		}
		break;
	}
	return 0;
}
//------------------------------------------------------------------------------
// ウィンドウの調整
void GraphicsDevice::adjustWindowForChange(){
	if(settings_->isWindowed()){
		// ウィンドウスタイルの設定
		SetWindowLong(windowHandle_, GWL_STYLE, windowStyle_);
		// メニューハンドルの設定
		if(menuHandle_ != NULL){
			SetMenu(windowHandle_, menuHandle_);
			menuHandle_ = NULL;
		}
		// ウィンドウサイズの調整
		SetWindowPos(windowHandle_, HWND_NOTOPMOST,
			windowBoundsSize_.left, windowBoundsSize_.top,
			(windowBoundsSize_.right - windowBoundsSize_.left),
			(windowBoundsSize_.bottom - windowBoundsSize_.top),
			SWP_SHOWWINDOW);
	}else{
		// ウィンドウスタイルの設定
		u_int fullScreenStyle = (WS_POPUP | WS_SYSMENU | WS_VISIBLE);
		SetWindowLong(windowHandle_, GWL_STYLE, fullScreenStyle);
		// メニューハンドルの設定
		if(menuHandle_ == NULL){
			menuHandle_ = GetMenu(windowHandle_);
			SetMenu(windowHandle_, NULL);
		}
	}
}
//------------------------------------------------------------------------------
// ウィンドウサイズ変更
void GraphicsDevice::handlePossibleSizeChange(){
	// 無視するフラグが立っていれば無視する
	if(ignoreSizeChange_){ return; }
	// クライアントサイズのバックアップ
	RECT windowClientSizeOld =  windowClientSize_;
	// 新しいサイズの取得
	::GetWindowRect(windowHandle_, &windowBoundsSize_);
	::GetClientRect(windowHandle_, &windowClientSize_);
	// サイズが変わっていなければ無視
	int width = (windowClientSize_.right - windowClientSize_.left);
	int height = (windowClientSize_.bottom - windowClientSize_.top);
	if(((windowClientSizeOld.right - windowClientSizeOld.left) == width) &&
		((windowClientSizeOld.bottom - windowClientSizeOld.top) == height)){
		return;
	}
	settings_->setWindowSize(DimensionI(width, height));
	settings_->buildPresentationParameters(
		&presentationParameter_, windowHandle_);
	if(!reset()){
		ErrorOut("GraphicsDevice::handlePossibleSizeChange()");
	}
	// つぶれた画面の表示を行わないようにする
	clear();
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
