#include "display.h"

SDL_Surface *SDLVideo = NULL;

static int SDL_BPP = 0;
static int SDL_Width = 0;
static int SDL_Height = 0;

struct gstruct {
	int active;
	unsigned int *map;
	double gamma;
	double gain;
	int brightness;
	int src_bpp;
	};

#define MAX_PROPERTIES 10
static struct gstruct PropList[MAX_PROPERTIES];

static void GetXvInfo(xvinfo_t *xvinfo);

static void ByteSwap(unsigned char *data, int nbytes);

static unsigned int * GetPropertyMap(int p, int src_bpp);
static unsigned int * CreatePropertyMap_16bpp_565(int p);
static unsigned int * CreatePropertyMap_16bpp_888(int p);

static int _SDLDisplay_8bpp(struct Image *img, unsigned int *map, int x_off, int y_off);
static int _SDLDisplay_16bpp(struct Image *img, unsigned int *map, int x_off, int y_off);
static int _SDLDisplay_24bpp(struct Image *img, unsigned int *map, int x_off, int y_off);

void
Message(char *str)
	{
	fprintf(stderr,"%s\n",str);
	}

void
FatalError(char *str)
	{
	Message(str);
	exit(1);
	}

// Create a "property", ie a collection of display parameters to be applied to
// an image
int
CreateProperty(int brightness, double gain, double gamma)
	{
	int i;

	for(i=0; i<MAX_PROPERTIES; ++i) {

	   if (! PropList[i].active) {
		PropList[i].active = 1;
		PropList[i].src_bpp = 0;
		PropList[i].gain = gain;
		PropList[i].gamma = gamma;
		PropList[i].brightness = brightness;
		PropList[i].map = NULL;
		return i;
		}
	   }

	printf("CreateProperty: no spare tables\n");
	fflush(stdout);

	// no spare tables
	return -1;
	}

void
SetProperty_brightness(int p, int brightness)
	{
	if (brightness != PropList[p].brightness) {
	   PropList[p].brightness = brightness;
	   if (PropList[p].map) free(PropList[p].map);
	   PropList[p].map = NULL;
	   }
	}

void
SetProperty_gamma(int p, double gamma)
	{
	if (gamma != PropList[p].gamma) {
	   PropList[p].gamma = gamma;
	   if (PropList[p].map) free(PropList[p].map);
	   PropList[p].map = NULL;
	   }
	}

void
SetProperty_gain(int p, double gain)
	{

	if (gain != PropList[p].gain) {
	   PropList[p].gain = gain;
	   if (PropList[p].map) free(PropList[p].map);
	   PropList[p].map = NULL;
	   }
	}

int
AdjustProperty_brightness(int p, int brightness)
	{
	int b = PropList[p].brightness + brightness;

	if (b<0) b=0;

	if (b != PropList[p].brightness) {
	   PropList[p].brightness = b;
	   if (PropList[p].map) free(PropList[p].map);
	   PropList[p].map = NULL;
	   }

	return b;
	}

double
AdjustProperty_gamma(int p, double gamma)
	{
	double g = PropList[p].gamma + gamma;

	if (g<0) g=0;

	if (g != PropList[p].gamma) {
	   PropList[p].gamma = g;
	   if (PropList[p].map) free(PropList[p].map);
	   PropList[p].map = NULL;
	   }

	return g;
	}

double
AdjustProperty_gain(int p, double gain)
	{
	double g = PropList[p].gain + gain;

	if (g<0) g=0;

	if (g != PropList[p].gain) {
	   PropList[p].gain = g;
	   if (PropList[p].map) free(PropList[p].map);
	   PropList[p].map = NULL;
	   }

	return g;
	}

void
DeleteProperty(int i)
	{
	if (i<0 || i >= MAX_PROPERTIES) return;

	PropList[i].gain = 1.0;
	PropList[i].gamma = 1.0;
	if (PropList[i].map) free(PropList[i].map);
	PropList[i].map = NULL;
	PropList[i].src_bpp = 0;
	PropList[i].active = 0;
	}
	
