#include <stdlib.h>
#include <stdio.h>

#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES

#include <gbm.h>
#include <GL/gl.h>
#include <GL/glu.h>

#ifndef GLAPIENTRY
#define GLAPIENTRY
#endif

#include <GL/glext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <drm.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

#ifdef GL_OES_EGL_image
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES_func;
static PFNEGLCREATEIMAGEKHRPROC glEGLCreateImageKHR_func; 
#endif

static const char device_name[] = "/dev/dri/card0";
GLint copy_prog;

static GLint
compile_glsl_prog(GLenum type, const char *source)
{
  GLint ok;
  GLint prog;

  prog = glCreateShader(type);
  glShaderSource(prog, 1, (const GLchar **) &source, NULL);
  glCompileShader(prog);
  glGetShaderiv(prog, GL_COMPILE_STATUS, &ok);
  if (!ok) {
    GLchar *info;
    GLint size;

    glGetShaderiv(prog, GL_INFO_LOG_LENGTH, &size);
    info = malloc(size);
    glGetShaderInfoLog(prog, size, NULL, info);
    fprintf(stderr, "Failed to compile %s: %s\n",
	    type == GL_FRAGMENT_SHADER ? "FS" : "VS", info);
    fprintf(stderr, "Program source:\n%s", source);
  }

  return prog;
}

static void
link_glsl_prog(GLint prog)
{
  GLint ok;

  glLinkProgram(prog);
  glGetProgramiv(prog, GL_LINK_STATUS, &ok);
  if (!ok) {
    GLchar *info;
    GLint size;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size);
    info = malloc(size);
    glGetProgramInfoLog(prog, size, NULL, info);
    fprintf(stderr, "Failed to link: %s\n", info);
    fprintf(stderr, "GLSL link failure\n");
  }
}

#define VERTEX_POS 0
#define VERTEX_SOURCE 1

static void 
init_shader(void)
{
  const char *copy_vs =
    "attribute vec4 v_position;\n"
    "attribute vec4 v_texcoord0;\n"
    "varying vec2 source_texture;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = v_position;\n"
    "   source_texture = v_texcoord0.xy;\n" "}\n";

  const char *copy_fs =
    "varying vec2 source_texture;\n"
    "uniform sampler2D sampler;\n"
    "void main()\n"
    "{\n"
    "     gl_FragColor = texture2D(sampler, source_texture).rgba;\n"
    "    } \n" ;

  GLint sampler_uniform_location;
  GLint fs_prog, vs_prog;

  copy_prog = glCreateProgram();
  vs_prog = compile_glsl_prog(GL_VERTEX_SHADER, copy_vs);
  fs_prog = compile_glsl_prog(GL_FRAGMENT_SHADER, copy_fs);
  glAttachShader(copy_prog, vs_prog);
  glAttachShader(copy_prog, fs_prog);
  glBindAttribLocation(copy_prog, VERTEX_POS, "v_position");
  glBindAttribLocation(copy_prog, VERTEX_SOURCE, "v_texcoord0");
  link_glsl_prog(copy_prog);
  glUseProgram(copy_prog);
  sampler_uniform_location = glGetUniformLocation(copy_prog, "sampler");
  glUniform1i(sampler_uniform_location, 0);
  glUseProgram(0);
}

static GLuint 
create_tex_from_img(EGLImageKHR image)
{
  GLuint tex;

  glGenTextures(1, &tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glEGLImageTargetTexture2DOES_func (GL_TEXTURE_2D, image);
  glBindTexture(GL_TEXTURE_2D, 0);

  return tex;
}

static GLuint 
create_fbo_render2tex(GLint tex)
{
  GLuint fb;

  glGenFramebuffers(1, &fb);
  glBindFramebuffer(GL_FRAMEBUFFER, fb);
  glFramebufferTexture2D(GL_FRAMEBUFFER,
			 GL_COLOR_ATTACHMENT0,
			 GL_TEXTURE_2D, tex,
			 0);

  if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) !=
      GL_FRAMEBUFFER_COMPLETE) {
    fprintf(stderr, "framebuffer not complete\n");
    exit(1);
  }

  return fb;
}

