
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "egl_tracker.h"

#include "egllog.h"

#include "pipe/p_inlines.h"
#include "pipe/p_screen.h"
#include "pipe/p_context.h"

#include "state_tracker/drm_api.h"

/*
 * Util functions
 */

static drmModeModeInfoPtr
drm_find_mode(drmModeConnectorPtr connector, _EGLMode *mode)
{
	int i;
	drmModeModeInfoPtr m = NULL;

	for (i = 0; i < connector->count_modes; i++) {
		m = &connector->modes[i];
		if (m->hdisplay == mode->Width && m->vdisplay == mode->Height && m->vrefresh == mode->RefreshRate)
			break;
		m = &connector->modes[0]; /* if we can't find one, return first */
	}

	return m;
}

static struct st_framebuffer *
drm_create_framebuffer(const __GLcontextModes *visual,
                       unsigned width,
                       unsigned height,
                       void *priv)
{
	enum pipe_format colorFormat, depthFormat, stencilFormat;

	if (visual->redBits == 5)
		colorFormat = PIPE_FORMAT_R5G6B5_UNORM;
	else
		colorFormat = PIPE_FORMAT_A8R8G8B8_UNORM;

	if (visual->depthBits == 16)
		depthFormat = PIPE_FORMAT_Z16_UNORM;
	else if (visual->depthBits == 24)
		depthFormat = PIPE_FORMAT_S8Z24_UNORM;
	else
		depthFormat = PIPE_FORMAT_NONE;

	if (visual->stencilBits == 8)
		stencilFormat = PIPE_FORMAT_S8Z24_UNORM;
	else
		stencilFormat = PIPE_FORMAT_NONE;

	return st_create_framebuffer(visual,
	                             colorFormat,
	                             depthFormat,
	                             stencilFormat,
	                             width,
	                             height,
	                             priv);
}

static void
drm_create_texture(_EGLDisplay *dpy,
                   struct drm_screen *scrn,
                   unsigned w, unsigned h)
{
	struct drm_device *dev = lookup_drm_device(dpy);
	struct pipe_screen *screen = dev->screen;
	struct pipe_surface *surface;
	struct pipe_texture *texture;
	struct pipe_texture templat;
	struct pipe_buffer *buf = NULL;
	unsigned pitch = 0;

	memset(&templat, 0, sizeof(templat));
	templat.tex_usage = PIPE_TEXTURE_USAGE_RENDER_TARGET;
	templat.tex_usage |= PIPE_TEXTURE_USAGE_PRIMARY;
	templat.target = PIPE_TEXTURE_2D;
	templat.last_level = 0;
	templat.depth[0] = 1;
	templat.format = PIPE_FORMAT_A8R8G8B8_UNORM;
	templat.width[0] = w;
	templat.height[0] = h;
	pf_get_block(templat.format, &templat.block);

	texture = screen->texture_create(dev->screen,
	                                 &templat);

	if (!texture)
		goto err_tex;

	surface = screen->get_tex_surface(screen,
	                                  texture,
	                                  0,
	                                  0,
	                                  0,
	                                  PIPE_BUFFER_USAGE_GPU_WRITE);

	if (!surface)
		goto err_surf;

	scrn->tex = texture;
	scrn->surface = surface;
	scrn->front.width = w;
	scrn->front.height = h;
	scrn->front.pitch = pitch;
	dev->api->local_handle_from_texture(dev->api, screen, texture,
	                                    &scrn->front.pitch, &scrn->front.handle);
	if (0)
		goto err_handle;

	return;

err_handle:
	pipe_surface_reference(&surface, NULL);
err_surf:
	pipe_texture_reference(&texture, NULL);
err_tex:
	pipe_buffer_reference(&buf, NULL);
	return;
}

/*
 * Exported functions
 */

void
drm_takedown_shown_screen(_EGLDisplay *dpy, struct drm_screen *screen)
{
	struct drm_device *dev = lookup_drm_device(dpy);

	screen->surf = NULL;

	drmModeSetCrtc(
		dev->drmFD,
		screen->crtcID,
		0, // FD
		0, 0,
		NULL, 0, // List of output ids
		NULL);

	drmModeRmFB(dev->drmFD, screen->fbID);
	drmModeFreeFB(screen->fb);
	screen->fb = NULL;

	pipe_surface_reference(&screen->surface, NULL);
	pipe_texture_reference(&screen->tex, NULL);
	pipe_buffer_reference(&screen->buffer, NULL);

	screen->shown = 0;
}

_EGLSurface *
drm_create_window_surface(_EGLDriver *drv, _EGLDisplay *dpy, _EGLConfig *conf, NativeWindowType window, const EGLint *attrib_list)
{
	return NULL;
}


_EGLSurface *
drm_create_pixmap_surface(_EGLDriver *drv, _EGLDisplay *dpy, _EGLConfig *conf, NativePixmapType pixmap, const EGLint *attrib_list)
{
	return NULL;
}