// Generate a lookup table that will convert image values of the designated src_bpp into pixels
// taking into account the property adjustments
static unsigned int *
GetPropertyMap(int p, int src_bpp)
	{

	// Has someone already generated the map ?
	if (PropList[p].map != NULL && PropList[p].src_bpp == src_bpp)
	   return PropList[p].map;

	// Otherwise create a lookup table that maps image pixels + display properties
	// onto screen pixels.

	switch (src_bpp) {
	   case 16:
		if (SDL_BPP==16) return CreatePropertyMap_16bpp_565(p);
		if (SDL_BPP==32) return CreatePropertyMap_16bpp_888(p);
		printf("Unknown SDL_BPP %d\n",SDL_BPP);
		exit(1);
		break;
	   default:
		printf("unknown or not supported src_bpp %d\n",src_bpp);
		exit(1);
		break;
	   }

	printf("GetPropertyMap: don't support bpp=%d and SDL_BPP=%d\n",PropList[p].src_bpp,SDL_BPP);
	exit(1); return NULL;
	}

/*
 * create a lookup table that maps a value (multiplied by gain) to 565
 */
static unsigned int *
CreatePropertyMap_16bpp_565(int p)
        {
        int i;
	int brightness = PropList[p].brightness;
	double gain = PropList[p].gain;
	double gamma = PropList[p].gamma;
	unsigned int *map;

	if (PropList[p].map == NULL) 
	   PropList[p].map = (unsigned int *)Malloc(65536 * 4);

	map = PropList[p].map;

        for(i=0; i<65536; ++i) {
	   int n = i * gain + brightness;
	   if (n>65535) n = 65535;
	   if (n<0) n=0;

           unsigned int v = (unsigned int) (pow((double)n / 65535.0, 1.0/gamma) * 65535);
           if (v>65535) v=65535;
	   if (v<0) v=0;

	   // convert to 5-6-5 format
           map[i] = (v&0xf800) | ((v&0xfc00)>>5) | (v>>11);
           }

	PropList[p].src_bpp = 16;
        return map;
        }

static unsigned int *
CreatePropertyMap_16bpp_888(int p)
        {
        int i;
	int brightness = PropList[p].brightness;
	double gain = PropList[p].gain;
	double gamma = PropList[p].gamma;
	unsigned int *map;

	if (PropList[p].map == NULL) 
	   PropList[p].map = (unsigned int *)Malloc(65536 * 4);

	map = PropList[p].map;

        for(i=0; i<65536; ++i) {
	   int n = i * gain + brightness;
	   if (n>65535) n = 65535;
	   if (n<0) n=0;

           unsigned int v = (unsigned int) (pow((double)n / 65535.0, 1.0/gamma) * 65535);
           if (v>65535) v=65535;
	   if (v<0) v=0;

	   v >>= 8;

	   // convert to 5-6-5 format
           map[i] = v | (v<<8) | (v<<16);
           }

	PropList[p].src_bpp = 16;
        return map;
        }

int
InitProperties()
	{
	int i;

	for(i=0; i<MAX_PROPERTIES; ++i) {
	   PropList[i].active = 0;
	   PropList[i].brightness = 0;
	   PropList[i].gamma = 1.0;
	   PropList[i].gain = 1.0;
	   PropList[i].src_bpp = 0;
	   PropList[i].map = NULL;
	   }

	return 0;
	}

int
GetDisplayWidth()
	{
	return SDL_Width; 
	}

int 
GetDisplayHeight()
	{
	return SDL_Height;
	}

