/****************************************************************************
*                xwindows.c
*
*  This module implements X-windows specific routines.
*
*  from Persistence of Vision Raytracer
*  Copyright 1993 Persistence of Vision Team
*---------------------------------------------------------------------------
*  NOTICE: This source code file is provided so that users may experiment
*  with enhancements to POV-Ray and to port the software to platforms other 
*  than those supported by the POV-Ray Team.  There are strict rules under
*  which you are permitted to use this file.  The rules are in the file
*  named POVLEGAL.DOC which should be distributed with this file. If 
*  POVLEGAL.DOC is not available or for more info please contact the POV-Ray
*  Team Coordinator by leaving a message in CompuServe's Graphics Developer's
*  Forum.  The latest version of POV-Ray may be found there as well.
*
* This program is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
*
*****************************************************************************/


 /*
 * Here are some routines I wrote which implement +d option on unix computers
 * running X-windows. For now, only black and white output is supported. If I
 * get access to a computer with a color monitor, I'll probably add support
 * for colors to my routines.
 * 
 * In future I'll probably add some more dithering methods. I have tested these
 * routines on SUN 3 and SUN 4. I'm using the tvtwm window manager.
 * 
 * If you have some suggestions to my source code or if you change anything,
 * please let me know. I can be reached at the following address: Marek
 * Rzewuski, Avstikkeren 11, 1156 Oslo 11, Norway or marekr@ifi.uio.no on
 * Internet.
 */

/*
 * 91-07-29 Allan H. Wax (Wax.OSBU_South@Xerox.COM) Hacked this module so it
 * can be used with either a colour or B&W screen. Made all variables local
 * to this routine (no need for more globals if no one ever uses them). Fixed
 * bug in refresh routine. Use dither from colorx.c. Use xpov.icon instead of
 * theIcon.
 */

#include <stdio.h>
#include <X11/Xlib.h>		/* some Xlib stuff */
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include "xpov.icn"
#include <math.h>

#include "frame.h"
#include "povproto.h"

#define 	BORDER_WIDTH	2
#define	EV_MASK	(ButtonPressMask | \
		 KeyPressMask	 | \
		 ExposureMask    | \
		 StructureNotifyMask)

static Display *theDisplay;
static int      theScreen;
static int      theDepth;
static unsigned long theBlackPixel;
static unsigned long theWhitePixel;
static XEvent   theEvent;
static Window   theWindow, openWindow();
static GC       theGC;
static unsigned char *bitmap;	/* pointer to our picture bitmap */
static unsigned char *bitmapPos;/* position to the last drawn pixel in our
				 * bitmap */


static char    *display_name = 0;
static int     disp_wide, disp_high;
static XImage  *xi;
static char    *data;
static XGCValues gcvalues;
static long     colorpixels[256];
static Bool     iscolor;
static int 	screen_open = 0;

struct {
    void            (*Init) ();
    void            (*Close) ();
    void            (*Finished) ();
    void            (*Plot) ();
}               display;

/* global POVRay variables */

extern FRAME    Frame;
extern unsigned int Options;
extern char     DisplayFormat;
extern int      First_Line;

/*
 * Sets up a connection to the X server and stores informations about the
 * enviroment
 */

static          initX()
{
    theDisplay = XOpenDisplay(NULL);
    if (theDisplay == NULL) {
	fprintf(stderr, "ERROR: Cannot establish a connection to the X server %s\n",
		XDisplayName(NULL));
	exit(1);
    }
    theScreen = DefaultScreen(theDisplay);
    theDepth = DefaultDepth(theDisplay, theScreen);
    theWhitePixel = WhitePixel(theDisplay, theScreen);
    theBlackPixel = BlackPixel(theDisplay, theScreen);
}

/*
 * This procedure will do the following things: 1) 	Set up attributes
 * desired for the window 2)	Set up an icon to our window. 3) 	Send
 * hints to the window manager. 4) 	Open a window on the display 5)	Tell
 * the X to place the window on the screen 6)	Flush out all the queued up X
 * requests to the X server
 */

