/*
 * Copyright © 2010 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Author: Kristian Høgsberg <krh@bitplanet.net>
 */

/**
 * \file egl-util.c
 * Common framework for EGL tests.
 *
 * \author Kristian Høgsberg <krh@bitplanet.net>
 */

#include <X11/XKBlib.h>
#include "piglit-util-gl.h"
#include "piglit-util-egl.h"
#include "egl-util.h"

int depth;

int
egl_probe_front_pixel_rgb(struct egl_state *state,
			  int x, int y, const float *expected)
{
	XImage *ximage = XGetImage(state->dpy, state->win,
				   x, state->height - y - 1, 1, 1, AllPlanes, ZPixmap);
	unsigned long pixel = XGetPixel(ximage, 0, 0);
	uint8_t *probe = (uint8_t *)&pixel;
	int pass = 1;
	int i;

	XDestroyImage(ximage);

	/* NB: XGetPixel returns a normalized BGRA, byte per
	 * component, pixel format */
	for(i = 0; i < 3; ++i) {
		if (fabs(probe[2 - i]/255.0 - expected[i]) > piglit_tolerance[i]) {
			pass = 0;
		}
	}

	if (pass)
		return 1;

	printf("Front Buffer Probe at (%i,%i)\n", x, y);
	printf("  Expected: %f %f %f %f\n", expected[0], expected[1], expected[2], expected[3]);
	printf("  Observed: %f %f %f %f\n", probe[0]/255.0, probe[1]/255.0, probe[2]/255.0, probe[3]/255.0);

	return 0;
}

void
egl_init_test(struct egl_test *test)
{
	static const char *no_extensions[] = { NULL };

	test->context_attribs = NULL;
	test->config_attribs = egl_default_attribs;
	test->surface_attribs = NULL;
	test->draw = NULL;
	test->extensions = no_extensions;
	test->window_width = egl_default_window_width;
	test->window_height = egl_default_window_height;
	test->stop_on_failure = true;
}

EGLNativePixmapType
egl_util_create_native_pixmap(struct egl_state *state, int width, int height)
{
	return XCreatePixmap(state->dpy, state->win,
			     width, height, state->depth);
}

EGLSurface
egl_util_create_pixmap(struct egl_state *state,
		       int width, int height, const EGLint *attribs)
{
	EGLNativePixmapType pixmap;
	EGLSurface surf;

	pixmap = egl_util_create_native_pixmap(state, width, height);

	surf = eglCreatePixmapSurface(state->egl_dpy, state->cfg,
				      pixmap, attribs);

	return surf;
}

static enum piglit_result
create_window(struct egl_state *state)
{
	XSetWindowAttributes window_attr;
	XVisualInfo template, *vinfo;
	EGLint id;
	unsigned long mask;
	int screen = DefaultScreen(state->dpy);
	Window root_win = RootWindow(state->dpy, screen);
	int count;

	if (!eglGetConfigAttrib(state->egl_dpy,
				state->cfg, EGL_NATIVE_VISUAL_ID, &id)) {
		fprintf(stderr, "eglGetConfigAttrib() failed\n");
		return PIGLIT_FAIL;
	}

	template.visualid = id;
	vinfo = XGetVisualInfo(state->dpy, VisualIDMask, &template, &count);
	if (count != 1) {
		fprintf(stderr, "XGetVisualInfo() failed\n");
		if (vinfo)
			XFree(vinfo);
		return PIGLIT_FAIL;
	}

	state->depth = vinfo->depth;
	window_attr.background_pixel = 0;
	window_attr.border_pixel = 0;
	window_attr.colormap = XCreateColormap(state->dpy, root_win,
					       vinfo->visual, AllocNone);
	window_attr.event_mask =
		StructureNotifyMask | ExposureMask | KeyPressMask;
	mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
	state->win = XCreateWindow(state->dpy, root_win, 0, 0,
				   state->width, state->height,
				   0, vinfo->depth, InputOutput,
				   vinfo->visual, mask, &window_attr);

	/* Some tests expect the window size to be what they set. It is really
	 * up to the window manager to decide the size, but we can at least
	 * try to ask it to do what we want.
	 */
	XSetWMNormalHints(state->dpy, state->win, &(XSizeHints){
		.flags = PSize | PMinSize | PMaxSize | PBaseSize,
		.width = state->width,
		.height = state->height,
		.min_width = state->width,
		.min_height = state->height,
		.max_width = state->width,
		.max_height = state->height,
		.base_width = state->width,
		.base_height = state->height,
	});

	XMapWindow(state->dpy, state->win);

	XFree(vinfo);
	return PIGLIT_PASS;
}

static enum piglit_result
event_loop(struct egl_state *state, const struct egl_test *test)
{
	XEvent event;
	enum piglit_result result = PIGLIT_FAIL;

	while (1) {
		XNextEvent (state->dpy, &event);

		if (event.type == Expose) {
			result = test->draw(state);
			if (piglit_automatic)
				break;
		}

		if (event.type == KeyPress) {
			KeySym sym = XkbKeycodeToKeysym (state->dpy,
							 event.xkey.keycode,
							 0, 0);

			if (sym == XK_Escape || sym == XK_q || sym == XK_Q)
				break;
		}
	}

	return result;
}

