#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>

/* gcc -ansi -pedantic on GNU/Linux causes warnings and errors
 * unless this is defined:
 * warning: #warning "Files using this header must be compiled with _SVID_SOURCE or _XOPEN_SOURCE"
 */
#ifndef _XOPEN_SOURCE
#  define _XOPEN_SOURCE 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <GL/glx.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h> 

#include "gstglsink.h"

typedef struct _GstGLImageConnection GstGLImageConnection;

// this contains everything to draw an image, including all necessary graphics card data. 
struct _GstGLImageConnection {
  GstImageConnection conn;
  Display *dpy; // the Xlib drawing context
  GLXContext ctx; // The GLX drawing context
  gint w, h;
  gint bpp;
  
  int rgbatex_id;
  unsigned char *m_memory;
};

#define TEX_XSIZE 1024
#define TEX_YSIZE 1024


typedef struct _GstGLImage GstGLImage;
struct _GstGLImage
{
  GstImageData data;
  GstGLImageConnection *conn;
};

static GstGLImageInfo *		gst_gl_rgbimage_info		(GstImageInfo *info);
static GstGLImageConnection *	gst_gl_rgbimage_connection 	(GstImageConnection *conn);

static GstCaps * 		gst_gl_rgbimage_get_caps	(GstImageInfo *info); 
static GstImageConnection *	gst_gl_rgbimage_set_caps	(GstImageInfo *info, GstCaps *caps);
static GstImageData *		gst_gl_rgbimage_get_image	(GstImageInfo *info, GstImageConnection *conn);
static void 			gst_gl_rgbimage_put_image	(GstImageInfo *info, GstImageData *image);
static void 			gst_gl_rgbimage_free_image	(GstImageData *image);
static void 			gst_gl_rgbimage_open_conn	(GstImageConnection *conn, GstImageInfo *info);
static void 			gst_gl_rgbimage_close_conn	(GstImageConnection *conn, GstImageInfo *info);
static void 			gst_gl_rgbimage_free_conn	(GstImageConnection *conn);

GstImagePlugin* get_gl_rgbimage_plugin(void)
{
  static GstImagePlugin plugin = { gst_gl_rgbimage_get_caps,
				   gst_gl_rgbimage_set_caps,
				   gst_gl_rgbimage_get_image,
				   gst_gl_rgbimage_put_image,
    				   gst_gl_rgbimage_free_image};

  return &plugin;
}

static GstGLImageInfo *
gst_gl_rgbimage_info (GstImageInfo *info)
{
  if (info == NULL || info->id != GST_MAKE_FOURCC ('X', 'l', 'i', 'b'))
  {
    return NULL;
  }
  return (GstGLImageInfo *) info;
}

static GstGLImageConnection *
gst_gl_rgbimage_connection (GstImageConnection *conn)
{
  if (conn == NULL || conn->free_conn != gst_gl_rgbimage_free_conn)
    return NULL; 
  return (GstGLImageConnection *) conn;
}

GstCaps *
gst_gl_rgbimage_get_caps (GstImageInfo *info)
{
  GstCaps *caps = NULL;
  Visual *visual;
  int xpad;
  XWindowAttributes attrib;
  XImage *ximage;
  GstGLImageInfo *xinfo = gst_gl_rgbimage_info (info);
  
  g_warning("rgbimage get caps called, context %p, endianness %d !\n", glXGetCurrentContext(), G_BIG_ENDIAN);
  /* we don't handle this image information */
  if (xinfo == NULL) return NULL;

  XGetWindowAttributes(xinfo->dpy, xinfo->win, &attrib);
  
  visual = attrib.visual;
  if (attrib.depth <= 8)
    xpad = 8;
  else if (attrib.depth <= 16)
    xpad = 16;
  else
    xpad = 32;
  
  // create a temporary image
  ximage = XCreateImage (xinfo->dpy, visual, attrib.depth, ZPixmap, 0, NULL, 
			      100, 100, xpad, (attrib.depth + 7) / 8 * 100);
  if (ximage != NULL) {
    caps = 
      GST_CAPS_NEW (
		    "forcing Video RGB",
		    "video/x-raw-rgb",
		    "format",          GST_PROPS_FOURCC (GST_STR_FOURCC ("RGB ")),
		    "depth",        GST_PROPS_INT(24),
		    "bpp",          GST_PROPS_INT(24),
		    "red_mask",        GST_PROPS_INT(0xff),
		    "green_mask",        GST_PROPS_INT(0xff00),
		    "blue_mask",        GST_PROPS_INT(0xff0000),
		    "endianness",  GST_PROPS_INT(G_BIG_ENDIAN), /*= 1234/4321 (INT) <- endianness */
		    
		    "width",      GST_PROPS_INT_RANGE (0, TEX_XSIZE), /* can't have videos larger than TEX_SIZE */
		    "height",     GST_PROPS_INT_RANGE (0, TEX_YSIZE) 
		    );
    XDestroyImage (ximage);
  }
  
  printf ("GL_RGBImage: returning caps at %p", caps);
  return caps;
}

