/*-------------------------------------------------------------------------
 * 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 X11Egl Platform.
 *//*--------------------------------------------------------------------*/

#include "tcuX11EglPlatform.hpp"
#include "egluGLContextFactory.hpp"
#include "eglwLibrary.hpp"
#include "eglwFunctions.hpp"
#include "eglwEnums.hpp"

namespace tcu
{
namespace x11
{
namespace egl
{

typedef ::Display*	EGLNativeDisplayType;
typedef ::Pixmap	EGLNativePixmapType;
typedef ::Window	EGLNativeWindowType;

DE_STATIC_ASSERT(sizeof(EGLNativeDisplayType)	<= sizeof(eglw::EGLNativeDisplayType));
DE_STATIC_ASSERT(sizeof(EGLNativePixmapType)	<= sizeof(eglw::EGLNativePixmapType));
DE_STATIC_ASSERT(sizeof(EGLNativeWindowType)	<= sizeof(eglw::EGLNativeWindowType));

extern "C"
{

typedef EGLW_APICALL	eglw::EGLDisplay	(EGLW_APIENTRY* eglX11GetDisplayFunc)			(EGLNativeDisplayType display_id);
typedef EGLW_APICALL	eglw::EGLBoolean	(EGLW_APIENTRY* eglX11CopyBuffersFunc)			(eglw::EGLDisplay dpy, eglw::EGLSurface surface, EGLNativePixmapType target);
typedef EGLW_APICALL	eglw::EGLSurface	(EGLW_APIENTRY* eglX11CreatePixmapSurfaceFunc)	(eglw::EGLDisplay dpy, eglw::EGLConfig config, EGLNativePixmapType pixmap, const eglw::EGLint* attrib_list);
typedef EGLW_APICALL	eglw::EGLSurface	(EGLW_APIENTRY* eglX11CreateWindowSurfaceFunc)	(eglw::EGLDisplay dpy, eglw::EGLConfig config, EGLNativeWindowType win, const eglw::EGLint* attrib_list);

}

using std::string;

using de::MovePtr;
using de::UniquePtr;
using glu::ContextFactory;
using eglu::GLContextFactory;
using eglu::NativeDisplay;
using eglu::NativeDisplayFactory;
using eglu::NativeWindow;
using eglu::NativeWindowFactory;
using eglu::NativePixmap;
using eglu::NativePixmapFactory;
using eglu::WindowParams;
using tcu::TextureLevel;

class Library : public eglw::DefaultLibrary
{
public:
	Library (void)
		: eglw::DefaultLibrary("libEGL.so")
	{
	}

	eglw::EGLBoolean copyBuffers (eglw::EGLDisplay dpy, eglw::EGLSurface surface, eglw::EGLNativePixmapType target) const
	{
		return ((eglX11CopyBuffersFunc)m_egl.copyBuffers)(dpy, surface, reinterpret_cast<EGLNativePixmapType>(target));
	}

	eglw::EGLSurface createPixmapSurface (eglw::EGLDisplay dpy, eglw::EGLConfig config, eglw::EGLNativePixmapType pixmap, const eglw::EGLint *attrib_list) const
	{
		return ((eglX11CreatePixmapSurfaceFunc)m_egl.createPixmapSurface)(dpy, config, reinterpret_cast<EGLNativePixmapType>(pixmap), attrib_list);
	}

	eglw::EGLSurface createWindowSurface (eglw::EGLDisplay dpy, eglw::EGLConfig config, eglw::EGLNativeWindowType win, const eglw::EGLint *attrib_list) const
	{
		return ((eglX11CreateWindowSurfaceFunc)m_egl.createWindowSurface)(dpy, config, reinterpret_cast<EGLNativeWindowType>(win), attrib_list);
	}

	eglw::EGLDisplay getDisplay (eglw::EGLNativeDisplayType display_id) const
	{
		return ((eglX11GetDisplayFunc)m_egl.getDisplay)(reinterpret_cast<EGLNativeDisplayType>(display_id));
	}
};

class Display : public NativeDisplay
{
public:
	static const Capability CAPABILITIES		= Capability(CAPABILITY_GET_DISPLAY_LEGACY |
															 CAPABILITY_GET_DISPLAY_PLATFORM);

								Display				(MovePtr<XlibDisplay> x11Display)
									: NativeDisplay	(CAPABILITIES,
													 EGL_PLATFORM_X11_EXT,
													 "EGL_EXT_platform_x11")
									, m_display		(x11Display) {}

	void*						getPlatformNative		(void)	{ return m_display->getXDisplay(); }
	eglw::EGLNativeDisplayType	getLegacyNative			(void)	{ return reinterpret_cast<eglw::EGLNativeDisplayType>(m_display->getXDisplay()); }

	XlibDisplay&				getX11Display			(void)			{ return *m_display;	}
	const eglw::Library&		getLibrary				(void) const	{ return m_library;		}
	const eglw::EGLAttrib*		getPlatformAttributes	(void) const	{ return DE_NULL;		}

private:
	UniquePtr<XlibDisplay>		m_display;
	Library						m_library;
};

class Window : public NativeWindow
{
public:
	static const Capability	CAPABILITIES		= Capability(CAPABILITY_CREATE_SURFACE_LEGACY |
															 CAPABILITY_CREATE_SURFACE_PLATFORM |
															 CAPABILITY_GET_SURFACE_SIZE |
															 CAPABILITY_SET_SURFACE_SIZE |
															 CAPABILITY_GET_SCREEN_SIZE);

