/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Win32 EGL native display factory
 *//*--------------------------------------------------------------------*/

#include "tcuWin32EGLNativeDisplayFactory.hpp"

#include "egluDefs.hpp"
#include "tcuWin32Window.hpp"
#include "tcuWin32API.h"
#include "tcuTexture.hpp"
#include "deMemory.h"
#include "deThread.h"
#include "deClock.h"
#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"

// Assume no call translation is needed
DE_STATIC_ASSERT(sizeof(eglw::EGLNativeDisplayType) == sizeof(HDC));
DE_STATIC_ASSERT(sizeof(eglw::EGLNativePixmapType) == sizeof(HBITMAP));
DE_STATIC_ASSERT(sizeof(eglw::EGLNativeWindowType) == sizeof(HWND));

namespace tcu
{
namespace win32
{
namespace
{

using namespace eglw;

enum
{
	DEFAULT_SURFACE_WIDTH		= 400,
	DEFAULT_SURFACE_HEIGHT		= 300,
	WAIT_WINDOW_VISIBLE_MS		= 500	//!< Time to wait before issuing screenshot after changing window visibility (hack for DWM)
};

static const eglu::NativeDisplay::Capability	DISPLAY_CAPABILITIES	= eglu::NativeDisplay::CAPABILITY_GET_DISPLAY_LEGACY;
static const eglu::NativePixmap::Capability		BITMAP_CAPABILITIES		= eglu::NativePixmap::CAPABILITY_CREATE_SURFACE_LEGACY;
static const eglu::NativeWindow::Capability		WINDOW_CAPABILITIES		= (eglu::NativeWindow::Capability)
																		   (eglu::NativeWindow::CAPABILITY_CREATE_SURFACE_LEGACY	|
																		    eglu::NativeWindow::CAPABILITY_GET_SURFACE_SIZE			|
																		    eglu::NativeWindow::CAPABILITY_GET_SCREEN_SIZE			|
																			eglu::NativeWindow::CAPABILITY_READ_SCREEN_PIXELS		|
																		    eglu::NativeWindow::CAPABILITY_SET_SURFACE_SIZE			|
																			eglu::NativeWindow::CAPABILITY_CHANGE_VISIBILITY);

class NativeDisplay : public eglu::NativeDisplay
{
public:
									NativeDisplay			(void);
	virtual							~NativeDisplay			(void) {}

	virtual EGLNativeDisplayType	getLegacyNative			(void)			{ return m_deviceContext;	}
	const eglw::Library&			getLibrary				(void) const	{ return m_library;			}

	HDC								getDeviceContext		(void)			{ return m_deviceContext;	}

private:
	HDC								m_deviceContext;
	eglw::DefaultLibrary			m_library;
};

class NativePixmapFactory : public eglu::NativePixmapFactory
{
public:
								NativePixmapFactory		(void);
								~NativePixmapFactory	(void) {}

	virtual eglu::NativePixmap*	createPixmap			(eglu::NativeDisplay* nativeDisplay, int width, int height) const;
	virtual eglu::NativePixmap*	createPixmap			(eglu::NativeDisplay* nativeDisplay, EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, int width, int height) const;
};

class NativePixmap : public eglu::NativePixmap
{
public:
								NativePixmap			(NativeDisplay* nativeDisplay, int width, int height, int bitDepth);
	virtual						~NativePixmap			(void);

	EGLNativePixmapType			getLegacyNative			(void) { return m_bitmap; }

private:
	HBITMAP						m_bitmap;
};

class NativeWindowFactory : public eglu::NativeWindowFactory
{
public:
								NativeWindowFactory		(HINSTANCE instance);
	virtual						~NativeWindowFactory	(void) {}

	virtual eglu::NativeWindow*	createWindow			(eglu::NativeDisplay* nativeDisplay, const eglu::WindowParams& params) const;

private:
	const HINSTANCE				m_instance;
};

class NativeWindow : public eglu::NativeWindow
{
public:
									NativeWindow			(NativeDisplay* nativeDisplay, HINSTANCE instance, const eglu::WindowParams& params);
	virtual							~NativeWindow			(void);

