/*--------------------------------------------------------

    DirectX utility library for sample

    Copyright (C) 2008 SEGA Corporation Library R&D Dept.
    All Rights Reserved.

    Module  : DirectX utility library for sample
    File    : smpDx.cpp
    Create  : 2008/06/13 (Ver.0.10.01)
    Modify  : 2008/11/05 (Ver.0.10.02)
	            フルスクリーンモードからウインドウモードへの切り替え時の不具合修正
			  2008/11/21 (Ver.0.10.03)
			    フルスクリーン用の PresentationInterval の設定をデフォルトから D3DPRESENT_INTERVAL_ONE に変更
			  2009/01/19 (Ver.0.10.04)
			    ・setIncludeCallbackFunc 関数を追加。
				・getD3DXInclude 関数を追加。
    Version : 0.10.04
    Note    : 

--------------------------------------------------------*/

#define __SMPDX_CPP__

//--------------------
// 組み込みファイル名
//--------------------
#include <stdio.h>
#include "smpDx.h"

////////////////////////////////////////////////////////////////////////////////
LPDIRECT3D9           CSmpDx::m_pD3d    = NULL;
LPDIRECT3DDEVICE9     CSmpDx::m_pD3ddev = NULL;
D3DPRESENT_PARAMETERS CSmpDx::m_d3dpp;

UINT32                CSmpDx::m_flag    = 0;

////////////////////////////////////////////////////////////////////////////////
//
static HWND  g_hWnd;		// ウインドウハンドル
static LONG  g_wndSytle;	// ウインドウスタイル
static RECT  g_wndRect;		// ウインドウ座標
static RECT  g_cltRect;		// クライアント座標

// プレゼンテーションパラメータ
static D3DPRESENT_PARAMETERS g_d3dppWin;	// ウインドウモード用
static D3DPRESENT_PARAMETERS g_d3dppFull;	// フルスクリーンモード用

//
static BOOL g_initFullScreenFlag;

////////////////////////////////////////////////////////////////////////////////
//
static CSmpDx::INCLUDE_CALLBACK_FUNC smpDxIncludeCallbackFunc = NULL;

//
class CSmpDxInclude : public ID3DXInclude{
public:
	HRESULT __stdcall Open( D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes ){
		if(smpDxIncludeCallbackFunc != NULL){
			LPCVOID argv[5];
			UINT    argc = 5;
			argv[0] = (void*)IncludeType;
			argv[1] = (void*)pFileName;
			argv[2] = (void*)pParentData;
			argv[3] = (void*)ppData;
			argv[4] = (void*)pBytes;
			return( smpDxIncludeCallbackFunc(CSmpDx::INCLUDE_MODE_OPEN, argc, argv) );
		}
		return(S_OK);
	}
	HRESULT __stdcall Close( LPCVOID pData ){
		if(smpDxIncludeCallbackFunc != NULL){
			LPCVOID argv[1];
			UINT    argc = 1;
			argv[0] = (void*) pData;
			return( smpDxIncludeCallbackFunc( CSmpDx::INCLUDE_MODE_CLOSE, argc, argv) );
		}
		return(S_OK);
	}
};
static CSmpDxInclude g_include;

////////////////////////////////////////////////////////////////////////////////
//--------
// 初期化
//--------
void CSmpDx::init(HWND hWnd)
{
	g_hWnd		= hWnd;
	g_wndSytle	= GetWindowLong(g_hWnd, GWL_STYLE);
	GetWindowRect(g_hWnd, &g_wndRect);

	// プレゼンテーションパラメータの初期化
	ZeroMemory(&g_d3dppWin,  sizeof(D3DPRESENT_PARAMETERS));
	ZeroMemory(&g_d3dppFull, sizeof(D3DPRESENT_PARAMETERS));

	// ウインドウモード用のデフォルト設定
	g_d3dppWin.Windowed						= TRUE;
    g_d3dppWin.BackBufferFormat				= D3DFMT_UNKNOWN;
	g_d3dppWin.hDeviceWindow				= g_hWnd;
	g_d3dppWin.BackBufferCount				= 1;
    g_d3dppWin.SwapEffect					= D3DSWAPEFFECT_DISCARD;
	g_d3dppWin.EnableAutoDepthStencil		= TRUE;
	g_d3dppWin.AutoDepthStencilFormat		= D3DFMT_D24S8;
	g_d3dppWin.PresentationInterval			= D3DPRESENT_INTERVAL_ONE;

	// フルスクリーンモード用のデフォルト設定
	g_d3dppFull = g_d3dppWin;
	g_d3dppFull.Windowed					= FALSE;

	//
	g_initFullScreenFlag					= FALSE;

	//
	smpDxIncludeCallbackFunc				= NULL;
}

