#include <assert.h>
#include <X11/Xlib.h>
#include <X11/extensions/XvMClib.h>
#include <pipe/p_context.h>
#include <vl_display.h>
#include <vl_screen.h>
#include <vl_context.h>
#include <vl_winsys.h>

static Status Validate
(
	Display *display,
	XvPortID port,
	int surface_type_id,
	unsigned int width,
	unsigned int height,
	int flags,
	int *found_port,
	int *chroma_format,
	int *mc_type
)
{
	unsigned int	found_surface = 0;
	XvAdaptorInfo	*adaptor_info;
	unsigned int	num_adaptors;
	int		num_types;
	unsigned int	max_width, max_height;
	Status		ret;
	unsigned int	i, j, k;

	assert(display && chroma_format);

	*found_port = 0;

	ret = XvQueryAdaptors(display, XDefaultRootWindow(display), &num_adaptors, &adaptor_info);
	if (ret != Success)
		return ret;

	/* Scan through all adaptors looking for this port and surface */
	for (i = 0; i < num_adaptors && !*found_port; ++i)
	{
		/* Scan through all ports of this adaptor looking for our port */
		for (j = 0; j < adaptor_info[i].num_ports && !*found_port; ++j)
		{
			/* If this is our port, scan through all its surfaces looking for our surface */
			if (adaptor_info[i].base_id + j == port)
			{
				XvMCSurfaceInfo *surface_info;

				*found_port = 1;
				surface_info = XvMCListSurfaceTypes(display, adaptor_info[i].base_id, &num_types);

				if (surface_info)
				{
					for (k = 0; k < num_types && !found_surface; ++k)
					{
						if (surface_info[k].surface_type_id == surface_type_id)
						{
							found_surface = 1;
							max_width = surface_info[k].max_width;
							max_height = surface_info[k].max_height;
							*chroma_format = surface_info[k].chroma_format;
							*mc_type = surface_info[k].mc_type;
						}
					}

					XFree(surface_info);
				}
				else
				{
					XvFreeAdaptorInfo(adaptor_info);
					return BadAlloc;
				}
			}
		}
	}

	XvFreeAdaptorInfo(adaptor_info);

	if (!*found_port)
		return XvBadPort;
	if (!found_surface)
		return BadMatch;
	if (width > max_width || height > max_height)
		return BadValue;
	if (flags != XVMC_DIRECT && flags != 0)
		return BadValue;

	return Success;
}

static enum vlProfile ProfileToVL(int xvmc_profile)
{
	if (xvmc_profile & XVMC_MPEG_1)
		assert(0);
	else if (xvmc_profile & XVMC_MPEG_2)
		return vlProfileMpeg2Main;
	else if (xvmc_profile & XVMC_H263)
		assert(0);
	else if (xvmc_profile & XVMC_MPEG_4)
		assert(0);
	else
		assert(0);

	return -1;
}

static enum vlEntryPoint EntryToVL(int xvmc_entry)
{
	return xvmc_entry & XVMC_IDCT ? vlEntryPointIDCT : vlEntryPointMC;
}

static enum vlFormat FormatToVL(int xvmc_format)
{
	switch (xvmc_format)
	{
		case XVMC_CHROMA_FORMAT_420:
			return vlFormatYCbCr420;
		case XVMC_CHROMA_FORMAT_422:
			return vlFormatYCbCr422;
		case XVMC_CHROMA_FORMAT_444:
			return vlFormatYCbCr444;
		default:
			assert(0);
	}

	return -1;
}

Status XvMCCreateContext(Display *display, XvPortID port, int surface_type_id, int width, int height, int flags, XvMCContext *context)
{
	int			found_port;
	int			chroma_format;
	int			mc_type;
	Status			ret;
	struct vlDisplay	*vl_dpy;
	struct vlScreen		*vl_scrn;
	struct vlContext	*vl_ctx;
	struct pipe_context	*pipe;

	assert(display);

	if (!context)
		return XvMCBadContext;

	ret = Validate(display, port, surface_type_id, width, height, flags, &found_port, &chroma_format, &mc_type);

	/* XXX: Success and XvBadPort have the same value */
	if (ret != Success || !found_port)
		return ret;

	/* XXX: Assumes default screen, should check which screen port is on */
	pipe = create_pipe_context(display, XDefaultScreen(display));

	assert(pipe);

	vlCreateDisplay(display, &vl_dpy);
	vlCreateScreen(vl_dpy, XDefaultScreen(display), pipe->screen, &vl_scrn);
	vlCreateContext
	(
		vl_scrn,
		pipe,
		width,
		height,
		FormatToVL(chroma_format),
		ProfileToVL(mc_type),
		EntryToVL(mc_type),
		&vl_ctx
	);

	context->context_id = XAllocID(display);
	context->surface_type_id = surface_type_id;
	context->width = width;
	context->height = height;
	context->flags = flags;
	context->port = port;
	context->privData = vl_ctx;

	return Success;
}

Status XvMCDestroyContext(Display *display, XvMCContext *context)
{
	struct vlContext	*vl_ctx;
	struct vlScreen		*vl_screen;
	struct vlDisplay	*vl_dpy;
	struct pipe_context	*pipe;

	assert(display);

	if (!context)
		return XvMCBadContext;

	vl_ctx = context->privData;

	assert(display == vlGetNativeDisplay(vlGetDisplay(vlContextGetScreen(vl_ctx))));

	pipe = vlGetPipeContext(vl_ctx);
	vl_screen = vlContextGetScreen(vl_ctx);
	vl_dpy = vlGetDisplay(vl_screen);
	vlDestroyContext(vl_ctx);
	vlDestroyScreen(vl_screen);
	vlDestroyDisplay(vl_dpy);
	destroy_pipe_context(pipe);

	return Success;
}