static void
check_extensions(struct egl_state *state, const struct egl_test *test)
{
	int i;

	for (i = 0; test->extensions[i]; i++)
		piglit_require_egl_extension(state->egl_dpy,
					     test->extensions[i]);
}

enum piglit_result
egl_util_run(const struct egl_test *test, int argc, char *argv[])
{
	struct egl_state state = { 0 };
	EGLint count;
	enum piglit_result result = PIGLIT_PASS;
	int i, dispatch_api, api_bit = ~0;

	EGLint nCtxAttribs = 0;
	EGLint ctxAttribs[40];

	for (i = 1; i < argc; ++i) {
		if (!strcmp(argv[i], "-auto"))
			piglit_automatic = 1;
		else
			fprintf(stderr, "Unknown option: %s\n", argv[i]);
	}

	state.dpy = XOpenDisplay(NULL);
	if (state.dpy == NULL) {
		fprintf(stderr, "couldn't open display\n");
		piglit_report_result(PIGLIT_SKIP);
	}

	/* read api_bit if EGL_RENDERABLE_TYPE set in the attribs */
	for (count = 0; test->config_attribs[count] != EGL_NONE; count += 2) {
		if (test->config_attribs[count] == EGL_RENDERABLE_TYPE) {
			api_bit = test->config_attribs[count+1];
		}
	}

	/* choose dispatch_api and set ctx version to ctxAttribs if using ES */
	switch (api_bit) {
	case EGL_OPENGL_ES_BIT:
		dispatch_api = PIGLIT_DISPATCH_ES1;
		ctxAttribs[nCtxAttribs++] = EGL_CONTEXT_CLIENT_VERSION;
		ctxAttribs[nCtxAttribs++] = 1;
		break;
	case EGL_OPENGL_ES2_BIT:
		dispatch_api = PIGLIT_DISPATCH_ES2;
		ctxAttribs[nCtxAttribs++] = EGL_CONTEXT_CLIENT_VERSION;
		ctxAttribs[nCtxAttribs++] = 2;
		break;
	case EGL_OPENGL_BIT:
		dispatch_api = PIGLIT_DISPATCH_GL;
		break;
	default:
		fprintf(stderr, "Note: No EGL_RENDERABLE_TYPE defined in test attributes, defaulted to gl\n");
		dispatch_api = PIGLIT_DISPATCH_GL;
	}


	state.egl_dpy = piglit_egl_get_display(EGL_PLATFORM_X11_EXT, state.dpy);
	if (state.egl_dpy == EGL_NO_DISPLAY) {
		fprintf(stderr, "piglit_egl_get_display() failed\n");
		result = PIGLIT_FAIL;
		goto fail;
	}

	if (!eglInitialize(state.egl_dpy, &state.major, &state.minor)) {
		fprintf(stderr, "eglInitialize() failed\n");
		result = PIGLIT_FAIL;
		goto fail;
	}

	/* bind chosen API and set ctxattribs if using ES */
	if (api_bit == EGL_OPENGL_BIT)
		eglBindAPI(EGL_OPENGL_API);
	else
		eglBindAPI(EGL_OPENGL_ES_API);

	check_extensions(&state, test);

	if (!eglChooseConfig(state.egl_dpy, test->config_attribs, &state.cfg, 1, &count) ||
	    count == 0) {
		fprintf(stderr, "eglChooseConfig() failed\n");
		result = PIGLIT_FAIL;
		goto fail;
	}

	for (int i = 0; test->context_attribs && test->context_attribs[i] != EGL_NONE; i++)
		ctxAttribs[nCtxAttribs++] = test->context_attribs[i];

	ctxAttribs[nCtxAttribs++] = EGL_NONE;
	assert(nCtxAttribs < ARRAY_SIZE(ctxAttribs));

	state.ctx = eglCreateContext(state.egl_dpy, state.cfg,
				     EGL_NO_CONTEXT, ctxAttribs);
	if (state.ctx == EGL_NO_CONTEXT) {
		fprintf(stderr, "eglCreateContext() failed\n");
		result = PIGLIT_FAIL;
		goto fail;
	}

	state.width = test->window_width;
	state.height = test->window_height;
	result = create_window(&state);
	if (result != PIGLIT_PASS)
		goto fail;

	state.surf = eglCreateWindowSurface(state.egl_dpy,
					    state.cfg, state.win,
					    test->surface_attribs);
	if (state.surf == EGL_NO_SURFACE) {
		fprintf(stderr, "eglCreateWindowSurface() failed\n");
		result = PIGLIT_FAIL;
		goto fail;
	}

	if (!eglMakeCurrent(state.egl_dpy,
			    state.surf, state.surf, state.ctx)) {
		fprintf(stderr, "eglMakeCurrent() failed\n");
		result = PIGLIT_FAIL;
		goto fail;
	}

	piglit_dispatch_default_init(dispatch_api);

	result = event_loop(&state, test);

fail:
	if (state.egl_dpy) {
		eglMakeCurrent(state.egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
		eglDestroyContext(state.egl_dpy, state.ctx);
	}
	if (state.win)
		XDestroyWindow(state.dpy, state.win);
	if (state.egl_dpy)
		eglTerminate(state.egl_dpy);
	if (test->stop_on_failure)
		piglit_report_result(result);
	return result;
}