//----------------------------------
// フルスクリーンモードの初期化設定
//----------------------------------
void CSmpDx::initFullScreen(UINT width, UINT height, D3DFORMAT format, UINT refreshRate, BOOL enb)
{
	// バックバッファサイズ
	g_d3dppFull.BackBufferWidth				= width;
	g_d3dppFull.BackBufferHeight			= height;
	// バックバッファのフォーマット
	g_d3dppFull.BackBufferFormat			= format;
	// リフレッシュレート
	g_d3dppFull.FullScreen_RefreshRateInHz	= refreshRate;
	g_d3dppFull.PresentationInterval		= D3DPRESENT_INTERVAL_ONE;

	g_initFullScreenFlag					= enb;
}

//------------------------
// 描画デバイスを生成する
//------------------------
static HRESULT smpDxCreateDevice(HWND hWnd, IDirect3D9 *pD3d, D3DPRESENT_PARAMETERS *pD3dpp, IDirect3DDevice9 **pD3ddev)
{
	if(SUCCEEDED(pD3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, pD3dpp, pD3ddev))) return(S_OK);
	if(SUCCEEDED(pD3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, pD3dpp, pD3ddev))) return(S_OK);
	if(SUCCEEDED(pD3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, pD3dpp, pD3ddev))) return(S_OK);
	return(E_FAIL);
}

//--------------------------------
// 適切なディスプレイモードを返す
//--------------------------------
static BOOL smpDxChooseDisplayMode(LPDIRECT3D9 pD3d, UINT width, UINT height, UINT refreshRate, D3DFORMAT format, D3DDISPLAYMODE &dispMode)
{
	BOOL           result;
	D3DDISPLAYMODE dm;
	INT            level, lev,
		           cnt,
		           i;
	result = FALSE;
	level = 1000000;
	// ディスプレイモード数を得る
	cnt   = pD3d->GetAdapterModeCount(D3DADAPTER_DEFAULT, format);
	//printf("%d\n", cnt);
	for (i=0; i<cnt; i++){
		// ディスプレイモードを得る
		pD3d->EnumAdapterModes(D3DADAPTER_DEFAULT, format, i, &dm);
		//printf("[%d] %d %d %d %d\n", i, dm.Width, dm.Height, dm.RefreshRate, dm.Format);
		lev = abs((INT)(width-dm.Width)) + abs((INT)(height-dm.Height)) + abs((INT)(refreshRate-dm.RefreshRate));
		if(lev < level){
			level = lev;
			dispMode = dm;
			result = TRUE;
		}
	}
	//printf("%d %d %d %d\n", dispMode.Width, dispMode.Height, dispMode.RefreshRate, dispMode.Format);
	return(result);
}

//----------------------------------------------------------------------
// 現在のフルスクリーンの設定を適切なディスプレイモードの設定に変更する
//----------------------------------------------------------------------
static BOOL smpDxFullScreenSet(LPDIRECT3D9 pD3d, D3DPRESENT_PARAMETERS &d3dppFull)
{
	BOOL           result;
	D3DDISPLAYMODE dispMode;
	// 適切なディスプレイモードを返す
	result = smpDxChooseDisplayMode(pD3d,
									d3dppFull.BackBufferWidth,
									d3dppFull.BackBufferHeight,
									d3dppFull.FullScreen_RefreshRateInHz,
									d3dppFull.BackBufferFormat,
									dispMode);
	if(result == TRUE){
		// 設定
		d3dppFull.BackBufferWidth				= dispMode.Width;
		d3dppFull.BackBufferHeight				= dispMode.Height;
		d3dppFull.FullScreen_RefreshRateInHz	= dispMode.RefreshRate;
	}
	return(result);
}