_EGLSurface *
drm_create_pbuffer_surface(_EGLDriver *drv, _EGLDisplay *dpy, _EGLConfig *conf,
                           const EGLint *attrib_list)
{
	int i;
	int width = -1;
	int height = -1;
	struct drm_surface *surf = NULL;
	__GLcontextModes *visual;

	for (i = 0; attrib_list && attrib_list[i] != EGL_NONE; i++) {
		switch (attrib_list[i]) {
			case EGL_WIDTH:
				width = attrib_list[++i];
				break;
			case EGL_HEIGHT:
				height = attrib_list[++i];
				break;
			default:
				_eglError(EGL_BAD_ATTRIBUTE, "eglCreatePbufferSurface");
				return EGL_NO_SURFACE;
		}
	}

	if (width < 1 || height < 1) {
		_eglError(EGL_BAD_ATTRIBUTE, "eglCreatePbufferSurface");
		return NULL;
	}

	surf = (struct drm_surface *) calloc(1, sizeof(struct drm_surface));
	if (!surf)
		goto err;

	if (!_eglInitSurface(drv, &surf->base, EGL_PBUFFER_BIT, conf, attrib_list))
		goto err_surf;

	surf->w = width;
	surf->h = height;

	visual = drm_visual_from_config(conf);
	surf->stfb = drm_create_framebuffer(visual,
	                                    width,
	                                    height,
	                                    (void*)surf);
	drm_visual_modes_destroy(visual);

	return &surf->base;

err_surf:
	free(surf);
err:
	return NULL;
}

_EGLSurface *
drm_create_screen_surface_mesa(_EGLDriver *drv, _EGLDisplay *dpy, _EGLConfig *cfg,
                               const EGLint *attrib_list)
{
	EGLSurface surf = drm_create_pbuffer_surface(drv, dpy, cfg, attrib_list);

	return surf;
}

EGLBoolean
drm_show_screen_surface_mesa(_EGLDriver *drv, _EGLDisplay *dpy,
                             _EGLScreen *screen,
                             _EGLSurface *surface, _EGLMode *mode)
{
	struct drm_device *dev = lookup_drm_device(dpy);
	struct drm_surface *surf = lookup_drm_surface(surface);
	struct drm_screen *scrn = lookup_drm_screen(screen);
	int ret;
	unsigned int i, k;

	if (scrn->shown)
		drm_takedown_shown_screen(dpy, scrn);


	drm_create_texture(dpy, scrn, mode->Width, mode->Height);
	if (!scrn->buffer)
		return EGL_FALSE;

	ret = drmModeAddFB(dev->drmFD,
	                   scrn->front.width, scrn->front.height,
	                   32, 32, scrn->front.pitch,
	                   scrn->front.handle,
	                   &scrn->fbID);

	if (ret)
		goto err_bo;

	scrn->fb = drmModeGetFB(dev->drmFD, scrn->fbID);
	if (!scrn->fb)
		goto err_bo;

	/* find a fitting crtc */
	{
		drmModeConnector *con = scrn->connector;

		scrn->mode = drm_find_mode(con, mode);
		if (!scrn->mode)
			goto err_fb;

		for (k = 0; k < con->count_encoders; k++) {
			drmModeEncoder *enc = drmModeGetEncoder(dev->drmFD, con->encoders[k]);
			for (i = 0; i < dev->res->count_crtcs; i++) {
				if (enc->possible_crtcs & (1<<i)) {
					/* save the ID */
					scrn->crtcID = dev->res->crtcs[i];

					/* skip the rest */
					i = dev->res->count_crtcs;
					k = dev->res->count_encoders;
				}
			}
			drmModeFreeEncoder(enc);
		}
	}

	ret = drmModeSetCrtc(dev->drmFD,
	                     scrn->crtcID,
	                     scrn->fbID,
	                     0, 0,
	                     &scrn->connectorID, 1,
	                     scrn->mode);

	if (ret)
		goto err_crtc;


	if (scrn->dpms)
		drmModeConnectorSetProperty(dev->drmFD,
		                            scrn->connectorID,
		                            scrn->dpms->prop_id,
		                            DRM_MODE_DPMS_ON);

	surf->screen = scrn;

	scrn->surf = surf;
	scrn->shown = 1;

	return EGL_TRUE;

err_crtc:
	scrn->crtcID = 0;

err_fb:
	drmModeRmFB(dev->drmFD, scrn->fbID);
	drmModeFreeFB(scrn->fb);
	scrn->fb = NULL;

err_bo:
	pipe_surface_reference(&scrn->surface, NULL);
	pipe_texture_reference(&scrn->tex, NULL);
	pipe_buffer_reference(&scrn->buffer, NULL);

	return EGL_FALSE;
}

EGLBoolean
drm_destroy_surface(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSurface *surface)
{
	struct drm_surface *surf = lookup_drm_surface(surface);
	if (!_eglIsSurfaceBound(&surf->base)) {
		if (surf->screen)
			drm_takedown_shown_screen(dpy, surf->screen);
		st_unreference_framebuffer(surf->stfb);
		free(surf);
	}
	return EGL_TRUE;
}

EGLBoolean
drm_swap_buffers(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSurface *draw)
{
	struct drm_device *dev = lookup_drm_device(dpy);
	struct drm_surface *surf = lookup_drm_surface(draw);
	struct pipe_surface *back_surf;

	if (!surf)
		return EGL_FALSE;

	/* error checking */
	if (!_eglSwapBuffers(drv, dpy, draw))
		return EGL_FALSE;

	st_get_framebuffer_surface(surf->stfb, ST_SURFACE_BACK_LEFT, &back_surf);

	if (back_surf) {

		st_notify_swapbuffers(surf->stfb);

		if (surf->screen) {
			surf->user->pipe->surface_copy(surf->user->pipe,
				surf->screen->surface,
				0, 0,
				back_surf,
				0, 0,
				surf->w, surf->h);
			surf->user->pipe->flush(surf->user->pipe, PIPE_FLUSH_RENDER_CACHE | PIPE_FLUSH_TEXTURE_CACHE, NULL);

#ifdef DRM_MODE_FEATURE_DIRTYFB
			/* TODO query connector property to see if this is needed */
			drmModeDirtyFB(dev->drmFD, surf->screen->fbID, NULL, 0);
#else
			(void)dev;
#endif

			/* TODO more stuff here */
		}
	}

	return EGL_TRUE;
}
