// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// ***************************************************************************
// display_dlg.cpp : implementation file
// ***************************************************************************

#include "stdafx.h"
#include "client_config.h"
#include "display_dlg.h"
#include "cfg_file.h"

using namespace std;
using namespace NLMISC;
using namespace NL3D;

// ***************************************************************************

std::vector<string>		GLExtensions;
std::string				GLRenderer;
std::string				GLVendor;
std::string				GLVersion;
std::string				D3DDescription;
std::string				D3DDeviceName;
std::string				D3DDriver;
std::string				D3DDriverVersion;
std::string				D3DVendor;

uint					VideoMemory;
uint					HardwareSoundBuffer;
uint64					SystemMemory;
uint					CPUFrequency;

bool GetGLInformation ()
{
	// *** INIT VARIABLES
	
	GLExtensions.clear ();
	GLRenderer = "";
	GLVendor = "";
	GLVersion = "";

	// *** INIT OPENGL
	
	// Register a window class
	WNDCLASS		wc;
	memset(&wc,0,sizeof(wc));
	wc.style			= CS_HREDRAW | CS_VREDRAW ;//| CS_DBLCLKS;
	wc.lpfnWndProc		= DefWindowProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= GetModuleHandle(NULL);
	wc.hIcon			= (HICON)NULL;
	wc.hCursor			= LoadCursor(NULL,IDC_ARROW);
	wc.hbrBackground	= WHITE_BRUSH;
	wc.lpszClassName	= "RyzomGetGlInformation";
	wc.lpszMenuName		= NULL;
	if ( !RegisterClass(&wc) ) 
		return false;

	// Create a window
	ULONG	WndFlags = WS_OVERLAPPEDWINDOW+WS_CLIPCHILDREN+WS_CLIPSIBLINGS;
	RECT	WndRect;
	WndRect.left=0;
	WndRect.top=0;
	WndRect.right=100;
	WndRect.bottom=100;
	HWND hWnd = CreateWindow (	"RyzomGetGlInformation",
							"",
							WndFlags,
							CW_USEDEFAULT,CW_USEDEFAULT,
							WndRect.right,WndRect.bottom,
							NULL,
							NULL,
							GetModuleHandle(NULL),
							NULL);
	if (!hWnd) 
		return false;

	HDC hDC = GetDC(hWnd);

	// Remove current gl context
	wglMakeCurrent (hDC, NULL);

	// Select pixel format
	int depth = GetDeviceCaps (hDC, BITSPIXEL);
	PIXELFORMATDESCRIPTOR	pfd;
	memset(&pfd,0,sizeof(pfd));
	pfd.nSize        = sizeof(pfd);
	pfd.nVersion     = 1;
	pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType   = PFD_TYPE_RGBA;
	pfd.cColorBits   = (char)depth;
	if(depth<=16)
		pfd.cDepthBits   = 16;
	else
	{
		pfd.cDepthBits = 24;
		pfd.cAlphaBits	= 8;
	}
	pfd.iLayerType	  = PFD_MAIN_PLANE;
	int pf=ChoosePixelFormat(hDC, &pfd);
	if (!pf)
		return false;
	if ( !SetPixelFormat(hDC, pf, &pfd) ) 
		return false;

	// Create final context
	HGLRC hRC = wglCreateContext (hDC);
	wglMakeCurrent(hDC, hRC);

	// *** GET INFORMATION

	GLVendor = (const char *) glGetString (GL_VENDOR);
	GLRenderer = (const char *) glGetString (GL_RENDERER);
	GLVersion = (const char *) glGetString (GL_VERSION);

	// Get extension string
	string glext = (char*)glGetString(GL_EXTENSIONS);
	const char *token = strtok ( (char*)glext.c_str (), " ");
	while( token != NULL )
	{
		GLExtensions.push_back (token);
		token = strtok( NULL, " ");
	}

	// Get proc adress
	typedef	const char*	(APIENTRY * PFNWGFGETEXTENSIONSSTRINGARB) (HDC);
	PFNWGFGETEXTENSIONSSTRINGARB wglGetExtensionsStringARB;
	if ((wglGetExtensionsStringARB=(PFNWGFGETEXTENSIONSSTRINGARB)wglGetProcAddress("wglGetExtensionsStringARB")))
	{
		// Get extension string
		glext = wglGetExtensionsStringARB (hDC);
		token = strtok ( (char*)glext.c_str (), " ");
		while( token != NULL )
		{
			GLExtensions.push_back (token);
			token = strtok( NULL, " ");
		}
	}

	// *** RELEASE OPENGL

	if (hRC)
		wglDeleteContext(hRC);
	if (hWnd&&hDC)
	{
		ReleaseDC (hWnd,hDC);
		DestroyWindow (hWnd);
	}

	// Done
	return true;
}

// ***************************************************************************