//----------------------------------------------
// ウインドウをフルスクリーンスタイルに変更する
//----------------------------------------------
void CSmpDx::changeFullScreenStyle(void)
{
	// 現在のウインドウスタイルを得る
	g_wndSytle = GetWindowLong(g_hWnd, GWL_STYLE);
	// 現在のウインドウの座標を得る
	GetWindowRect(g_hWnd, &g_wndRect);
	// 現在のクライアントの座標を得る
	GetClientRect(g_hWnd, &g_cltRect);
	// フルスクリーン用のウインドウスタイルにする
	SetWindowLong(g_hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
}

//--------------------------------
// フルスクリーンモードに変更する
//--------------------------------
HRESULT CSmpDx::changeFullScreenMode(void)
{
	BOOL result;
	// 現在のフルスクリーンの設定を適切なディスプレイモードの設定に変更する
	result = smpDxFullScreenSet(m_pD3d, g_d3dppFull);
	if(result == TRUE){
		// ウインドウをフルスクリーンスタイルに変更する
		changeFullScreenStyle();
		//
		m_d3dpp = g_d3dppFull;
		//
		return(m_pD3ddev->Reset(&m_d3dpp));
	}
	return(E_FAIL);
}

//----------------------------
// ウインドウモードに変更する
//----------------------------
HRESULT CSmpDx::changeWindowMode(void)
{
	m_d3dpp = g_d3dppWin;
	//
	m_d3dpp.BackBufferWidth  = g_cltRect.right-g_cltRect.left;
	m_d3dpp.BackBufferHeight = g_cltRect.bottom-g_cltRect.top;
	//
	HRESULT hr = m_pD3ddev->Reset(&m_d3dpp);
	// もとのウインドウスタイルに戻す
	SetWindowLong(g_hWnd, GWL_STYLE, g_wndSytle | WS_VISIBLE );
	// もとのウインドウ座標に戻す
	//    WM_SIZE のメッセージが送られることに注意すること。
	//    もつ１つ、フルスクリーンモードであることにも注意すること。
	SetWindowPos(g_hWnd, HWND_NOTOPMOST,
				 g_wndRect.left, g_wndRect.top,
				 g_wndRect.right - g_wndRect.left,
				 g_wndRect.bottom - g_wndRect.top,
				 SWP_SHOWWINDOW);
	return(hr);
}

//-----------------------------------------------
// DirectX Graphics で使用するデバイスを生成する
//-----------------------------------------------
HRESULT CSmpDx::create(void)
{
	// Direct3Dシステム作成
	m_pD3d = Direct3DCreate9(D3D_SDK_VERSION);

	if(g_initFullScreenFlag == FALSE){
		m_d3dpp = g_d3dppWin;
	} else{
		// 現在のフルスクリーンの設定を適切なディスプレイモードの設定に変更する
		if(smpDxFullScreenSet(m_pD3d, g_d3dppFull) == TRUE){
			changeFullScreenStyle();
			m_d3dpp = g_d3dppFull;
		} else{
			// 失敗
			m_d3dpp = g_d3dppWin;
		}
	}

	// 描画デバイスを生成する
	HRESULT result;
	result = smpDxCreateDevice(g_hWnd , m_pD3d, &m_d3dpp, &m_pD3ddev);
	return(result);
}

//-------------------------
// DirectX Graphics の終了
//-------------------------
void CSmpDx::release(void)
{
	if(m_pD3ddev != NULL){
		m_pD3ddev->Release();
	}
	if(m_pD3d != NULL){
		m_pD3d->Release();
	}
}

//------------------------------------------
// インクルードコールバック関数をセットする
//------------------------------------------
void CSmpDx::setIncludeCallbackFunc(INCLUDE_CALLBACK_FUNC func){smpDxIncludeCallbackFunc=func;}

//------------------------------------
// ID3DXIncludeインターフェイスを返す
//------------------------------------
LPD3DXINCLUDE CSmpDx::getD3DXInclude(void){return(&g_include);}
