//------------------------------------------------------------------------------
// 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/DeviceSelector/DesktopGraphicsDeviceSelector.h"
#include "Graphics/System/LampGraphics.h"
#include "Graphics/System/GraphicsDeviceSettings.h"
#include "Graphics/Enumeration/GraphicsDeviceEnumeration.h"

namespace Lamp{

//------------------------------------------------------------------------------
// コンストラクタ
DesktopGraphicsDeviceSelector::DesktopGraphicsDeviceSelector(){
}
//------------------------------------------------------------------------------
// デストラクタ
DesktopGraphicsDeviceSelector::~DesktopGraphicsDeviceSelector(){
}
//------------------------------------------------------------------------------
// ウィンドウモードの最適な設定を探す
bool DesktopGraphicsDeviceSelector::findBestWindowedMode(
	HWND windowHandle, bool requireHAL, bool requireREF){
	Direct3D* direct3D = LampGraphics::getDirect3D();
	GraphicsDeviceEnumeration* enumeration =
		GraphicsDeviceEnumeration::getInstance();
	// 最適なデバイス群
	GraphicsAdapterInformation* bestAdapter = NULL;
	GraphicsDeviceInformation* bestDevice = NULL;
	GraphicsDeviceComboInformation* bestDeviceCombo = NULL;
	// ウィンドウはプライマリデスクトップにあると仮定する
	D3DDISPLAYMODE primaryDesktopDisplayMode;
	direct3D->GetAdapterDisplayMode(0, &primaryDesktopDisplayMode);
	// 最適なデバイスを探す
	bool foundBestDevice = false;
	// アダプタループ
	int adapterCount = enumeration->getAdapterCount();
	for(int i = 0; i < adapterCount; i++){
		GraphicsAdapterInformation* adapter = enumeration->getAdapter(i);
		// デバイスループ
		int deviceCount = adapter->getDeviceCount();
		for(int j = 0; j < deviceCount; j++){
			GraphicsDeviceInformation* device = adapter->getDevice(j);
			// HAL要求に答えられないなら無視
			if(requireHAL && (device->getDeviceType() != D3DDEVTYPE_HAL)){
				continue;
			}
			// REF要求に答えられないなら無視
			if(requireREF && (device->getDeviceType() != D3DDEVTYPE_REF)){
				continue;
			}
			// デバイスコンボループ
			int deviceComboCount = device->getDeviceComboCount();
			for(int k = 0; k < deviceComboCount; k++){
				GraphicsDeviceComboInformation* combo =
					device->getDeviceCombo(k);
				// フルスクリーンモードなら無視
				if(!combo->isWindowed()){ continue; }
				// フォーマットが合わなければ無視
				if(combo->getAdapterFormat() !=
					primaryDesktopDisplayMode.Format){ continue; }
				// バックバッファとフォーマット一致
				bool matchedBackBuffer = 
					(combo->getAdapterFormat() == combo->getBackBufferFormat());
				if(	// デバイスコンボがNULLなら合格
					(bestDeviceCombo == NULL) ||
					// HALに対応できるようになるなら合格
					((bestDeviceCombo->getDeviceType() != D3DDEVTYPE_HAL) &&
						(combo->getDeviceType() == D3DDEVTYPE_HAL)) ||
					// HALでバックバッファが一致するなら合格
					((bestDeviceCombo->getDeviceType() == D3DDEVTYPE_HAL) &&
						matchedBackBuffer)
				){
					bestAdapter = adapter;
					bestDevice = device;
					bestDeviceCombo = combo;
					// 最高の条件ならループを終了させる
					if((device->getDeviceType() == D3DDEVTYPE_HAL) &&
						matchedBackBuffer){
						foundBestDevice = true;
						break;
					}
				}
			}
			if(foundBestDevice){ break; }
		}
		if(foundBestDevice){ break; }
	}
	// デバイスを発見できなければ失敗
	if(bestDeviceCombo == NULL){ return false; }

	// 設定にコピーする
	buildWindowModeSettings(windowHandle, primaryDesktopDisplayMode,
		bestAdapter, bestDevice, bestDeviceCombo);
	return true;
}
//------------------------------------------------------------------------------
// フルスクリーンモードの最適な設定を探す
bool DesktopGraphicsDeviceSelector::findBestFullscreenMode(
	HWND windowHandle, bool requireHAL, bool requireREF){
	Direct3D* direct3D = LampGraphics::getDirect3D();
	GraphicsDeviceEnumeration* enumeration =
		GraphicsDeviceEnumeration::getInstance();
	// 最適なデバイス群
	GraphicsAdapterInformation* bestAdapter = NULL;
	GraphicsDeviceInformation* bestDevice = NULL;
	GraphicsDeviceComboInformation* bestDeviceCombo = NULL;
	// 最適なデスクトップのディスプレイモード
	// 現在のデスクトップディスプレイモードに合わそうとします
	D3DDISPLAYMODE bestDesktopDisplayMode;
	// 最適なデバイスを探す
	bool foundBestDevice = false;
	// アダプタループ
	int adapterCount = enumeration->getAdapterCount();
	for(int i = 0; i < adapterCount; i++){
		GraphicsAdapterInformation* adapter = enumeration->getAdapter(i);
		// アダプタディスプレイモード取得
		D3DDISPLAYMODE desktopDisplayMode;
		direct3D->GetAdapterDisplayMode(
			adapter->getAdapterOrdinal(), &desktopDisplayMode);
		// デバイスループ
		int deviceCount = adapter->getDeviceCount();
		for(int j = 0; j < deviceCount; j++){
			GraphicsDeviceInformation* device = adapter->getDevice(j);
			// HAL要求に答えられないなら無視
			if(requireHAL && (device->getDeviceType() != D3DDEVTYPE_HAL)){
				continue;
			}
			// REF要求に答えられないなら無視
			if(requireREF && (device->getDeviceType() != D3DDEVTYPE_REF)){
				continue;
			}
			// デバイスコンボループ
			int deviceComboCount = device->getDeviceComboCount();
			for(int k = 0; k < deviceComboCount; k++){
				GraphicsDeviceComboInformation* combo =
					device->getDeviceCombo(k);
				// ウィンドウモードなら無視
				if(combo->isWindowed()){ continue; }
				// デスクトップとフォーマット一致
				bool matchedDesktop =
					(combo->getAdapterFormat() == desktopDisplayMode.Format);
				// バックバッファとフォーマット一致
				bool matchedBackBuffer = 
					(combo->getAdapterFormat() == combo->getBackBufferFormat());
				// デバイスコンボの審査
				if(	// デバイスコンボがNULLなら合格
					(bestDeviceCombo == NULL) ||
					// HALに対応できるようになるなら合格
					((bestDeviceCombo->getDeviceType() != D3DDEVTYPE_HAL) &&
						(combo->getDeviceType() == D3DDEVTYPE_HAL)) ||
					// HALでデスクトップフォーマットを一致できるようになるなら合格
					((bestDeviceCombo->getDeviceType() == D3DDEVTYPE_HAL) &&
						(bestDeviceCombo->getAdapterFormat() !=
							desktopDisplayMode.Format) && matchedDesktop) ||
					// HALでデスクトップもバックバッファも一致するなら合格
					((bestDeviceCombo->getDeviceType() == D3DDEVTYPE_HAL) &&
						matchedDesktop && matchedBackBuffer)
				){
					// 合格したのでデータ保存
					bestDesktopDisplayMode = desktopDisplayMode;
					bestAdapter = adapter;
					bestDevice = device;
					bestDeviceCombo = combo;
					// 最高の条件ならループを終了させる
					if((device->getDeviceType() == D3DDEVTYPE_HAL) &&
						matchedDesktop && matchedBackBuffer){
						foundBestDevice = true;
						break;
					}
				}
			}
			if(foundBestDevice){ break; }
		}
		if(foundBestDevice){ break; }
	}
	// デバイスを発見できなければ失敗
	if(bestDeviceCombo == NULL){ return false; }
/*
	// 最適なディスプレイモードを探す
	D3DDISPLAYMODE bestDisplayMode;
	bestDisplayMode.Width = 0;
	bestDisplayMode.Height = 0;
	bestDisplayMode.Format = D3DFMT_UNKNOWN;
	bestDisplayMode.RefreshRate = 0;
	int displayModeCount = bestAdapter->getDisplayModeCount();
	for(int i = 0; i < displayModeCount; i++){
		D3DDISPLAYMODE displayMode = bestAdapter->getDisplayMode(i);
		// フォーマットが違えば無視
		if(bestDeviceCombo->getAdapterFormat() != displayMode.Format){
			continue;
		}
		// デスクトップと完全一致したら終了
		if((displayMode.Width == bestDesktopDisplayMode.Width) &&
			(displayMode.Height == bestDesktopDisplayMode.Height) &&
			(displayMode.RefreshRate == bestDesktopDisplayMode.RefreshRate)){
			bestDisplayMode = displayMode;
			break;
		// リフレッシュレートが大きいだけなら合格
		}else if((displayMode.Width == bestDesktopDisplayMode.Width) &&
			(displayMode.Height == bestDesktopDisplayMode.Height) &&
			(displayMode.RefreshRate > bestDesktopDisplayMode.RefreshRate)){
			bestDisplayMode = displayMode;
		// 幅が同じなら合格
		}else if(displayMode.Width == bestDesktopDisplayMode.Width){
			bestDisplayMode = displayMode;
		// 一つめのディスプレイモードは合格
		}else if(bestDisplayMode.Width == 0){
			bestDisplayMode = displayMode;
		}
	}
//*/
//*
	// 最適なディスプレイモードを探す
	RECT windowRect;
	::GetClientRect(windowHandle, &windowRect);
	DimensionI windowSize(windowRect.right - windowRect.left,
		windowRect.bottom - windowRect.top);
	D3DDISPLAYMODE bestDisplayMode;
	bestDisplayMode.Width = 0;
	bestDisplayMode.Height = 0;
	bestDisplayMode.Format = D3DFMT_UNKNOWN;
	bestDisplayMode.RefreshRate = 0;
	int displayModeCount = bestAdapter->getDisplayModeCount();
	for(int i = 0; i < displayModeCount; i++){
		D3DDISPLAYMODE displayMode = bestAdapter->getDisplayMode(i);
		// フォーマットが違えば無視
		if(bestDeviceCombo->getAdapterFormat() != displayMode.Format){
			continue;
		}
		// 一つめのディスプレイモードは合格
		if(bestDisplayMode.Width == 0){
			bestDisplayMode = displayMode;
			continue;
		}
		// 幅が遠ければ不合格
		int newWidthDist = Math::abs((int)(
			(int)windowSize.width - (int)displayMode.Width));
		int bestWidthDist = Math::abs((int)(
			(int)windowSize.width - (int)bestDisplayMode.Width));
		if(newWidthDist > bestWidthDist){ continue; }
		// 幅が同じでなければ合格
		if(newWidthDist != bestWidthDist){
			bestDisplayMode = displayMode;
			continue;
		}
		// 幅が同じ場合、高さが遠ければ不合格
		int newHeightDist = Math::abs((int)(
			(int)windowSize.height - (int)displayMode.Height));
		int bestHeightDist = Math::abs((int)(
			(int)windowSize.height - (int)bestDisplayMode.Height));
		if(newHeightDist > bestHeightDist){ continue; }
		// 幅が同じ場合、高さが同じでなければ合格
		if(newHeightDist != bestHeightDist){
			bestDisplayMode = displayMode;
			continue;
		}
		// 幅も高さも同じ場合、リフレッシュレートがより違えば不合格
		int newRefreshDist = Math::abs((int)(
			(int)bestDesktopDisplayMode.RefreshRate -
			(int)displayMode.RefreshRate));
		int bestRefreshDist = Math::abs((int)(
			(int)bestDesktopDisplayMode.RefreshRate -
			(int)bestDisplayMode.RefreshRate));
		if(newRefreshDist > bestRefreshDist){ continue; }
		// そうでなければ合格
		bestDisplayMode = displayMode;
	}
//*/
	// 設定にコピーする
	buildFullscreenModeSettings(
		bestDisplayMode, bestAdapter, bestDevice, bestDeviceCombo);
	return true;
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