bool GetD3DInformation (NL3D::IDriver *d3dDriver)
{
	IDriver::CAdapter desc;
	if (d3dDriver->getAdapter (0xffffffff, desc))
	{
		D3DDescription = desc.Description;
		D3DDeviceName = desc.DeviceName;
		D3DDriver = desc.Driver;
		D3DDriverVersion = toString ((uint16)(desc.DriverVersion>>48))+"."+
			toString ((uint16)(desc.DriverVersion>>32))+"."+
			toString ((uint16)(desc.DriverVersion>>16))+"."+
			toString ((uint16)(desc.DriverVersion&0xffff));
		D3DVendor = desc.Vendor;		
		return true;
	}
	return false;
}

// ***************************************************************************

bool GetVideoMemory ()
{
	VideoMemory = 0;
	bool ret = false;

	// Initialise Direct Draw
	IDirectDraw *dd;
	HRESULT result = DirectDrawCreate(NULL, &dd, NULL);
	if (result == DD_OK)
	{
		DDCAPS caps;
		memset (&caps, 0, sizeof(DDCAPS));
		caps.dwSize = sizeof(DDCAPS);
		dd->GetCaps (&caps, NULL);
		VideoMemory = caps.dwVidMemTotal;
		ret = true;
		dd->Release ();
	}

	// Can't get it
	return ret;
}

// ***************************************************************************

bool GetHardwareSoundBuffer ()
{
	bool ret = false;
	HardwareSoundBuffer = 0;

	// The DirectSound object
#if (DIRECTSOUND_VERSION >= 0x0800)
    LPDIRECTSOUND8 _DirectSound;
#else
	LPDIRECTSOUND _DirectSound;
#endif

#if (DIRECTSOUND_VERSION >= 0x0800)
	if (DirectSoundCreate8(NULL, &_DirectSound, NULL) == DS_OK)
#else
	if (DirectSoundCreate(NULL, &_DirectSound, NULL) == DS_OK)
#endif
	{
		DSCAPS caps;
		memset (&caps, 0, sizeof (DSCAPS));
		caps.dwSize = sizeof (DSCAPS);

		HRESULT result = _DirectSound->GetCaps (&caps);
		if (result == DS_OK)
		{
			HardwareSoundBuffer = caps.dwFreeHw3DStaticBuffers;
			ret = true;
		}
		_DirectSound->Release ();
	}
	return ret;
}

// ***************************************************************************

bool GetSystemInformation (IDriver *d3dDriver)
{
	bool result = GetGLInformation ();
	result |= GetD3DInformation (d3dDriver);
	result |= GetVideoMemory ();
	result |= GetHardwareSoundBuffer ();
	SystemMemory = CSystemInfo::totalPhysicalMemory ();
	CPUFrequency = (uint)(CSystemInfo::getProcessorFrequency () / (uint64)1000000);
	return result;
}
// ***************************************************************************

std::vector<CVideoMode>		VideoModes[2];

// ***************************************************************************

void RegisterVideoModes (uint driverId, IDriver *driver)
{
	vector<GfxMode> modes;
	driver->getModes (modes);

	uint i;
	VideoModes[driverId].clear();
	for (i=0; i<modes.size(); i++)
	{
		// Keep only 32 bits
		if ((modes[i].Depth == 32) && (modes[i].Width >= 800) && (modes[i].Width >= 600))
		{
			// Add this mode
			CVideoMode mode;
			mode.Width = modes[i].Width;
			mode.Height = modes[i].Height;
			mode.ColorDepth = modes[i].Depth;
			mode.Frequency = modes[i].Frequency;
			VideoModes[driverId].push_back (mode);
		}
	}
}

// ***************************************************************************
// CDisplayDlg dialog
// ***************************************************************************

CDisplayDlg::CDisplayDlg(CWnd* pParent /*=NULL*/)
	: CBaseDialog(CDisplayDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CDisplayDlg)
	DriverChoiceMode = DrvChooseUnknwown;
	Windowed = -1;
	Width = 0;
	Height = 0;
	Mode = -1;
	PositionX = 0;
	PositionY = 0;
	//}}AFX_DATA_INIT
}

// ***************************************************************************

void CDisplayDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CDisplayDlg)
	DDX_Control(pDX, IDC_DISPLAY_FS_TEXT0, TextFS0);
	DDX_Control(pDX, IDC_DISPLAY_WND_TEXT0, TextWnd0);
	DDX_Control(pDX, IDC_DISPLAY_WND_TEXT1, TextWnd1);
	DDX_Control(pDX, IDC_DISPLAY_WND_TEXT2, TextWnd2);
	DDX_Control(pDX, IDC_DISPLAY_WND_TEXT3, TextWnd3);
	DDX_Control(pDX, IDC_POSITION_Y, PositionYCtrl);
	DDX_Control(pDX, IDC_POSITION_X, PositionXCtrl);
	DDX_Control(pDX, IDC_HEIGHT, HeightCtrl);
	DDX_Control(pDX, IDC_WIDTH, WidthCtrl);
	DDX_Control(pDX, IDC_MODE, ModeCtrl);
	DDX_Radio(pDX, IDC_DRV3D_AUTO, DriverChoiceMode);
	DDX_Radio(pDX, IDC_FULLSCREEN, Windowed);
	DDX_Text(pDX, IDC_WIDTH, Width);
	DDX_Text(pDX, IDC_HEIGHT, Height);
	DDX_CBIndex(pDX, IDC_MODE, Mode);
	DDX_Text(pDX, IDC_POSITION_X, PositionX);
	DDX_Text(pDX, IDC_POSITION_Y, PositionY);
	//}}AFX_DATA_MAP
}