int
InitDisplay(char *caption, int width, int height, int bpp)
	{
	const SDL_VideoInfo  *sdlvideoinfo;
	SDL_Rect **modes;
	unsigned int sdlflags;
	int i,sdlbpp;
	xvinfo_t xvinfo;

	GetXvInfo(&xvinfo);

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE) == -1) {
	   FatalError("Could not init SDL\n");
	   exit(1);
	   }

	sdlvideoinfo = SDL_GetVideoInfo();

	sdlflags = SDL_ANYFORMAT | SDL_RESIZABLE;

  	if ((xvinfo.max_width!=-1)&&(xvinfo.max_height!=-1)) {
    	   // if the XV area is too small, we use software acceleration
    	   if ((xvinfo.max_width < width)|| (xvinfo.max_height < height)) {
      	   	//Message("SDL: Using SW surface");
      	   	sdlflags|= SDL_SWSURFACE;
      	   	sdlflags&= ~SDL_HWSURFACE;
      	   	sdlflags&= ~SDL_HWACCEL;
    	   	}
    	   else {
      	   	//Message("SDL: Using HW surface");
      	   	sdlflags|= SDL_HWSURFACE;
      	   	sdlflags|= SDL_HWACCEL;
      	   	sdlflags&= ~SDL_SWSURFACE;
    	   	}
  	   }
  	else {
    	   // try HW accel and pray...
      	   //Message("SDL: Using any surface");
    	   sdlflags|= SDL_SWSURFACE;
    	   sdlflags&= ~SDL_HWSURFACE;
    	   sdlflags&= ~SDL_HWACCEL;
  	   }

	modes = SDL_ListModes(NULL,sdlflags);
  	if (modes!=(SDL_Rect**)-1) {
    	   // not all resolutions are OK for this video card. 
	   // For safety we switch to software accel
    	   Message("SDL: No SDL mode available with hardware accel. Trying without HWSURFACE");
    	   sdlflags&= ~SDL_HWSURFACE;
    	   sdlflags&= ~SDL_HWACCEL;
    	   modes=SDL_ListModes(NULL,sdlflags);
    	   if (modes!=(SDL_Rect**)-1) {
      	      fprintf(stderr,"SDL: Still no modes available. Can't start SDL!\n");
      	      SDL_Quit();
      	      return(0);
    	      }
	   }
	
	sdlbpp = SDL_VideoModeOK(width, height, bpp, sdlflags);

	if (sdlbpp != bpp) {
	   fprintf(stderr,"SDL: Requested %d but SDL says %d is nearest bpp\n",
		bpp,sdlbpp);
	   SDL_Quit();
	   return(0);
	   }

	SDLVideo = SDL_SetVideoMode(width, height, bpp, sdlflags);

 	if (SDLVideo == NULL) {
    	   FatalError(SDL_GetError());
    	   return(0);
  	   }

  	if (SDL_SetColorKey( SDLVideo, SDL_SRCCOLORKEY, 0x0) < 0 ) {
    	   Message(SDL_GetError());
	   return(0);
  	   }

	SDL_ShowCursor(1);
	SDL_WM_SetCaption(caption,caption);
	SDL_EnableKeyRepeat(50,100);

	SDL_BPP = bpp;
	SDL_Width = width;
	SDL_Height = height;

	return(1);
	}

/* The following function is a strip-down version of the program 'xvinfo' of the XFree86 project. */
static void
GetXvInfo(xvinfo_t *xvinfo) {

#ifdef HAVE_XV

  unsigned int ver, rev, eventB, reqB, errorB;
  int i, j, n;

  xvinfo->max_height=-1;
  xvinfo->max_width=-1;

  if(!(xvinfo->dpy = XOpenDisplay(NULL))) {
    FatalError("Unable to open display");
    return;
  }

  if((Success != XvQueryExtension(xvinfo->dpy, &ver, &rev, &reqB, &eventB, &errorB))) {
    //Message("xvinfo: No X-Video Extension");
    return;
  }

  i=0; // 0: we use only the first screen
  XvQueryAdaptors(xvinfo->dpy, RootWindow(xvinfo->dpy, i), &xvinfo->nadaptors, &xvinfo->ainfo);
  if( !xvinfo->nadaptors || !xvinfo->ainfo ){
    //Message( "xvinfo: No adpators present\n" );
    return;
  }
  j=0; // 0: we use only the first adaptor
  XvQueryEncodings(xvinfo->dpy, xvinfo->ainfo[j].base_id, &xvinfo->nencode, &xvinfo->encodings);

  if(xvinfo->encodings && xvinfo->nencode) {
    xvinfo->ImageEncodings = 0;

    for(n = 0; n < xvinfo->nencode; n++) {
      if(!strcmp(xvinfo->encodings[n].name, "XV_IMAGE"))
        xvinfo->ImageEncodings++;
    }

    if(xvinfo->ImageEncodings && (xvinfo->ainfo[j].type & XvImageMask)) {
      //char imageName[5] = {0, 0, 0, 0, 0};

      for(n = 0; n < xvinfo->nencode; n++) {
        if(!strcmp(xvinfo->encodings[n].name, "XV_IMAGE")) {
          //fprintf(stdout, "maximum XvImage size: %li x %li\n", xvinfo->encodings[n].width, xvinfo->encodings[n].height);
          xvinfo->max_height=(int)xvinfo->encodings[n].height;
          xvinfo->max_width=(int)xvinfo->encodings[n].width;
          break;
        }
      }
      /*
      formats = XvListImageFormats(dpy, ainfo[j].base_id, &numImages);
      fprintf(stdout, "    Number of image formats: %i\n", numImages);

      for(n = 0; n < numImages; n++) {
        memcpy(imageName, &(formats[n].id), 4);
        fprintf(stdout, "      id: 0x%x", formats[n].id);
        if(isprint(imageName[0]) && isprint(imageName[1]) && isprint(imageName[2]) && isprint(imageName[3])) {
          fprintf(stdout, " (%s)\n", imageName);
        } else {
          fprintf(stdout, "\n");
        }
        fprintf(stdout, "        bits per pixel: %i\n", formats[n].bits_per_pixel);
        fprintf(stdout, "        number of planes: %i\n", formats[n].num_planes);
        fprintf(stdout, "        type: %s (%s)\n",
                (formats[n].type == XvRGB) ? "RGB" : "YUV",
                (formats[n].format == XvPacked) ? "packed" : "planar");
        if(formats[n].type == XvRGB) {
          fprintf(stdout, "        depth: %i\n", formats[n].depth);
          fprintf(stdout, "        red, green, blue masks: 0x%x, 0x%x, 0x%x\n",
                  formats[n].red_mask,
                  formats[n].green_mask,
                  formats[n].blue_mask);
        }
      }
      if(formats) XFree(formats);
      */
    }

    XvFreeEncodingInfo(xvinfo->encodings);
  }

  XvFreeAdaptorInfo(xvinfo->ainfo);

#else
  xvinfo->max_height=-1;
  xvinfo->max_width=-1;
#endif

  //fprintf(stderr,"%d %d\n", xvinfo->max_height, xvinfo->max_width);
}