static GstImageConnection *	
gst_gl_rgbimage_set_caps (GstImageInfo *info, GstCaps *caps)
{
  g_warning("in set_caps !\n");

  GstGLImageConnection *new = NULL;
  Visual *visual;
  XWindowAttributes attrib;
  GstGLImageInfo *xinfo = gst_gl_rgbimage_info (info);  
  guint32 format;
  gint depth;
  gint endianness;
  gint red_mask, green_mask, blue_mask;
  gint width, height, bpp;

  /* check if this is the right image info */
  if (xinfo == NULL) return NULL;
    
  XGetWindowAttributes(xinfo->dpy, xinfo->win, &attrib);
  
  visual = attrib.visual;

  gst_caps_get (caps,
		  "format", 	&format,
		  "depth",  	&depth,
		  "endianness", &endianness,
		  "red_mask", 	&red_mask,
		  "green_mask",	&green_mask,
		  "blue_mask",	&blue_mask,
		  "width",	&width,
		  "height",	&height,
		  "bpp",	&bpp,
		  NULL);
  
  /* check if the caps are ok */
  if (format != GST_MAKE_FOURCC ('R', 'G', 'B', ' ')) return NULL;
  /* if (gst_caps_get_int (caps, "bpp") != ???) return NULL; */
  //if (depth != attrib.depth) return NULL;
  //if (endianness != ((ImageByteOrder (xinfo->dpy) == LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN)) return NULL;
  //if (red_mask != visual->red_mask) return NULL;
  //if (green_mask != visual->green_mask) return NULL;
  //if (blue_mask != visual->blue_mask) return NULL;
  GST_DEBUG ("GL_RGBImage: caps %p are ok, creating image", caps);
  
  new = g_new (GstGLImageConnection, 1);
  new->conn.open_conn = gst_gl_rgbimage_open_conn;
  new->conn.close_conn = gst_gl_rgbimage_close_conn;
  new->conn.free_conn = gst_gl_rgbimage_free_conn;
  new->dpy = xinfo->dpy;
  new->ctx = xinfo->ctx;
  new->w = width;
  new->h = height;
  new->bpp = bpp;
  
  return (GstImageConnection *) new;
}

static GstImageData *
gst_gl_rgbimage_get_image (GstImageInfo *info, GstImageConnection *conn)
{
  GstGLImage *image;
  //XWindowAttributes attrib;
  GstGLImageInfo *xinfo = gst_gl_rgbimage_info (info);  
  GstGLImageConnection *xconn = gst_gl_rgbimage_connection (conn);  
  
  image = g_new (GstGLImage, 1);

  /* checks */
  if (xinfo == NULL) return NULL;
  if (xconn == NULL) return NULL;
  if (xinfo->dpy != xconn->dpy)
  {
    g_warning ("XImage: wrong x display specified in 'get_image'\n");
    return NULL;
  }

  image->conn = xconn;
  image->data.size = xconn->w * xconn->h * 4;
  image->data.data = g_malloc(image->data.size);
  if (image->data.data == NULL)
  {
    g_warning ("GL_RGBImage: data allocation failed!");
    g_free (image);
    return NULL;
  }

  return (GstImageData *) image;
}


