/*
**
**
**---------------------------------------------------------------------------
** Copyright 2003-2005 Tim Stump
** Copyright 2005-2016 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <GL/gl.h>
#include "wglext.h"

#include "gl_sysfb.h"
#include "hardware.h"

#include "version.h"
#include "c_console.h"
#include "v_video.h"
#include "i_input.h"
#include "i_system.h"
#include "v_text.h"
#include "m_argv.h"
#include "base_sysfb.h"
#include "win32basevideo.h"
#include "c_dispatch.h"
#include "i_mainwindow.h"

extern "C" {
    __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;	
}

EXTERN_CVAR(Int, vid_defwidth)
EXTERN_CVAR(Int, vid_defheight)

//==========================================================================
//
// Windows framebuffer
//
//==========================================================================

//==========================================================================
//
// 
//
//==========================================================================

 void SystemBaseFrameBuffer::GetCenteredPos(int in_w, int in_h, int &winx, int &winy, int &winw, int &winh, int &scrwidth, int &scrheight)
{
	DEVMODE displaysettings;
	RECT rect;
	int cx, cy;

	memset(&displaysettings, 0, sizeof(displaysettings));
	displaysettings.dmSize = sizeof(displaysettings);
	EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &displaysettings);
	scrwidth = (int)displaysettings.dmPelsWidth;
	scrheight = (int)displaysettings.dmPelsHeight;
	GetWindowRect(mainwindow.GetHandle(), &rect);
	cx = scrwidth / 2;
	cy = scrheight / 2;
	if (in_w > 0) winw = in_w;
	else winw = rect.right - rect.left;
	if (in_h > 0) winh = in_h;
	else winh = rect.bottom - rect.top;
	winx = cx - winw / 2;
	winy = cy - winh / 2;
}

//==========================================================================
//
// 
//
//==========================================================================

void SystemBaseFrameBuffer::KeepWindowOnScreen(int &winx, int &winy, int winw, int winh, int scrwidth, int scrheight)
{
	// If the window is too large to fit entirely on the screen, at least
	// keep its upperleft corner visible.
	if (winx + winw > scrwidth)
	{
		winx = scrwidth - winw;
	}
	if (winx < 0)
	{
		winx = 0;
	}
	if (winy + winh > scrheight)
	{
		winy = scrheight - winh;
	}
	if (winy < 0)
	{
		winy = 0;
	}
}

//==========================================================================
//
// 
//
//==========================================================================

void SystemBaseFrameBuffer::SaveWindowedPos()
{
	// Don't save if we were run with the -0 option.
	if (Args->CheckParm("-0"))
	{
		return;
	}
	// Make sure we only save the window position if it's not fullscreen.
	static const int WINDOW_STYLE = WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX;
	if ((GetWindowLong(mainwindow.GetHandle(), GWL_STYLE) & WINDOW_STYLE) == WINDOW_STYLE)
	{
		RECT wrect;

		if (GetWindowRect(mainwindow.GetHandle(), &wrect))
		{
			// If (win_x,win_y) specify to center the window, don't change them
			// if the window is still centered.
			if (win_x < 0 || win_y < 0 || win_w < 0 || win_h < 0)
			{
				int winx, winy, winw, winh, scrwidth, scrheight;

				GetCenteredPos(win_w, win_h, winx, winy, winw, winh, scrwidth, scrheight);
				KeepWindowOnScreen(winx, winy, winw, winh, scrwidth, scrheight);
				if (win_x < 0 && winx == wrect.left)
				{
					wrect.left = win_x;
				}
				if (win_y < 0 && winy == wrect.top)
				{
					wrect.top = win_y;
				}
				wrect.right = winw + wrect.left;
				wrect.bottom = winh + wrect.top;
			}

			win_x = wrect.left;
			win_y = wrect.top;
			win_w = wrect.right - wrect.left;
			win_h = wrect.bottom - wrect.top;
		}

		win_maximized = IsZoomed(mainwindow.GetHandle()) == TRUE;
	}
}

//==========================================================================
//
// 
//
//==========================================================================

void SystemBaseFrameBuffer::RestoreWindowedPos()
{
	int winx, winy, winw, winh, scrwidth, scrheight;

	GetCenteredPos(win_w, win_h, winx, winy, winw, winh, scrwidth, scrheight);

	// Just move to (0,0) if we were run with the -0 option.
	if (Args->CheckParm("-0"))
	{
		winx = winy = 0;
	}
	else
	{
		if (win_x >= 0)
		{
			winx = win_x;
		}
		if (win_y >= 0)
		{
			winy = win_y;
		}
		KeepWindowOnScreen(winx, winy, winw, winh, scrwidth, scrheight);
	}
	SetWindowPos(mainwindow.GetHandle(), nullptr, winx, winy, winw, winh, SWP_NOZORDER | SWP_FRAMECHANGED);

	if (win_maximized && !Args->CheckParm("-0"))
		ShowWindow(mainwindow.GetHandle(), SW_MAXIMIZE);
}

//==========================================================================
//
// 
//
//==========================================================================

void SystemBaseFrameBuffer::SetWindowSize(int w, int h)
{
	if (w < 0 || h < 0)
	{
		RestoreWindowedPos();
	}
	else
	{
		LONG style = WS_VISIBLE | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW;
		LONG exStyle = WS_EX_WINDOWEDGE;
		SetWindowLong(mainwindow.GetHandle(), GWL_STYLE, style);
		SetWindowLong(mainwindow.GetHandle(), GWL_EXSTYLE, exStyle);

		int winx, winy, winw, winh, scrwidth, scrheight;

		RECT r = { 0, 0, w, h };
		AdjustWindowRectEx(&r, style, false, exStyle);
		w = int(r.right - r.left);
		h = int(r.bottom - r.top);

		GetCenteredPos(w, h, winx, winy, winw, winh, scrwidth, scrheight);

		// Just move to (0,0) if we were run with the -0 option.
		if (Args->CheckParm("-0"))
		{
			winx = winy = 0;
		}
		else
		{
			KeepWindowOnScreen(winx, winy, winw, winh, scrwidth, scrheight);
		}

		if (!vid_fullscreen)
		{
			ShowWindow(mainwindow.GetHandle(), SW_SHOWNORMAL);
			SetWindowPos(mainwindow.GetHandle(), nullptr, winx, winy, winw, winh, SWP_NOZORDER | SWP_FRAMECHANGED);
			win_maximized = false;
			SetSize(GetClientWidth(), GetClientHeight());
			SaveWindowedPos();
		}
		else
		{
			win_x = winx;
			win_y = winy;
			win_w = winw;
			win_h = winh;
			win_maximized = false;
			vid_fullscreen = false;
		}
	}
}

//==========================================================================
//
// 
//
//==========================================================================

void SystemBaseFrameBuffer::PositionWindow(bool fullscreen, bool initialcall)
{
	RECT r;
	LONG style, exStyle;

	RECT monRect = {};

	if (!m_Fullscreen && fullscreen && !initialcall) SaveWindowedPos();
	if (m_Monitor)
	{
		MONITORINFOEXA mi;
		mi.cbSize = sizeof mi;

		if (GetMonitorInfoA(HMONITOR(m_Monitor), &mi))
		{
			strcpy(m_displayDeviceNameBuffer, mi.szDevice);
			m_displayDeviceName = m_displayDeviceNameBuffer;
			monRect = mi.rcMonitor;

			// Set the default windowed size if not specified yet.
			if (win_w < 0 || win_h < 0)
			{
				win_w = int(monRect.right - monRect.left) * 8 / 10;
				win_h = int(monRect.bottom - monRect.top) * 8 / 10;
			}
		}
	}

	ShowWindow(mainwindow.GetHandle(), SW_SHOW);

	GetWindowRect(mainwindow.GetHandle(), &r);
	style = WS_VISIBLE | WS_CLIPSIBLINGS;
	exStyle = 0;

	if (fullscreen)
		style |= WS_POPUP;
	else
	{
		style |= WS_OVERLAPPEDWINDOW;
		exStyle |= WS_EX_WINDOWEDGE;
	}

	SetWindowLong(mainwindow.GetHandle(), GWL_STYLE, style);
	SetWindowLong(mainwindow.GetHandle(), GWL_EXSTYLE, exStyle);

	if (fullscreen)
	{
		SetWindowPos(mainwindow.GetHandle(), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
		MoveWindow(mainwindow.GetHandle(), monRect.left, monRect.top, monRect.right-monRect.left, monRect.bottom-monRect.top, FALSE);

		// And now, seriously, it IS in the right place. Promise.
	}
	else
	{
		RestoreWindowedPos();
		// This doesn't restore the window size properly so we must force a set size the next tic.
		if (m_Fullscreen)
		{
			::vid_fullscreen = false;
		}

	}
	m_Fullscreen = fullscreen;
	SetSize(GetClientWidth(), GetClientHeight());
}

//==========================================================================
//
// 
//
//==========================================================================

SystemBaseFrameBuffer::SystemBaseFrameBuffer(void *hMonitor, bool fullscreen) : DFrameBuffer(vid_defwidth, vid_defheight)
{
	m_Monitor = hMonitor;
	m_displayDeviceName = 0;
	PositionWindow(fullscreen, true);

	HDC hDC = GetDC(mainwindow.GetHandle());

	ReleaseDC(mainwindow.GetHandle(), hDC);
}

//==========================================================================
//
// 
//
//==========================================================================

SystemBaseFrameBuffer::~SystemBaseFrameBuffer()
{
	if (!m_Fullscreen) SaveWindowedPos();

	ShowWindow (mainwindow.GetHandle(), SW_SHOW);
	SetWindowLong(mainwindow.GetHandle(), GWL_STYLE, WS_VISIBLE | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW);
	SetWindowLong(mainwindow.GetHandle(), GWL_EXSTYLE, WS_EX_WINDOWEDGE);
	SetWindowPos(mainwindow.GetHandle(), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

	static_cast<Win32BaseVideo *>(Video)->Shutdown();
}


//==========================================================================
//
// 
//
//==========================================================================

bool SystemBaseFrameBuffer::IsFullscreen()
{
	return m_Fullscreen;
}

//==========================================================================
//
// 
//
//==========================================================================

void SystemBaseFrameBuffer::ToggleFullscreen(bool yes)
{
	PositionWindow(yes);
}

//==========================================================================
//
// 
//
//==========================================================================

int SystemBaseFrameBuffer::GetClientWidth()
{
	RECT rect = { 0 };
	GetClientRect(mainwindow.GetHandle(), &rect);
	return rect.right - rect.left;
}

int SystemBaseFrameBuffer::GetClientHeight()
{
	RECT rect = { 0 };
	GetClientRect(mainwindow.GetHandle(), &rect);
	return rect.bottom - rect.top;
}