static Window   openWindow(x, y, width, height, flag, theNewGC)
int             x, y;
int             width, height;
int             flag;
GC             *theNewGC;
{
    XSetWindowAttributes theWindowAttributes;
    XSizeHints      theSizeHints;
    unsigned long   theWindowMask;
    Window          theNewWindow;
    Pixmap          theIconPixmap;
    XWMHints        theWMHints;


    /*
     * Set up some attributes for the window. Override_redirect tells the
     * window manager to deal width the window or leave it alone
     */

    theWindowAttributes.border_pixel = theBlackPixel;
    theWindowAttributes.background_pixel = theWhitePixel;
    theWindowAttributes.override_redirect = False;
    theWindowMask = CWBackPixel | CWBorderPixel | CWOverrideRedirect;

    /* Now, open out window */

    theNewWindow = XCreateWindow(theDisplay,
				 RootWindow(theDisplay, theScreen),
				 x, y,
				 width, height,
				 BORDER_WIDTH,
				 theDepth,
				 InputOutput,
				 CopyFromParent,
				 theWindowMask,
				 &theWindowAttributes);

    /* Create one iconbitmap */

    theIconPixmap = XCreateBitmapFromData(theDisplay,
					  theNewWindow,
					  xpov_bits,
					  xpov_width,
					  xpov_height);

    /*
     * Now tell the window manager where on screen we should place our
     * window.
     */

    theWMHints.icon_pixmap = theIconPixmap;
    theWMHints.initial_state = NormalState;	/* we don't want an iconized
						 * window when it's created */
    theWMHints.flags = IconPixmapHint | StateHint;

    XSetWMHints(theDisplay, theNewWindow, &theWMHints);

    theSizeHints.flags = PPosition | PSize;
    theSizeHints.x = x;
    theSizeHints.y = y;
    theSizeHints.width = width;
    theSizeHints.height = height;
    /*
     * theSizeHints.min_width	= width; theSizeHints.min_height	=
     * height; theSizeHints.max_width	= width; theSizeHints.max_height =
     * height;
     */

    XSetNormalHints(theDisplay, theNewWindow, &theSizeHints);


    if (createGC(theNewWindow, theNewGC) == 0) {
	XDestroyWindow(theDisplay, theNewWindow);
	return ((Window) 0);
    }

    /* make a name for our window */

    XStoreName(theDisplay, theNewWindow, "POVRay \0");

    /*
     * Now, could we please see the window on the screen? Until now, we have
     * dealed with a window which has been created but noe appeared on the
     * screen. Maping the window places it visibly	on the screen
     */

    XMapWindow(theDisplay, theNewWindow);
    XFlush(theDisplay);
    return (theNewWindow);
}

static          refreshWindow(theExposedWindow)
Window          theExposedWindow;
{
    int             i = 0, x = 0, y = First_Line;
    register unsigned char *dummy = bitmap;
    while (dummy != bitmapPos) {
	/* draw until most current pixel is drawn */
	if (*dummy)
	    XDrawPoint(theDisplay, theWindow, theGC, x, y);
	if (x == Frame.Screen_Width) {
	    x = 0;
	    y++;
	}
	else {
	    dummy++;
	    x++;
	    i++;
	}
    }
    XFlush(theDisplay);
}

/* Creates a new graphics context */

static int      createGC(theNewWindow, theNewGC)
Window          theNewWindow;
GC             *theNewGC;
{
    XGCValues       theGCValues;
    *theNewGC = XCreateGC(theDisplay,
			  theNewWindow,
			  (unsigned long) 0,
			  &theGCValues);

    if (*theNewGC == 0) {
	return (0);		/* error */
    }
    else {			/* set foreground and background defaults for
				 * the new GC */
	XSetForeground(theDisplay,
		       *theNewGC,
		       theBlackPixel);

	XSetBackground(theDisplay,
		       *theNewGC,
		       theWhitePixel);

	return (1);		/* OK */
    }
}

static          initEvents(theWindow)
Window          theWindow;
{
    XSelectInput(theDisplay,
		 theWindow,
		 EV_MASK);
}

void            BWFinished()
{

}


