/*
**  wt -- a 3d game engine
**
**  Copyright (C) 1994 by Chris Laurel
**  email:  claurel@mr.net
**  snail mail:  Chris Laurel, 5700 W Lake St #208,  St. Louis Park, MN  55416
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation; either version 2 of the License, or
**  (at your option) any later version.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/


#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <sys/ipc.h>
#ifndef NO_XSHM
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif

#include "wt.h"
#include "error.h"
#include "wtmem.h"
#include "framebuf.h"
#include "color.h"
#include "graphics.h"
#include "dither.h"


static void create_window(void);
static Palette_info load_palette(void);
static void map_shared_mem(int width, int height);
static void unmap_shared_mem(void);

Display *display;
int screen;
Window wtwin;
static GC gc;
#ifndef NO_XSHM
static XShmSegmentInfo xshminfo;
#endif
static XImage *image;
static int graphics_initialized = 0;
static Boolean dither_mode, blocky_mode, direct_mode;
static DevicePixel *display_fb;
static Pixel *render_fb;
static int display_height, display_width;
static int shm_ckpnt = 0, use_shm, xerror;


Graphics_info *init_graphics(int width, int height, Boolean blocky)
{
     static Graphics_info ginfo;


     if (graphics_initialized == 1)
	  return &ginfo;

     ginfo.width = width;
     ginfo.height = height;

     /* Here, we should really get the visual information and determine
     **   what kind of visual we're running on.  For example, if the frame
     **   buffer is true color, and the visual is as well, we should not have
     **   to do any dithering to display the frame buffer.  I'd have more
     **   motivation to fix this if I could my get hands on some true color
     **   hardware.
     */
     if (sizeof(Pixel) > sizeof(DevicePixel))
	  dither_mode = True;
     blocky_mode = blocky;

     /* If we're not dithering and we're not rendering a blocky frame buffer,
     **   then we can render directly to the frame buffer than we display
     **   on screen, resulting in better performance.
     */
     if (dither_mode || blocky_mode)
	  direct_mode = False;
     else
	  direct_mode = True;

     if (blocky_mode) {
	  display_width = ginfo.width * 2;
	  display_height = ginfo.height * 2;
     } else {
	  display_width = ginfo.width;
	  display_height = ginfo.height;
     }

     display_fb = wtmalloc(display_width * display_height);

     create_window();
     ginfo.palette = load_palette();
     map_shared_mem(display_width, display_height);

     graphics_initialized = 1;

     if (dither_mode)
	  calc_dither(&ginfo);

     return &ginfo;
}


void end_graphics(void)
{
   unmap_shared_mem();
   if (graphics_initialized) {
        XCloseDisplay(display);
        graphics_initialized = 0;
   }
}


static void create_window(void)
{
     XGCValues gc_values;
     char *argv[2] = { "xwt", NULL };
     char *win_name = "What's That?";
     char *icon_name = "xwt";
     XTextProperty w_name_prop, i_name_prop;
     XSizeHints size_hints;


     display = XOpenDisplay(NULL);
     if (display == NULL)
	  fatal_error("Unable to open display.\n");

     screen = DefaultScreen(display);

     wtwin = XCreateSimpleWindow(display, DefaultRootWindow(display),
				 0, 0, display_width, display_height, 2,
				 BlackPixel(display, screen),
				 BlackPixel(display, screen));

     XStringListToTextProperty(&win_name, 1, &w_name_prop);
     XStringListToTextProperty(&icon_name, 1, &i_name_prop);

     size_hints.flags = PSize | PMinSize | PMaxSize;
     size_hints.width= size_hints.min_width = size_hints.max_width =
	  display_width;
     size_hints.height= size_hints.min_height = size_hints.max_height =
	  display_height;

     XSetWMProperties(display, wtwin, &w_name_prop, &i_name_prop, argv, 1,
                      &size_hints, NULL, NULL);

     XMapWindow(display, wtwin);
     XSelectInput(display, wtwin,
		  ButtonPressMask | ButtonReleaseMask |
		  ButtonMotionMask | KeyPressMask | ExposureMask);
     gc_values.graphics_exposures = False;
     gc = XCreateGC(display, wtwin, GCGraphicsExposures, &gc_values);
}


int alloc_colors(Colormap colormap, int color_levels, unsigned long *pixels)
{
     int index = 0;
     int r, g, b;

     for (r = 0; r < color_levels; r++)
     for (g = 0; g < color_levels; g++)
     for (b = 0; b < color_levels; b++) {
          XColor color;

          color.red =   (short) (r * 65535 / (color_levels - 1));
          color.green = (short) (g * 65535 / (color_levels - 1));
          color.blue =  (short) (b * 65535 / (color_levels - 1));
          color.flags = DoRed | DoGreen | DoBlue;

          if (!XAllocColor(display, colormap, &color)) break;

          pixels[index] = color.pixel;
          index++;
     }

     return index;
}