								Window				(Display&				display,
													 const WindowParams&	params,
													 Visual*				visual);

	eglw::EGLNativeWindowType	getLegacyNative		(void) { return reinterpret_cast<eglw::EGLNativeWindowType>(m_window.getXID()); }
	void*						getPlatformNative	(void) { return &m_window.getXID();	}

	IVec2						getSurfaceSize		(void) const;
	void						setSurfaceSize		(IVec2 size);
	IVec2						getScreenSize		(void) const { return getSurfaceSize(); }

private:
	XlibWindow					m_window;
};

Window::Window (Display& display, const WindowParams& params, Visual* visual)
	: NativeWindow	(CAPABILITIES)
	, m_window		(display.getX11Display(), params.width, params.height, visual)
{
	m_window.setVisibility((params.visibility != WindowParams::VISIBILITY_HIDDEN));
}

IVec2 Window::getSurfaceSize (void) const
{
	IVec2 ret;
	m_window.getDimensions(&ret.x(), &ret.y());
	return ret;
}

void Window::setSurfaceSize (IVec2 size)
{
	m_window.setDimensions(size.x(), size.y());
}

class WindowFactory : public NativeWindowFactory
{
public:
						WindowFactory		(void);

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

	NativeWindow*		createWindow		(NativeDisplay*			nativeDisplay,
											 eglw::EGLDisplay		display,
											 eglw::EGLConfig		config,
											 const eglw::EGLAttrib*	attribList,
											 const WindowParams&	params) const;
};

WindowFactory::WindowFactory (void)
	: NativeWindowFactory ("window", "X11 Window", Window::CAPABILITIES)
{
}

NativeWindow* WindowFactory::createWindow (NativeDisplay*		nativeDisplay,
										   const WindowParams&	params) const
{
	Display&	display	= *dynamic_cast<Display*>(nativeDisplay);

	return new Window(display, params, DE_NULL);
}

NativeWindow* WindowFactory::createWindow (NativeDisplay*			nativeDisplay,
										   eglw::EGLDisplay			eglDisplay,
										   eglw::EGLConfig			config,
										   const eglw::EGLAttrib*	attribList,
										   const WindowParams&		params) const
{
	DE_UNREF(attribList);

	Display&		display		= *dynamic_cast<Display*>(nativeDisplay);
	eglw::EGLint	visualID	= 0;
	::Visual*		visual		= DE_NULL;
	nativeDisplay->getLibrary().getConfigAttrib(eglDisplay, config, EGL_NATIVE_VISUAL_ID, &visualID);

	if (visualID != 0)
		visual = display.getX11Display().getVisual(visualID);

	return new Window(display, params, visual);
}

#if 0
class Pixmap : public NativePixmap
{
public:
	enum {
		CAPABILITIES = (CAPABILITY_CREATE_SURFACE_LEGACY |
						CAPABILITY_CREATE_SURFACE_PLATFORM |
						CAPABILITY_READ_PIXELS)
	};

							Pixmap				(MovePtr<x11::Pixmap> x11Pixmap)
								: NativePixmap	(CAPABILITIES)
								, m_pixmap		(x11Pixmap) {}

	void*					getPlatformNative	(void) { return &m_pixmap.getXID(); }
	void					readPixels			(TextureLevel* dst);

private:
	UniquePtr<x11::Pixmap>	m_pixmap;
};

class PixmapFactory : public NativePixmapFactory
{
public:
					PixmapFactory	(void)
						: NativePixmapFactory ("pixmap", "X11 Pixmap", Pixmap::CAPABILITIES) {}

	NativePixmap*	createPixmap	(NativeDisplay* nativeDisplay,
									 int			width,
									 int			height) const;
};

NativePixmap* PixmapFactory::createPixmap (NativeDisplay* nativeDisplay,
										   int			width,
										   int			height) const

{
	Display*				display		= dynamic_cast<Display*>(nativeDisplay);
	MovePtr<x11::Pixmap>	x11Pixmap	(new x11::Pixmap(display->getX11Display(),
														 width, height));
	return new Pixmap(x11Pixmap);
}
#endif

class DisplayFactory : public NativeDisplayFactory
{
public:
						DisplayFactory		(EventState& eventState);

	NativeDisplay*		createDisplay		(const eglw::EGLAttrib* attribList) const;

private:
	EventState&			m_eventState;
};

DisplayFactory::DisplayFactory (EventState& eventState)
	: NativeDisplayFactory	("x11", "Native X11 Display",
							 Display::CAPABILITIES,
							 EGL_PLATFORM_X11_SCREEN_EXT,
							 "EGL_EXT_platform_x11")
	, m_eventState			(eventState)
{
	m_nativeWindowRegistry.registerFactory(new WindowFactory());
	// m_nativePixmapRegistry.registerFactory(new PixmapFactory());
}

NativeDisplay* DisplayFactory::createDisplay (const eglw::EGLAttrib* attribList) const
{
	DE_UNREF(attribList);

	//! \todo [2014-03-18 lauri] Somehow make the display configurable from command line
	MovePtr<XlibDisplay>	x11Display	(new XlibDisplay(m_eventState, DE_NULL));

	return new Display(x11Display);
}

Platform::Platform (EventState& eventState)
{
	m_nativeDisplayFactoryRegistry.registerFactory(new DisplayFactory(eventState));
}

MovePtr<ContextFactory> Platform::createContextFactory (void)
{
	return MovePtr<ContextFactory>(new GLContextFactory(m_nativeDisplayFactoryRegistry));
}


} // egl
} // x11
} // tcu