static EGLImageKHR 
create_image_from_handle(EGLDisplay dpy, EGLContext ctx, 
                         int fd, int handle, int width, 
                         int height, int stride, int bpp)
{
  struct drm_gem_flink flink;
  EGLImageKHR image;
  EGLint attribs[] = {
    EGL_WIDTH, 0,
    EGL_HEIGHT, 0,
    EGL_DRM_BUFFER_STRIDE_MESA, 0,
    EGL_DRM_BUFFER_FORMAT_MESA,
    EGL_DRM_BUFFER_FORMAT_ARGB32_MESA,
    EGL_DRM_BUFFER_USE_MESA,
    EGL_DRM_BUFFER_USE_SHARE_MESA,
    EGL_NONE
  };

  attribs[1] = width;
  attribs[3] = height;
  attribs[5] = stride * bpp/8;

  flink.handle = handle;
  if (ioctl(fd, DRM_IOCTL_GEM_FLINK, &flink) < 0)
    return EGL_NO_IMAGE_KHR;

  image = glEGLCreateImageKHR_func(dpy , ctx,
				   EGL_DRM_BUFFER_MESA,
				   (void *) (uintptr_t)flink.name, attribs);
  if (image == EGL_NO_IMAGE_KHR)
    return EGL_NO_IMAGE_KHR;

  return image;
}

/* use shader to copy tex to current fbo. */
static void 
copy_tex(GLint tex)
{
  static float vertices[8] = { -1, -1,
			       1, -1,
			       1, 1,
			       -1, 1
  };
  static float texcoords[8] = { 0, 1,
				1, 1,
				1, 0,
				0, 0
  };

  glVertexAttribPointer(VERTEX_POS, 2, GL_FLOAT,
			GL_FALSE, 2 * sizeof(float),
			vertices);
  glEnableVertexAttribArray(VERTEX_POS);
  glVertexAttribPointer(VERTEX_SOURCE, 2, GL_FLOAT,
			GL_FALSE, 2 * sizeof(float),
			texcoords);
  glEnableVertexAttribArray(VERTEX_SOURCE);

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, tex);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		  GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
		  GL_NEAREST);

  glUseProgram(copy_prog);

  glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

  glUseProgram(0);
  glDisableVertexAttribArray(VERTEX_SOURCE);
  glDisableVertexAttribArray(VERTEX_POS);
}