void            BWInit()
{
    int             i;

    /*
     * set some room for a bitmap for our picture. I've got to "remember" the
     * whole picture bacuse of resizing of the window, overlapping etc. Then
     * I've got to refresh the picture. This should be easy to convert to an
     * "color version" in future
     */

    bitmap = (unsigned char *) malloc(sizeof(unsigned char) *
				(Frame.Screen_Width * Frame.Screen_Height));
    bitmapPos = bitmap;
    if (bitmap == NULL)
	printf("ERROR: Can not allocate the buffer..\n");

    for (i = 0; i < (Frame.Screen_Width * Frame.Screen_Height); i++) {
	*bitmapPos++ = 0;
    }
    bitmapPos = bitmap;
    initX();
    theWindow = openWindow(0, 0, Frame.Screen_Width, Frame.Screen_Height, 0, &theGC);
    initEvents(theWindow);
    XFlush(theDisplay);
    XNextEvent(theDisplay, &theEvent);
    XFlush(theDisplay);
}				/* end of display initilazation */

void            BWClose()
{
    sleep(10);			/* an simple delay. 10 seconds. */
    XDestroyWindow(theDisplay, theWindow);
    XFlush(theDisplay);
    XCloseDisplay(theDisplay);
    free(bitmap);
}

void            BWPlot(x, y, Red, Green, Blue)
int             x, y;
unsigned char   Red, Green, Blue;
{
    unsigned char   color;
    long            thePixel;
    /* lets find if there are some events waiting for us */

    while (XPending(theDisplay) > 0) {	/* now deal with the events.. */
	XNextEvent(theDisplay, &theEvent);

	switch (theEvent.type) {
	  case Expose:
	    /* printf("Window is exposed.\n"); */
	    refreshWindow(theEvent.xany.window);
	    break;

	  case MapNotify:
	    /* printf("The window is mapped.\n"); */
	    refreshWindow(theEvent.xany.window);
	    break;

	  case ButtonPress:
	    /* printf("A mouse button was pressed.\n"); */
	    break;

	  case ConfigureNotify:
	    /* printf("The window configuration has been changed\n"); */
	    refreshWindow(theEvent.xany.window);
	    break;
	}
    }
    if (dither(x, y, Red, Green, Blue)) {
	thePixel = BlackPixel(theDisplay, theScreen);
	XDrawPoint(theDisplay, theWindow, theGC, x, y);
	XFlush(theDisplay);
    }
    else {
	thePixel = WhitePixel(theDisplay, theScreen);
    }
    *bitmapPos++ = thePixel;
}

/* so we can get at the dimensions */
extern FRAME    Frame;

/* x11 stuff */

static Bool     evtPred(d, e, a)
Display        *d;
XEvent         *e;
char           *a;
{
    return (True);
}

static void     BuildColormap()
{
    XColor          tmp;
    register unsigned short r, g, b, i;
    Colormap        cmap;

    /*
     * build a colormap that will contain 216, or 6^3 colors. We won't do
     * anything clever like adding extra grays in the leftover space.
     * Remember, this was written in a few hours.
     */
    cmap = DefaultColormap(theDisplay, theScreen);

    i = 0;
    for (r = 0; r < 6; r++) {
	for (g = 0; g < 6; g++) {
	    for (b = 0; b < 6; b++, i++) {
		tmp.red = (r * 36) << 8;
		tmp.green = (g * 36) << 8;
		tmp.blue = (b * 36) << 8;
		tmp.flags = DoRed | DoGreen | DoBlue;
		if (!XAllocColor(theDisplay, cmap, &tmp))
		    goto crap_out;
		colorpixels[i] = tmp.pixel;
	    }
	}
    }
crap_out:			/* couldn't get what we wanted.  Boo-hoo. */
    if (i != 216)
	fprintf(stderr, "warning: could only get %d colors.\n", i);
}

void            ColourFinished()
{
    XEvent         *report;
    /* If I knew enough about X, I'd have it optionally wait for a click */
    /* Just exit! */
#ifdef WAIT_ON_COMPLETION
    while (1) {
	XNextEvent(theDisplay, &report);
	XPutImage(theDisplay, theWindow, theGC, xi, 0, 0, 0, 0, Frame.Screen_Width,
		  Frame.Screen_Height);
    }
#endif
}