static void
gst_gl_rgbimage_put_image (GstImageInfo *info, GstImageData *image)
{
  float xmax, ymax;

  GstGLImageInfo *xinfo = gst_gl_rgbimage_info (info);
  GstGLImage *im = (GstGLImage *) image;

  int img_width = im->conn->w; 
  int img_height = im->conn->h; 

  g_assert (xinfo != NULL);

  // both upload the video, and redraw the screen
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -5.0);

  glEnable(GL_TEXTURE_2D);

  if (xinfo->info.demo)
    {
      glTranslatef(0.0, 0.0, -5.0); // make it avoid the clipping plane, zoom 2.0 instead
      glRotatef(180.0*sin(xinfo->rotX),1,0,0);
      glRotatef(180.0*cos(xinfo->rotY),0,1,0);

      xinfo->rotX += 0.01;
      xinfo->rotY -= 0.015;
      float zoom = xinfo->zoom;
      glScalef(zoom,zoom,zoom); 

      if (xinfo->zoom > 2.0)
	xinfo->zoomdir = -0.01;

      if (xinfo->zoom < 1.0)
	xinfo->zoomdir = 0.01;

      xinfo->zoom += xinfo->zoomdir;
    }  

  //Draws the surface rectangle
  glBindTexture(GL_TEXTURE_2D, im->conn->rgbatex_id);
  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, im->conn->w, im->conn->h, GL_RGB, 
  		  GL_UNSIGNED_BYTE, im->data.data);  
  xmax = (float)im->conn->w/TEX_XSIZE;
  ymax = (float)im->conn->h/TEX_YSIZE;

  float aspect = img_width/(float)img_height;
  float hor = aspect;

  glColor4f(1,1,1,1);
  glBegin(GL_QUADS);

  glNormal3f(0, -1, 0);
  
  glTexCoord2f(xmax, 0);
  glVertex3f(hor,1,0);

  glTexCoord2f(0, 0);
  glVertex3f(-hor,1,0);

  glTexCoord2f(0, ymax);
  glVertex3f(-hor,-1,0);

  glTexCoord2f(xmax, ymax);
  glVertex3f(hor,-1,0);
  glEnd();

  if (xinfo->info.dumpvideo)
    {
      static int framenr = 0; 
      char capfilename[255];
      static guint8 *cap_image_data = NULL, *cap_image_data2 = NULL;
      int i;
      
      // hmmmm, is this reentrant ?!
      if (cap_image_data == NULL)
	cap_image_data = (guint8 *)malloc(img_width * img_height * 3);

      if (cap_image_data2 == NULL)
	cap_image_data2 = (guint8 *)malloc(img_width * img_height * 3);
      
      printf("Recording frame #%d\n", framenr);
      glReadPixels(0,0,img_width,img_height,GL_RGB,GL_UNSIGNED_BYTE,cap_image_data);
      // invert the pixels
      for (i = 0; i < img_height; i++)
	memcpy(cap_image_data2 + i * img_width * 3, cap_image_data + (img_height-1-i) * img_width * 3, img_width*3);
      
      sprintf(capfilename, "cap%04d.ppm", framenr);
      FILE *outfile = fopen(capfilename, "wb");
      if (outfile != NULL)
	{
	  fprintf(outfile, "P6\n"); 
	  fprintf(outfile,"# created by raw_zb\n"); 
	  fprintf(outfile,"%d %d\n",img_width,img_height); 
	  fprintf(outfile,"255\n"); 
	  fwrite(cap_image_data2, sizeof(char), img_width*img_height*3, outfile);
	  fclose(outfile);
	}
      framenr++;
    }

  glXSwapBuffers(xinfo->dpy, xinfo->win);
}

void
gst_gl_rgbimage_free_image (GstImageData *image)
{
  GstGLImage *im = (GstGLImage *) image;

  g_warning ("gst_gl_rgbimage_free_image doesn't do anything yet -> freeing image\n");
  g_free (im->data.data);
  g_free (im);
}

/* Creates an OpenGL texture to upload the picture over */
static void
gst_gl_rgbimage_open_conn (GstImageConnection *conn, GstImageInfo *info)
{
  g_warning("Opening RGB Connection; classic OpenGL 1.2 renderer.");

  //GstGLImageInfo *xinfo = gst_gl_rgbimage_info (info);  
  GstGLImageConnection *xconn = gst_gl_rgbimage_connection (conn);

  glGenTextures(1, &xconn->rgbatex_id);
  glBindTexture(GL_TEXTURE_2D, xconn->rgbatex_id);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_XSIZE, TEX_YSIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}

/* Deletes the creates OpenGL textures */
static void 
gst_gl_rgbimage_close_conn (GstImageConnection *conn, GstImageInfo *info)
{
  GstGLImageConnection *xconn = gst_gl_rgbimage_connection (conn);
  //GstGLImageInfo *xinfo = gst_gl_rgbimage_info (info);  

  glDeleteTextures(1, &xconn->rgbatex_id);
 }

static void
gst_gl_rgbimage_free_conn (GstImageConnection *conn)
{
  GstGLImageConnection *xconn = gst_gl_rgbimage_connection (conn);
  
  g_assert (xconn != NULL);
  
  g_free (xconn);
}