int main(int argc, char *argv[])
{
  EGLDisplay dpy;
  EGLContext ctx;
  EGLImageKHR shadow_image, screen_image;
  EGLint major, minor;
  const char *ver, *extensions;
  uint32_t shadow_handle, shadow_stride;
  int ret, fd;
  struct gbm_device *gbm;
  struct gbm_bo *shadow_bo, *screen_bo;
  GLuint shadow_tex, screen_tex, screen_fb;

  fd = open(device_name, O_RDWR);
  if (fd < 0) {
    /* Probably permissions error */
    fprintf(stderr, "couldn't open %s, skipping\n", device_name);
    return -1;
  }

  gbm = gbm_create_device(fd);
  if (gbm == NULL) {
    fprintf(stderr, "couldn't create gbm device\n");
    ret = -1;
    goto close_fd;
  }

  dpy = eglGetDisplay(gbm);
  if (dpy == EGL_NO_DISPLAY) {
    fprintf(stderr, "eglGetDisplay() failed\n");
    ret = -1;
    goto destroy_gbm_device;
  }
	
  if (!eglInitialize(dpy, &major, &minor)) {
    printf("eglInitialize() failed\n");
    ret = -1;
    goto egl_terminate;
  }

  ver = eglQueryString(dpy, EGL_VERSION);
  printf("EGL_VERSION = %s\n", ver);

  extensions = eglQueryString(dpy, EGL_EXTENSIONS);

  if (!strstr(extensions, "EGL_KHR_surfaceless_opengl")) {
    printf("No support for EGL_KHR_surfaceless_opengl\n");
    ret = -1;
    goto egl_terminate;
  }

  eglBindAPI(EGL_OPENGL_API);
  ctx = eglCreateContext(dpy, NULL, EGL_NO_CONTEXT, NULL);
  if (ctx == NULL) {
    fprintf(stderr, "failed to create context\n");
    ret = -1;
    goto egl_terminate;
  }

  if (!eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx)) {
    fprintf(stderr, "failed to make context current\n");
    ret = -1;
    goto destroy_context;
  }

  init_shader();

  glEGLImageTargetTexture2DOES_func = 
    (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
    eglGetProcAddress("glEGLImageTargetTexture2DOES");

  glEGLCreateImageKHR_func = (PFNEGLCREATEIMAGEKHRPROC)
    eglGetProcAddress("eglCreateImageKHR");


  shadow_bo = gbm_bo_create(gbm, 1024, 768,
			    GBM_BO_FORMAT_XRGB8888,
			    GBM_BO_USE_RENDERING);

  screen_bo = gbm_bo_create(gbm, 1024, 768,
			    GBM_BO_FORMAT_XRGB8888,
			    GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
  if (screen_bo == NULL) {
    fprintf(stderr, "failed to create gbm screen bo\n");
    ret = -1;
    goto unmake_current;
  }

  shadow_handle = gbm_bo_get_handle(shadow_bo).u32;
  shadow_stride = gbm_bo_get_pitch(shadow_bo);

  shadow_image = create_image_from_handle(dpy, ctx, fd, 
					  shadow_handle, 
					  1024, 
					  768, 
                                          shadow_stride,
					  32); 
  screen_image = eglCreateImageKHR(dpy, NULL, EGL_NATIVE_PIXMAP_KHR, screen_bo, NULL);
  if (screen_image == EGL_NO_IMAGE_KHR || shadow_image == EGL_NO_IMAGE_KHR) {
    fprintf(stderr, "failed to create egl image\n");
    ret = -1;
    goto destroy_gbm_bo;
  }

  shadow_tex = create_tex_from_img(shadow_image);
  screen_tex = create_tex_from_img(screen_image);

  screen_fb = create_fbo_render2tex(screen_tex);

  /* Copy shadow texture to screen texture by using shader. */
  glBindFramebuffer(GL_FRAMEBUFFER, screen_fb);
  copy_tex(shadow_tex);  
  glFlush();

  glDeleteFramebuffers(1, &screen_fb);
  glDeleteTextures(1, &shadow_tex);
  glDeleteTextures(1, &screen_tex);
  eglDestroyImageKHR(dpy, shadow_image);
  eglDestroyImageKHR(dpy, screen_image);
  /* Now create a smaller image from the same handle. */
  shadow_image = create_image_from_handle(dpy, ctx, fd, 
					  shadow_handle, 
					  1024 / 2 , 
					  768 / 2 , 
					  shadow_stride/2,
					  32);
  if (shadow_image == EGL_NO_IMAGE_KHR) {

    /* As we already destroyed the first shadow image which 
       created from the same handle, we should be able to 
       create a smaller image from the same handle again. 
       This is true, if we don't call into

       copy_tex(shadow_tex);

       But if we call into copy_tex, then we fail here. 
       You will get error message from the mesa side as below:
       "Region for name 1 already exists but is not compatible "
       copy_tex(shadow_tex) is a function to use shadow 
       texture as a texture source, and access it in a 
       shader program to render the screen fbo. I traced
       into the code, and found that glDrawArray will 
       implicitly increase the shadow texture's reference count.
       And then latter when we delete the texture, the 
       count will remain 1 rather then zero, and then
       it will not decrease the corresponding image region's
       reference counter and then when we destroy the shadow image,
       the image's reference counter will also be 1 and can't
       be freed. Then next time we use the same handle to
       create a new image, it will find the previou zombie
       image region and find they are not compatible.
     
       simply comment out the glDrawArray can avoid the function
       goes here too. Don't know how to fix this problem.
    */
    fprintf(stderr, "hit a bug?\n");
  } else
    eglDestroyImageKHR(dpy, shadow_image);

 destroy_gbm_bo:
  gbm_bo_destroy(shadow_bo);
  gbm_bo_destroy(screen_bo);
 unmake_current:
  eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
 destroy_context:
  eglDestroyContext(dpy, ctx);
 egl_terminate:
  eglTerminate(dpy);
 destroy_gbm_device:
  gbm_device_destroy(gbm);
 close_fd:
  close(fd);

  return ret;
}