static Palette_info load_palette(void)
{
     int color_levels = 6;
     int index, ncolors;
     Colormap cmap;
     Palette_info pinfo;

     ncolors = color_levels * color_levels * color_levels;
     pinfo.rgb_cube_size = color_levels;
     pinfo.color_lookup = wtmalloc(sizeof(int) * ncolors);

     cmap = DefaultColormap(display, screen);
     index = alloc_colors(cmap, color_levels, pinfo.color_lookup);
     if (index < ncolors) {
          XFreeColors(display, cmap, pinfo.color_lookup, index, 0);
          cmap = XCreateColormap(display, wtwin, DefaultVisual(display,screen),
                                 AllocNone);

          index = alloc_colors(cmap, color_levels, pinfo.color_lookup);
          if (index < ncolors)
               fatal_error("Unable to allocate enough color cells.");

          XSetWindowColormap(display, wtwin, cmap);
     }

     return pinfo;
}


static int error_handler(Display *display, XErrorEvent *event)
{
     xerror = 1;

     return 0;
}

static void trap_errors(void)
{
     xerror = 0;
     XSetErrorHandler(error_handler);
}

static int untrap_errors(void)
{
     XSync(display,0);
     XSetErrorHandler(NULL);
     return xerror;
}


static void map_shared_mem(int width, int height)
{
     int depth;
     XWindowAttributes win_attributes;
     Status result;


     unmap_shared_mem();

     if (!shm_ckpnt) atexit(unmap_shared_mem);
     shm_ckpnt = 1;

     XGetWindowAttributes(display, DefaultRootWindow(display),
                          &win_attributes);
     depth = win_attributes.depth;

     use_shm = 0;

#ifndef NO_XSHM
     if (XShmQueryExtension(display)) {
          use_shm = 1;
	  printf("Using shared memory.\n");

          trap_errors();
          image = XShmCreateImage(display, DefaultVisual(display, screen), 
                                  depth, ZPixmap, NULL, &xshminfo,
                                  width, height);
          if (untrap_errors()) {
               unmap_shared_mem();
               goto shmerror;
          }
          if (!image)
	       fatal_error("Unable to create image.");
          shm_ckpnt++;

          xshminfo.shmid = shmget(IPC_PRIVATE,
				  image->bytes_per_line * image->height,
				  IPC_CREAT | 0777);
          if (xshminfo.shmid < 0) {
               unmap_shared_mem();
               goto shmerror;
          }
          shm_ckpnt++;
          
          image->data = (char *) shmat(xshminfo.shmid, 0, 0);
          if (image->data == ((char *) -1)) {
               unmap_shared_mem();
               goto shmerror;
          }
          shm_ckpnt++;
          
	  if (direct_mode) {
	       display_fb = (DevicePixel *) image->data;
	       render_fb = (Pixel *) image->data;
	  } else {
	       display_fb = (DevicePixel *) image->data;
	       render_fb = (Pixel *) wtmalloc(width * height * sizeof(Pixel));
	  }

          xshminfo.shmaddr = image->data;
          xshminfo.readOnly = False;

          trap_errors();
          result = XShmAttach(display, &xshminfo);
          if (untrap_errors() || !result) {
               unmap_shared_mem();
               goto shmerror;
          }
          shm_ckpnt++;
      } else
#endif

      if (!use_shm) {
	 shmerror:
	   use_shm = 0;

	   display_fb = wtmalloc(width * height);
	   image = XCreateImage(display, DefaultVisual(display,screen), depth,
				ZPixmap, 0, (char *) display_fb, width, height,
				8, 0);
	   if (direct_mode)
		render_fb = (Pixel *) display_fb;
	   else
		render_fb = (Pixel *) wtmalloc(width * height * sizeof(Pixel));

	   if (image == NULL)
		fatal_error("Unable to create image.");
	   shm_ckpnt++;
      }
}


static void unmap_shared_mem(void)
{
#ifndef NO_XSHM
      if (shm_ckpnt > 4) XShmDetach(display, &xshminfo);
      if (shm_ckpnt > 3) shmdt(xshminfo.shmaddr);
      if (shm_ckpnt > 2) shmctl(xshminfo.shmid, IPC_RMID, 0);
#endif
      if (shm_ckpnt > 1) {
           XDestroyImage(image);
           if (!use_shm)
		wtfree(display_fb);
      }
      if (shm_ckpnt > 0) shm_ckpnt = 1;
}


void update_screen(Framebuffer *fb)
{
     if (blocky_mode) {
	  if (dither_mode)
	       expand_dither(render_fb, display_fb,
			     fb->fb_width, fb->fb_height);
	  else
	       expand(render_fb, display_fb, fb->fb_width, fb->fb_height);
     } else {
	  if (dither_mode)
	       dither(render_fb, display_fb, fb->fb_width, fb->fb_height);
     }

#ifndef NO_XSHM
     if (use_shm)
	  XShmPutImage(display, wtwin, gc, image, 0, 0, 0, 0,
		       display_width, display_height, 0);
     else
#endif
	  XPutImage(display, wtwin, gc, image, 0, 0, 0, 0,
                    display_width, display_height);
  
     XSync(display, 0);
}


Pixel *get_framebuffer_memory(int width, int height)
{
     return (Pixel *) render_fb;
}
