/*
 *  ppui/win32/DisplayDevice_WIN32.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "DisplayDevice_WIN32.h"
#include "Graphics.h"
#include "WaitWindow_WIN32.h"
#include "PPSystem.h"

PPDisplayDevice::PPDisplayDevice(HWND hWnd, pp_int32 width, pp_int32 height, pp_int32 scaleFactor/* = 1*/) :
	PPDisplayDeviceBase(width, height, scaleFactor),
	m_waitWindowVisible(false),
	m_hThread(NULL),
	m_threadID(0)
{
	m_BitmapInfo.biSize = sizeof(BITMAPINFOHEADER);
	m_BitmapInfo.biWidth = getSize().width;
	m_BitmapInfo.biHeight = -getSize().height;
	m_BitmapInfo.biPlanes = 1;
	m_BitmapInfo.biBitCount = 24;
	m_BitmapInfo.biCompression = BI_RGB;
	m_BitmapInfo.biSizeImage = 0;
	m_BitmapInfo.biXPelsPerMeter = 0;
	m_BitmapInfo.biYPelsPerMeter = 0;
	m_BitmapInfo.biClrUsed = 0;
	m_BitmapInfo.biClrImportant = 0;
	
	HDC hScreenDC = ::GetWindowDC(NULL);
	
	m_hBitmap = ::CreateDIBSection(hScreenDC, 
								   (LPBITMAPINFO)&m_BitmapInfo, 
								   DIB_RGB_COLORS,
								   (LPVOID *)&m_pBits, 
								   NULL, 
								   0); 
	
	::ReleaseDC(NULL,hScreenDC);				    

	m_hWnd = hWnd;

	m_hDC = NULL;

	// initialise graphics instance
	currentGraphics = new PPGraphics_BGR24(getSize().width, getSize().height, (getSize().width * 24) / 8, m_pBits);
	currentGraphics->lock = true;
}

PPDisplayDevice::~PPDisplayDevice()
{	
	delete currentGraphics;
}

PPGraphicsAbstract* PPDisplayDevice::open()
{
	if (!isEnabled())
		return NULL;		
		
	currentGraphics->lock = false;

	return currentGraphics;
}

void PPDisplayDevice::blit(HWND hWnd, HDC pDC, pp_int32 x, pp_int32 y, pp_int32 width, pp_int32 height)
{
	RECT r;
 
	GetClientRect(hWnd,&r);
  
	HDC hBitmapDC = ::CreateCompatibleDC(NULL);
	HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hBitmapDC, m_hBitmap);
 
	if (scaleFactor == 1)
		::BitBlt(pDC, x, y, width, height, hBitmapDC, x, y, SRCCOPY);
	else
		::StretchBlt(pDC, x*scaleFactor, y*scaleFactor, width*scaleFactor, height*scaleFactor, hBitmapDC, x, y, width, height, SRCCOPY);

	::SelectObject(hBitmapDC, hOldBitmap);
	::DeleteDC(hBitmapDC);
}

void PPDisplayDevice::close()
{
	currentGraphics->lock = true;
}

void PPDisplayDevice::update()
{
	if (m_hDC != NULL)
		return;
	
	if (!isUpdateAllowed() || !isEnabled())
		return;	

	m_hDC = ::GetDC(m_hWnd);

	blit(m_hWnd, m_hDC, 0, 0, getSize().width, getSize().height);

	::ReleaseDC(m_hWnd, m_hDC);

	m_hDC = NULL;
}

void PPDisplayDevice::update(const PPRect& r)
{
	if (m_hDC != NULL)
		return;
	
	if (!isUpdateAllowed() || !isEnabled())
		return;	
	
	m_hDC = ::GetDC(m_hWnd);

	blit(m_hWnd, m_hDC, r.x1, r.y1, r.width(), r.height());

	::ReleaseDC(m_hWnd, m_hDC);

	m_hDC = NULL;
}

extern HCURSOR g_cursorStandard;
extern HCURSOR g_cursorResizeWE;
extern HCURSOR g_cursorHand;

void PPDisplayDevice::setTitle(const PPSystemString& title)
{
	::SetWindowText(m_hWnd, title);
}

void PPDisplayDevice::setSize(const PPSize& size)
{
	// Important note: Save this size before the window 
	// is actually resized using SetWindowPos (below)
	this->size = size;

	RECT rc;

	pp_int32 modexs = size.width * scaleFactor +::GetSystemMetrics(SM_CXEDGE) * 2 + 2;
	pp_int32 modeys = size.height * scaleFactor + ::GetSystemMetrics(SM_CYCAPTION) + 2 + ::GetSystemMetrics(SM_CYEDGE) * 2;

	::GetWindowRect(m_hWnd, &rc);

	::SetWindowPos(m_hWnd, 
				   NULL, 
				   rc.left, 
				   rc.top, 
				   modexs, 
				   modeys, 
				   SWP_SHOWWINDOW | SWP_NOZORDER);

}