	EGLNativeWindowType				getLegacyNative			(void) { return m_window.getHandle(); }
	virtual IVec2					getSurfaceSize			(void) const;
	virtual IVec2					getScreenSize			(void) const { return getSurfaceSize(); }
	virtual void					processEvents			(void);
	virtual void					setSurfaceSize			(IVec2 size);
	virtual void					setVisibility			(eglu::WindowParams::Visibility visibility);
	virtual void					readScreenPixels		(tcu::TextureLevel* dst) const;

private:
	win32::Window					m_window;
	eglu::WindowParams::Visibility	m_curVisibility;
	deUint64						m_setVisibleTime;		//!< Time window was set visible.
};

// NativeDisplay

NativeDisplay::NativeDisplay (void)
	: eglu::NativeDisplay	(DISPLAY_CAPABILITIES)
	, m_deviceContext		((HDC)EGL_DEFAULT_DISPLAY)
	, m_library				("libEGL.dll")
{
}

// NativePixmap

NativePixmap::NativePixmap (NativeDisplay* nativeDisplay, int width, int height, int bitDepth)
	: eglu::NativePixmap	(BITMAP_CAPABILITIES)
	, m_bitmap				(DE_NULL)
{
	const HDC		deviceCtx	= nativeDisplay->getDeviceContext();
	BITMAPINFO		bitmapInfo;

	memset(&bitmapInfo, 0, sizeof(bitmapInfo));

	if (bitDepth != 24 && bitDepth != 32)
		throw NotSupportedError("Unsupported pixmap bit depth", DE_NULL, __FILE__, __LINE__);

	bitmapInfo.bmiHeader.biSize				= sizeof(bitmapInfo);
	bitmapInfo.bmiHeader.biWidth			= width;
	bitmapInfo.bmiHeader.biHeight			= height;
	bitmapInfo.bmiHeader.biPlanes			= 1;
	bitmapInfo.bmiHeader.biBitCount			= bitDepth;
	bitmapInfo.bmiHeader.biCompression		= BI_RGB;
	bitmapInfo.bmiHeader.biSizeImage		= 0;
	bitmapInfo.bmiHeader.biXPelsPerMeter	= 1;
	bitmapInfo.bmiHeader.biYPelsPerMeter	= 1;
	bitmapInfo.bmiHeader.biClrUsed			= 0;
	bitmapInfo.bmiHeader.biClrImportant		= 0;

	void* bitmapPtr = DE_NULL;
	m_bitmap = CreateDIBSection(deviceCtx, &bitmapInfo, DIB_RGB_COLORS, &bitmapPtr, NULL, 0);

	if (!m_bitmap)
		throw ResourceError("Failed to create bitmap", DE_NULL, __FILE__, __LINE__);
}

NativePixmap::~NativePixmap (void)
{
	DeleteObject(m_bitmap);
}

// NativePixmapFactory

NativePixmapFactory::NativePixmapFactory (void)
	: eglu::NativePixmapFactory	("bitmap", "Win32 Bitmap", BITMAP_CAPABILITIES)
{
}

eglu::NativePixmap* NativePixmapFactory::createPixmap (eglu::NativeDisplay* nativeDisplay, EGLDisplay display, EGLConfig config, const EGLAttrib* attribList, int width, int height) const
{
	const Library&	egl			= nativeDisplay->getLibrary();
	int				redBits		= 0;
	int				greenBits	= 0;
	int				blueBits	= 0;
	int				alphaBits	= 0;
	int				bitSum		= 0;

	DE_ASSERT(display != EGL_NO_DISPLAY);

	egl.getConfigAttrib(display, config, EGL_RED_SIZE,		&redBits);
	egl.getConfigAttrib(display, config, EGL_GREEN_SIZE,	&greenBits);
	egl.getConfigAttrib(display, config, EGL_BLUE_SIZE,		&blueBits);
	egl.getConfigAttrib(display, config, EGL_ALPHA_SIZE,	&alphaBits);
	EGLU_CHECK_MSG(egl, "eglGetConfigAttrib()");

	bitSum = redBits+greenBits+blueBits+alphaBits;

	return new NativePixmap(dynamic_cast<NativeDisplay*>(nativeDisplay), width, height, bitSum);
}

eglu::NativePixmap* NativePixmapFactory::createPixmap (eglu::NativeDisplay* nativeDisplay, int width, int height) const
{
	const int defaultDepth = 32;
	return new NativePixmap(dynamic_cast<NativeDisplay*>(nativeDisplay), width, height, defaultDepth);
}

// NativeWindowFactory

NativeWindowFactory::NativeWindowFactory (HINSTANCE instance)
	: eglu::NativeWindowFactory	("window", "Win32 Window", WINDOW_CAPABILITIES)
	, m_instance				(instance)
{
}

eglu::NativeWindow* NativeWindowFactory::createWindow (eglu::NativeDisplay* nativeDisplay, const eglu::WindowParams& params) const
{
	return new NativeWindow(dynamic_cast<NativeDisplay*>(nativeDisplay), m_instance, params);
}

// NativeWindow

NativeWindow::NativeWindow (NativeDisplay* nativeDisplay, HINSTANCE instance, const eglu::WindowParams& params)
	: eglu::NativeWindow	(WINDOW_CAPABILITIES)
	, m_window				(instance,
							 params.width	== eglu::WindowParams::SIZE_DONT_CARE ? DEFAULT_SURFACE_WIDTH	: params.width,
							 params.height	== eglu::WindowParams::SIZE_DONT_CARE ? DEFAULT_SURFACE_HEIGHT	: params.height)
	, m_curVisibility		(eglu::WindowParams::VISIBILITY_HIDDEN)
	, m_setVisibleTime		(0)
{
	if (params.visibility != eglu::WindowParams::VISIBILITY_DONT_CARE)
		setVisibility(params.visibility);
}

void NativeWindow::setVisibility (eglu::WindowParams::Visibility visibility)
{
	switch (visibility)
	{
		case eglu::WindowParams::VISIBILITY_HIDDEN:
			m_window.setVisible(false);
			m_curVisibility		= visibility;
			break;

		case eglu::WindowParams::VISIBILITY_VISIBLE:
		case eglu::WindowParams::VISIBILITY_FULLSCREEN:
			// \todo [2014-03-12 pyry] Implement FULLSCREEN, or at least SW_MAXIMIZE.
			m_window.setVisible(true);
			m_curVisibility		= eglu::WindowParams::VISIBILITY_VISIBLE;
			m_setVisibleTime	= deGetMicroseconds();
			break;

		default:
			DE_ASSERT(DE_FALSE);
	}
}

NativeWindow::~NativeWindow (void)
{
}

IVec2 NativeWindow::getSurfaceSize (void) const
{
	return m_window.getSize();
}

void NativeWindow::processEvents (void)
{
	m_window.processEvents();
}

void NativeWindow::setSurfaceSize (IVec2 size)
{
	m_window.setSize(size.x(), size.y());
}

void NativeWindow::readScreenPixels (tcu::TextureLevel* dst) const
{
	HDC			windowDC	= DE_NULL;
	HDC			screenDC	= DE_NULL;
	HDC			tmpDC		= DE_NULL;
	HBITMAP		tmpBitmap	= DE_NULL;
	RECT		rect;

	TCU_CHECK_INTERNAL(m_curVisibility != eglu::WindowParams::VISIBILITY_HIDDEN);

	// Hack for DWM: There is no way to wait for DWM animations to finish, so we just have to wait
	// for a while before issuing screenshot if window was just made visible.
	{
		const deInt64 timeSinceVisibleUs = (deInt64)(deGetMicroseconds()-m_setVisibleTime);

		if (timeSinceVisibleUs < (deInt64)WAIT_WINDOW_VISIBLE_MS*1000)
			deSleep(WAIT_WINDOW_VISIBLE_MS - (deUint32)(timeSinceVisibleUs/1000));
	}

	TCU_CHECK(GetClientRect(m_window.getHandle(), &rect));

	try
	{
		const int			width		= rect.right - rect.left;
		const int			height		= rect.bottom - rect.top;
		BITMAPINFOHEADER	bitmapInfo;

		deMemset(&bitmapInfo, 0, sizeof(bitmapInfo));

		screenDC = GetDC(DE_NULL);
		TCU_CHECK(screenDC);

		windowDC = GetDC(m_window.getHandle());
		TCU_CHECK(windowDC);

		tmpDC = CreateCompatibleDC(screenDC);
		TCU_CHECK(tmpDC != DE_NULL);

		MapWindowPoints(m_window.getHandle(), DE_NULL, (LPPOINT)&rect, 2);

		tmpBitmap = CreateCompatibleBitmap(screenDC, width, height);
		TCU_CHECK(tmpBitmap != DE_NULL);

		TCU_CHECK(SelectObject(tmpDC, tmpBitmap) != DE_NULL);

		TCU_CHECK(BitBlt(tmpDC, 0, 0, width, height, screenDC, rect.left, rect.top, SRCCOPY));


		bitmapInfo.biSize			= sizeof(BITMAPINFOHEADER);
		bitmapInfo.biWidth			= width;
		bitmapInfo.biHeight			= -height;
		bitmapInfo.biPlanes			= 1;
		bitmapInfo.biBitCount		= 32;
		bitmapInfo.biCompression	= BI_RGB;
		bitmapInfo.biSizeImage		= 0;
		bitmapInfo.biXPelsPerMeter	= 0;
		bitmapInfo.biYPelsPerMeter	= 0;
		bitmapInfo.biClrUsed		= 0;
		bitmapInfo.biClrImportant	= 0;

		dst->setStorage(TextureFormat(TextureFormat::BGRA, TextureFormat::UNORM_INT8), width, height);

		TCU_CHECK(GetDIBits(screenDC, tmpBitmap, 0, height, dst->getAccess().getDataPtr(), (BITMAPINFO*)&bitmapInfo, DIB_RGB_COLORS));

		DeleteObject(tmpBitmap);
		tmpBitmap = DE_NULL;

		ReleaseDC(DE_NULL, screenDC);
		screenDC = DE_NULL;

		ReleaseDC(m_window.getHandle(), windowDC);
		windowDC = DE_NULL;

		DeleteDC(tmpDC);
		tmpDC = DE_NULL;
	}
	catch (...)
	{
		if (screenDC)
			ReleaseDC(DE_NULL, screenDC);

		if (windowDC)
			ReleaseDC(m_window.getHandle(), windowDC);

		if (tmpBitmap)
			DeleteObject(tmpBitmap);

		if (tmpDC)
			DeleteDC(tmpDC);

		throw;
	}
}

} // anonymous

EGLNativeDisplayFactory::EGLNativeDisplayFactory (HINSTANCE instance)
	: eglu::NativeDisplayFactory	("win32", "Native Win32 Display", DISPLAY_CAPABILITIES)
	, m_instance					(instance)
{
	m_nativeWindowRegistry.registerFactory(new NativeWindowFactory(m_instance));
	m_nativePixmapRegistry.registerFactory(new NativePixmapFactory());
}

EGLNativeDisplayFactory::~EGLNativeDisplayFactory (void)
{
}

eglu::NativeDisplay* EGLNativeDisplayFactory::createDisplay (const EGLAttrib* attribList) const
{
	DE_UNREF(attribList);
	return new NativeDisplay();
}

} // win32
} // tcu