void            display_close()
{
	if(screen_open)
   		 (display.Close) ();
}

void            display_plot(x, y, Red, Green, Blue)
int             x, y;
unsigned char   Red, Green, Blue;
{
    (display.Plot) (x, y, Red, Green, Blue);
}

void            display_finished()
{
	if(screen_open)
    		(display.Finished) ();
}

void            ColourInit()
{
    Pixmap          icon;
    XSizeHints      hints;
    long            size, i;
    XSetWindowAttributes attrib;

    if ((theDisplay = XOpenDisplay(display_name)) == NULL) {
	fprintf(stderr, "Can't open X server %s.\n",
		XDisplayName(display_name));
	exit(1);
    }
    theScreen = DefaultScreen(theDisplay);

    /* screen dimensions */
    disp_wide = DisplayWidth(theDisplay, theScreen);
    disp_high = DisplayHeight(theDisplay, theScreen);

    theWindow = XCreateSimpleWindow(theDisplay, RootWindow(theDisplay, theScreen),
			      (disp_wide - Frame.Screen_Width) / 2,
			      (disp_high - Frame.Screen_Height) / 2,
			      Frame.Screen_Width,
			      Frame.Screen_Height, 2 /* border width */ ,
			  /* BlackPixel(theDisplay, theScreen), WhitePixel(theDisplay, theScreen)); */
			  WhitePixel(theDisplay, theScreen), BlackPixel(theDisplay, theScreen));

    /* create a graphics context for drawing */
    gcvalues.function = GXcopy;
    theGC = XCreateGC(theDisplay, theWindow, GCFunction, &gcvalues);

    /* make a cute icon */
    icon = XCreateBitmapFromData(theDisplay, theWindow, xpov_bits, xpov_width,
				 xpov_height);

    /* give a few clues about ourself */
    hints.flags = PPosition | PSize | PMinSize;
    hints.x = (disp_wide - Frame.Screen_Width) / 2;
    hints.y = (disp_high - Frame.Screen_Height) / 2;
    hints.width = Frame.Screen_Width;
    hints.height = Frame.Screen_Height;
    hints.min_width = 32;
    hints.min_height = 32;

    XSetStandardProperties(theDisplay, theWindow, "povray", "povray", icon, NULL, 0,
			   &hints);

    /* only want to know about expose events */
    XSelectInput(theDisplay, theWindow, ExposureMask);
    XMapWindow(theDisplay, theWindow);

    BuildColormap();

    size = Frame.Screen_Width * Frame.Screen_Height;
    data = (char *) XtMalloc(size);
    for (i = 0; i < size; i++)
	data[i] = 0;
    xi = XCreateImage(theDisplay, DefaultVisual(theDisplay, theScreen), 8, ZPixmap, 0, data,
	    Frame.Screen_Width, Frame.Screen_Height, 8, Frame.Screen_Width);
};

void            ColourClose()
{
    sleep(10);
    XDestroyImage(xi);
    XtFree(data);
    XDestroyWindow(theDisplay, theWindow);
    XCloseDisplay(theDisplay);
}

 /* static */ int dither(x, y, Red, Green, Blue)
register unsigned int x, y;
unsigned char   Red, Green, Blue;
{
    /*
     * Perform ordered dithering for halftone screen. For details, see
     * Graphics Gems, Ed. Andrew Glassner, p. 176-178 Floyd-Steinberg dither
     * would look better, but we would need more saved state, making the
     * coding a lot uglier.  Since this is just a display hack and not an
     * image processing package, I don't bother.
     */
    static unsigned int dm[] = {
	0, 192, 48, 240, 12, 204, 60, 252,
	128, 64, 176, 112, 140, 76, 188, 124,
	32, 224, 16, 208, 44, 236, 28, 220,
	160, 96, 144, 80, 172, 108, 156, 92,
	8, 200, 56, 248, 4, 196, 52, 244,
	136, 72, 184, 120, 132, 68, 180, 116,
	40, 232, 24, 216, 36, 228, 20, 212,
	168, 104, 152, 88, 164, 100, 148, 84
    };
    register unsigned long luminence =
    (307 * Red + 599 * Green + 118 * Blue) / 1024;
    return (luminence < dm[(x & 7) + ((y & 7) << 3)]);
}