// ***************************************************************************

BEGIN_MESSAGE_MAP(CDisplayDlg, CDialog)
	//{{AFX_MSG_MAP(CDisplayDlg)
	ON_BN_CLICKED(IDC_FULLSCREEN, OnFullscreen)
	ON_BN_CLICKED(IDC_WINDOW, OnWindow)
	ON_BN_CLICKED(IDC_DIRECT3D, OnDirect3d)
	ON_BN_CLICKED(IDC_DRV3D_AUTO, OnDrv3DAuto)
	ON_EN_CHANGE(IDC_HEIGHT, OnChangeHeight)
	ON_BN_CLICKED(IDC_OPENGL, OnOpengl)
	ON_EN_CHANGE(IDC_POSITION_X, OnChangePositionX)
	ON_EN_CHANGE(IDC_WIDTH, OnChangeWidth)
	ON_EN_CHANGE(IDC_POSITION_Y, OnChangePositionY)
	ON_CBN_SELCHANGE(IDC_MODE, OnSelchangeMode)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// ***************************************************************************
// CDisplayDlg message handlers
// ***************************************************************************

void CDisplayDlg::OnFullscreen() 
{
	UpdateData (TRUE);
	updateState ();
	UpdateData (FALSE);
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnWindow() 
{
	UpdateData (TRUE);
	updateState ();
	UpdateData (FALSE);
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::updateState ()
{
	ModeCtrl.EnableWindow (Windowed == 0);
	TextFS0.EnableWindow (Windowed == 0);
	WidthCtrl.EnableWindow (Windowed == 1);
	HeightCtrl.EnableWindow (Windowed == 1);
	PositionXCtrl.EnableWindow (Windowed == 1);
	PositionYCtrl.EnableWindow (Windowed == 1);
	TextWnd0.EnableWindow (Windowed == 1);
	TextWnd1.EnableWindow (Windowed == 1);
	TextWnd2.EnableWindow (Windowed == 1);
	TextWnd3.EnableWindow (Windowed == 1);
	
	// Fill the combobox values
	ModeCtrl.ResetContent ();
	uint i;
	if (DriverChoiceMode != DrvChooseUnknwown)
	{
		TDriver actualDriver = getActualDriver();
		for (i=0; i<VideoModes[actualDriver].size (); i++)
		{
			ucstring videoMode = toString (VideoModes[actualDriver][i].Width) + "x" + toString (VideoModes[actualDriver][i].Height) + 
				" " + toString (VideoModes[actualDriver][i].ColorDepth) + " " + CI18N::get ("uiConfigBits") + " " + 
				toString (VideoModes[actualDriver][i].Frequency) + " " + CI18N::get ("uiConfigHz");

			// hulud : Don't know how to set a wide string in a combo box...
			ModeCtrl.SendMessage (CB_INSERTSTRING, -1, (LPARAM)(videoMode.toString().c_str()));
		}
	}

	// Select the string
	ModeCtrl.SetCurSel (Mode);
}

// ***************************************************************************

BOOL CDisplayDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	updateState ();
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

// ***************************************************************************

void CDisplayDlg::OnDirect3d() 
{
	UpdateData (TRUE);
	updateState ();
	UpdateData (FALSE);
	InvalidateConfig ();
}

// ***************************************************************************
void CDisplayDlg::OnDrv3DAuto()
{
	UpdateData (TRUE);
	updateState ();
	UpdateData (FALSE);
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnChangeHeight() 
{
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnOpengl() 
{
	UpdateData (TRUE);
	updateState ();
	UpdateData (FALSE);
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnChangePositionX() 
{
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnChangeWidth() 
{
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnChangePositionY() 
{
	InvalidateConfig ();
}

// ***************************************************************************

void CDisplayDlg::OnSelchangeMode() 
{
	InvalidateConfig ();
}

// ***************************************************************************
CDisplayDlg::TDriver CDisplayDlg::getActualDriver() const
{
	switch(DriverChoiceMode)
	{
		case DrvChooseOpenGL: return OpenGL;
		case DrvChooseDirect3D: return Direct3D;
		default:		
		{				
			// D3D better on ATI Radeon cards
			std::string deviceName;
			uint64 drvVersion;
			CSystemInfo::getVideoInfo(deviceName, drvVersion);
			return strstr(toLower(deviceName).c_str(), "radeon") != NULL ? Direct3D : OpenGL;
		}		
	}
	return OpenGL;
}