void
ShutdownDisplay()
	{
	SDL_Quit();
	}

// Display an image, format can be: 8bpp mono, 16bpp mono, 24bpp colour
// p is a property index
// (x_off,y_off) is the origin of the image
// return 1 for success, 0 on error

int
DisplayImage(struct Image *img, int p, int x_off,int y_off)
	{
	unsigned int *map;
	int rval;

	if (! (map = GetPropertyMap(p,img->depth))) {
	   printf("DisplayImage: cannot get property map\n");
	   return 0;
	   }

	switch(img->depth) {
	   case 8: rval= _SDLDisplay_8bpp(img,map,x_off,y_off); break;
	   case 16: rval= _SDLDisplay_16bpp(img,map,x_off,y_off); break;
	   case 24: rval= _SDLDisplay_24bpp(img,map,x_off,y_off); break;
	   default: printf("DisplayImage: unknown image depth %d\n",img->depth);
		    exit(1); break;
	   }

	return rval;
	}
	
// display an image using a lookup table to convert src values into pixels
// assuming a 8bpp monochrome src image
static int
_SDLDisplay_8bpp(struct Image *img, unsigned int *map, int x_off, int y_off)
	{
	unsigned char *data = (unsigned char *)img->data;
	int x,y,x1,y1,x2,y2;
	int W,width = img->width;
	int H,height = img->height;
	const SDL_PixelFormat *f = SDLVideo->format;
	int bpp = f->BytesPerPixel;

	// may be displaying a subregion
	x1 = img->cutout.x1; y1 = img->cutout.y1; x2 = img->cutout.x2; y2 = img->cutout.y2;

	W = x2-x1+1;	// dimensions of region we are transferring
	H = y2-y1+1;

	SDL_LockSurface(SDLVideo);

	for(y=y1; y<=y2; ++y) {
	   register unsigned char *sptr = data + y * width + x1;

	   if (SDL_BPP == 16) {
    	   	register unsigned short *dst = (unsigned short *)((Uint8 *)SDLVideo->pixels + (y_off+y-y1) * SDLVideo->pitch + x_off * bpp);
		for(x=0; x<W; ++x) *(dst++) = (unsigned short)map[*(sptr++)];
		}
	   if (SDL_BPP == 32) {
    	   	register unsigned int *dst = (unsigned int *)((Uint8 *)SDLVideo->pixels + (y_off+y-y1) * SDLVideo->pitch + x_off * bpp);
	   	for(x=0; x<W; ++x) *(dst++) = map[*(sptr++)];
		}
	   }

	SDL_UnlockSurface(SDLVideo);
	SDL_UpdateRect(SDLVideo,x_off,y_off,W,H);
	}

