/*-------------------------------------------------------------------------
 * 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 OS X platform.
 *//*--------------------------------------------------------------------*/

#include "tcuOSXPlatform.hpp"
#include "tcuRenderTarget.hpp"

#include "tcuOSXVulkanPlatform.hpp"

#include "gluDefs.hpp"
#include "gluPlatform.hpp"
#include "gluRenderContext.hpp"
#include "gluRenderConfig.hpp"
#include "glwFunctions.hpp"
#include "glwInitFunctions.hpp"
#include "deDynamicLibrary.hpp"
#include "glwEnums.hpp"

#include <string>

#include <OpenGL/OpenGL.h>
#include <OpenGL/CGLCurrent.h>
#include <OpenGL/CGLContext.h>
#include <OpenGL/CGLTypes.h>
#include <OpenGL/CGLRenderers.h>

#define OPENGL_LIBRARY_PATH "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"

namespace tcu
{

class CGLRenderContext : public glu::RenderContext
{
public:
								CGLRenderContext		(const glu::RenderConfig& config);
								~CGLRenderContext		(void);

	glu::ContextType			getType					(void) const { return m_type;			}
	const glw::Functions&		getFunctions			(void) const { return m_functions;		}
	const tcu::RenderTarget&	getRenderTarget			(void) const { return m_renderTarget;	}
	void						postIterate				(void) {}

private:
	const glu::ContextType		m_type;
	CGLContextObj				m_context;
	glw::Functions				m_functions;
	RenderTarget				m_renderTarget;
};

class CGLContextFactory : public glu::ContextFactory
{
public:
	CGLContextFactory (void)
		: glu::ContextFactory("cgl", "CGL Context (surfaceless, use fbo)")
	{
	}

	glu::RenderContext*	createContext (const glu::RenderConfig& config, const tcu::CommandLine&, const glu::RenderContext*) const
	{
		return new CGLRenderContext(config);
	}
};

class OSXGLPlatform : public glu::Platform
{
public:
	OSXGLPlatform(void)
	{
		m_contextFactoryRegistry.registerFactory(new CGLContextFactory());
	}

	~OSXGLPlatform(void) {}
};

class OSXPlatform : public tcu::Platform
{
public:
	OSXPlatform(void)
		: m_gluPlatform(), m_vkPlatform()
	{
	}

	~OSXPlatform(void)
	{
	}

	const glu::Platform&	getGLPlatform	(void) const { return m_gluPlatform; }
	const vk::Platform&		getVulkanPlatform	(void) const { return m_vkPlatform; }

private:
	OSXGLPlatform m_gluPlatform;
	osx::VulkanPlatform m_vkPlatform;
};

namespace
{

class GLFunctionLoader : public glw::FunctionLoader
{
public:
	GLFunctionLoader (const char* path)
		: m_library(path)
	{
	}

	glw::GenericFuncType get (const char* name) const
	{
		return m_library.getFunction(name);
	}

private:
	de::DynamicLibrary m_library;
};

} // anonymous

static CGLOpenGLProfile getCGLProfile (glu::ContextType type)
{
	if (type.getAPI().getProfile() != glu::PROFILE_CORE)
		throw NotSupportedError("Requested OpenGL profile is not supported in CGL");

	if (type.getAPI().getMajorVersion() == 4)
		return kCGLOGLPVersion_GL4_Core;
	else if (type.getAPI().getMajorVersion() == 3)
		return kCGLOGLPVersion_GL3_Core;
	else
		throw NotSupportedError("Requested OpenGL version is not supported in CGL");
}

static glu::ApiType getVersion (const glw::Functions& gl)
{
	int major = 0;
	int minor = 0;
	gl.getIntegerv(GL_MAJOR_VERSION, &major);
	gl.getIntegerv(GL_MINOR_VERSION, &minor);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to query exact GL version");
	return glu::ApiType::core(major, minor);
}

CGLRenderContext::CGLRenderContext (const glu::RenderConfig& config)
	: m_type			(config.type)
	, m_context			(DE_NULL)
	, m_renderTarget	(0, 0, tcu::PixelFormat(0,0,0,0), 0, 0, 0)
{
	try
	{
		const CGLPixelFormatAttribute attribs[] =
		{
			kCGLPFAAccelerated,
			kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)getCGLProfile(config.type),
			(CGLPixelFormatAttribute)0
		};

		CGLPixelFormatObj	pixelFormat;
		int					numVScreens;

		if (CGLChoosePixelFormat(&attribs[0], &pixelFormat, &numVScreens) != kCGLNoError)
			throw NotSupportedError("No compatible pixel formats found");

		try
		{
			if (CGLCreateContext(pixelFormat, DE_NULL, &m_context) != kCGLNoError)
				throw ResourceError("Failed to create CGL context");

			if (CGLSetCurrentContext(m_context) != kCGLNoError)
				throw ResourceError("Failed to set current CGL context");
		}
		catch (...)
		{
			CGLReleasePixelFormat(pixelFormat);
			throw;
		}

		CGLReleasePixelFormat(pixelFormat);

		{
			GLFunctionLoader loader(OPENGL_LIBRARY_PATH);
			glu::initFunctions(&m_functions, &loader, config.type.getAPI());
		}

		{
			const glu::ApiType actualApi = getVersion(m_functions);
			if (!contextSupports(glu::ContextType(actualApi, glu::ContextFlags(0)), config.type.getAPI()))
				throw tcu::NotSupportedError("OpenGL version not supported");
		}
	}
	catch (...)
	{
		if (m_context)
		{
			CGLSetCurrentContext(DE_NULL);
			CGLDestroyContext(m_context);
		}
		throw;
	}
}

CGLRenderContext::~CGLRenderContext (void)
{
	CGLSetCurrentContext(DE_NULL);
	if (m_context)
		CGLDestroyContext(m_context);
}

} // tcu

tcu::Platform* createPlatform (void)
{
	return new tcu::OSXPlatform();
}