void            ColourPlot(x, y, Red, Green, Blue)
int             x, y;
unsigned char   Red, Green, Blue;
{
    long            thePixel;
    int             r, g, b, rr,gg,bb;
    XEvent          report;
    int             luminence;
#ifdef CDITHER
    /* This quick hack does primitive dithering */
    static int dm[] = {
	  0, 192,  48, 240,  12, 204,  60, 252,
	128,  64, 176, 112, 140,  76, 188, 124,
	 32, 224,  16, 208,  44, 236,  28, 220,
	160,  96, 144,  80, 172, 108, 156,  92,
	  8, 200,  56, 248,   4, 196,  52, 244,
	136,  72, 184, 120, 132,  68, 180, 116,
	 40, 232,  24, 216,  36, 228,  20, 212,
	168, 104, 152,  88, 164, 100, 148,  84
    };
/* 
#define d(x,y) ((dm[((x) & 7) + ((y) & 7) << 3] & 0xff - 128) / 6)
*/

#define d(x,y) ((dm[((x) & 7) + (((y) & 7) << 3)] & 0xff - 128) / 3)
    rr = ((int)Red  )+ d(x,y); if (rr>255) rr=255; if (rr<0) rr=0;
    gg = ((int)Green)+ d(x,y); if (gg>255) gg=255; if (gg<0) gg=0;
    bb = ((int)Blue )+ d(x,y); if (bb>255) bb=255; if (bb<0) bb=0;
#else
    rr = Red;
    gg = Green;
    bb = Blue;
#endif
    /* scale rgb to be in our 216 color space */
    r = (rr * 216) / (256 * 36);
    g = (gg * 216) / (256 * 36);
    b = (bb * 216) / (256 * 36);

    /* find the pixel we got */
    thePixel = colorpixels[(r * 36) + (g * 6) + b];
    XPutPixel(xi, x, y, thePixel);
    /* on each pixel, copy a scanline.  Inefficient, but so what?  See above. */
    XPutImage(theDisplay, theWindow, theGC, xi, 0, y, 0, y, Frame.Screen_Width, 1);

    /* poll for exposure events */
    if (XCheckIfEvent(theDisplay, &report, evtPred, "blah")) {
	XPutImage(theDisplay, theWindow, theGC, xi, 0, 0, 0, 0, Frame.Screen_Width, y + 1);
    }
}

void unix_init_POVRAY PARAMS((void))
{
}

#ifdef DOMAIN
int             matherr(x)
struct exception *x;
{
    switch (x->type) {
      case DOMAIN:
      case OVERFLOW:
	x->retval = 1.0e17;
	break;

      case SING:
      case UNDERFLOW:
	x->retval = 0.0;
	break;

      default:
	break;
    }
    return (1);
}
#endif
extern void     BWInit(), BWClose(), BWFinished(), BWPlot();

void            display_init(int xx, int yy)
{
    Pixmap          icon;
    XSizeHints      hints;
    long            size, i;
    XSetWindowAttributes attrib;

    if ((theDisplay = XOpenDisplay(display_name)) == NULL) {
	fprintf(stderr, "Can't open X server %s.\n",
		XDisplayName(display_name));
	exit(1);
    }
    
    screen_open = 1;
    
    theScreen = DefaultScreen(theDisplay);
    iscolor = (XDisplayCells(theDisplay, theScreen) > 2);

    if (iscolor) {
	display.Init = ColourInit;
	display.Close = ColourClose;
	display.Finished = ColourFinished;
	display.Plot = ColourPlot;
    }
    else {
	display.Init = BWInit;
	display.Close = BWClose;
	display.Finished = BWFinished;
	display.Plot = BWPlot;
    }

    XCloseDisplay(theDisplay);
    (display.Init) ();

}