void PPDisplayDevice::adjustWindowSize()
{
	PPSize size = this->size;
	setSize(size);
}

static BOOL SetFullScreen (HWND hWnd, int w, int h)
{
	if (!::IsWindow(hWnd))
		return FALSE;

	DEVMODE dmScreen;

	int frequency = -1;
	if (::EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmScreen))
	{
		frequency = dmScreen.dmDisplayFrequency;
	}

	dmScreen.dmSize			= sizeof ( DEVMODE );
	dmScreen.dmPelsWidth	= w;
	dmScreen.dmPelsHeight	= h;
	if (frequency != -1)
		dmScreen.dmDisplayFrequency	= frequency;

	dmScreen.dmFields		= DM_PELSWIDTH | DM_PELSHEIGHT | (frequency == -1 ? 0 : DM_DISPLAYFREQUENCY);

	// Try with current frequency first
    if ( ::ChangeDisplaySettings( &dmScreen, CDS_FULLSCREEN | CDS_RESET) == DISP_CHANGE_SUCCESSFUL )
		return TRUE;

	// Try without setting frequency
	dmScreen.dmFields		= DM_PELSWIDTH | DM_PELSHEIGHT;
    if ( ::ChangeDisplaySettings( &dmScreen, CDS_FULLSCREEN | CDS_RESET) == DISP_CHANGE_SUCCESSFUL )
		return TRUE;

	return FALSE;
}

bool PPDisplayDevice::goFullScreen(bool b)
{
	if (b)
	{
		::GetWindowRect(m_hWnd, &m_lastRect);

		if (::SetFullScreen(m_hWnd, size.width * scaleFactor, size.height * scaleFactor))
		{
			m_lastWindowStyle = ::GetWindowLong(m_hWnd, GWL_STYLE);
			m_lastWindowExStyle = ::GetWindowLong(m_hWnd, GWL_EXSTYLE);

			::SetWindowLong(m_hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS);
			::SetWindowLong(m_hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);

			::ShowWindow(m_hWnd, SW_SHOWMAXIMIZED);
		    
            ::SetWindowPos(m_hWnd, HWND_TOPMOST, 
                           0, 
					       0, 
					       0, 0, SWP_NOSIZE);
			
			bFullScreen = true;
		}
		else 
		{
			bFullScreen = false;
			return false;
		}
	}
	else
	{
		::ChangeDisplaySettings(NULL, 0);
		
		::SetWindowLong(m_hWnd, GWL_STYLE, m_lastWindowStyle);
		::SetWindowLong(m_hWnd, GWL_EXSTYLE, m_lastWindowExStyle);

		::SetWindowPos(m_hWnd, 
					   HWND_NOTOPMOST, 
					   m_lastRect.left, 
					   m_lastRect.top, 
					   m_lastRect.right - m_lastRect.left, 
					   m_lastRect.bottom - m_lastRect.top, SWP_SHOWWINDOW | SWP_FRAMECHANGED);

		bFullScreen = false;
	}

	return true;
}

void PPDisplayDevice::shutDown()
{
	::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
}

DWORD WINAPI PPDisplayDevice::UpdateWindowThreadProc(LPVOID lpParameter)
{
	PPDisplayDevice* thisDisplayDevice = (PPDisplayDevice*)lpParameter;

	while (true)
	{
		if (thisDisplayDevice->m_waitWindowVisible)
			WaitWindow::getInstance()->update(); 
	
		System::msleep(100);
	}

	::ExitThread(0);
	return 0;
}


void PPDisplayDevice::signalWaitState(bool b, const PPColor& color)
{
	waitBarColor = color;
	
	if (b)
	{
		RECT bounds;
		::GetWindowRect(m_hWnd, &bounds);

		WaitWindow::getInstance()->move(bounds.left + ((bounds.right-bounds.left) >> 1) - (WaitWindow::getInstance()->getWidth()>>1),
										bounds.top + ((bounds.bottom-bounds.top) >> 1) - (WaitWindow::getInstance()->getHeight()>>1));
		
		WaitWindow::getInstance()->setColor(color);
		
		WaitWindow::getInstance()->show();

		m_waitWindowVisible = true;

		if (!m_hThread)
		{
			m_hThread = CreateThread(NULL, 
								     0, 
									 &UpdateWindowThreadProc, 
								     (void*)this, 
								     0,
								     &m_threadID);
		}
	}
	else
	{
		m_waitWindowVisible = false;
		WaitWindow::getInstance()->hide();
	}
}

void PPDisplayDevice::setMouseCursor(MouseCursorTypes type)
{
	currentCursorType = type;
	
	switch (type)
	{
		case MouseCursorTypeStandard:
			::SetCursor(g_cursorStandard);
			break;
			
		case MouseCursorTypeResizeLeft:
			::SetCursor(g_cursorResizeWE);
			break;

		case MouseCursorTypeResizeRight:
			::SetCursor(g_cursorResizeWE);
			break;

		case MouseCursorTypeHand:
			::SetCursor(g_cursorHand);
			break;
	}
}