// display an image using a lookup table to convert src values into pixels
// assuming a 16bpp monochrome src image
static int
_SDLDisplay_16bpp(struct Image *img, unsigned int *map, int x_off, int y_off)
	{
	unsigned short *data = (unsigned short *)img->data;
	int x,y,x1,y1,x2,y2;
	int W,width = img->width;
	int H,height = img->height;
	const SDL_PixelFormat *f = SDLVideo->format;
	int bpp = f->BytesPerPixel;

	// may be displaying a subregion
	x1 = img->cutout.x1; y1 = img->cutout.y1; x2 = img->cutout.x2; y2 = img->cutout.y2;

	W = x2-x1+1;	// dimensions of region we are transferring
	H = y2-y1+1;

	SDL_LockSurface(SDLVideo);

	for(y=y1; y<=y2; ++y) {
	   register unsigned short *sptr = data + y * width + x1;

	   if (SDL_BPP == 16) {
    	   	register unsigned short *dst = (unsigned short *)((Uint8 *)SDLVideo->pixels + (y_off+y-y1) * SDLVideo->pitch + x_off * bpp);
		for(x=0; x<W; ++x) *(dst++) = (unsigned short)map[*(sptr++)];
		}
	   if (SDL_BPP == 32) {
    	   	register unsigned int *dst = (unsigned int *)((Uint8 *)SDLVideo->pixels + (y_off+y-y1) * SDLVideo->pitch + x_off * bpp);
	   	for(x=0; x<W; ++x) *(dst++) = map[*(sptr++)];
		}
	   }

	SDL_UnlockSurface(SDLVideo);
	SDL_UpdateRect(SDLVideo,x_off,y_off,W,H);
	}

// display an image using a lookup table to convert src values into pixels
// assuming a 24bpp 8/8/8 RGB src image
static int
_SDLDisplay_24bpp(struct Image *img, unsigned int *map, int x_off, int y_off)
	{
	unsigned char *data = (unsigned char *)img->data;
	int x,y,x1,y1,x2,y2;
	int W,width = img->width;
	int H,height = img->height;
	const SDL_PixelFormat *f = SDLVideo->format;
	int bpp = f->BytesPerPixel;

	// may be displaying a subregion
	x1 = img->cutout.x1; y1 = img->cutout.y1; x2 = img->cutout.x2; y2 = img->cutout.y2;

	W = x2-x1+1;	// dimensions of region we are transferring
	H = y2-y1+1;

	SDL_LockSurface(SDLVideo);

	for(y=y1; y<=y2; ++y) {
	   register unsigned char *sptr = data + (y * width + x1) * 3;

	   if (SDL_BPP == 16) {
    	   	register unsigned short *dst = (unsigned short *)((Uint8 *)SDLVideo->pixels + (y_off+y-y1) * SDLVideo->pitch + x_off * bpp);
		for(x=0; x<W; ++x) {
		   unsigned int v = *(sptr++); 
		   v <<= 8; v |= *(sptr++);
		   v <<= 8; v |= *(sptr++);
		   *(dst++) = (unsigned short)map[v];
		   }
		}
	   if (SDL_BPP == 32) {
    	   	register unsigned int *dst = (unsigned int *)((Uint8 *)SDLVideo->pixels + (y_off+y-y1) * SDLVideo->pitch + x_off * bpp);
	   	for(x=0; x<W; ++x) {
		   unsigned int v = *(sptr++); 
		   v <<= 8; v |= *(sptr++);
		   v <<= 8; v |= *(sptr++);
		   *(dst++) = (unsigned int)map[v];
		   }
		}
	   }

	SDL_UnlockSurface(SDLVideo);
	SDL_UpdateRect(SDLVideo,x_off,y_off,W,H);
	}

static void
ByteSwap(unsigned char *data, int nbytes)
	{
	int i;

	while(nbytes) {
	   unsigned char tmp = *data;
	   *data = *(data+1);
	   *(data+1) = tmp;
	   nbytes -= 2;
	   }
	}
