/*
 * xgraphics.c
 *
 * Copyright (C) 1989, 1991 Craig E. Kolb, Rod G. Bogart
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 * 
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 * xgraphics.c,v 1.1.1.1 1995/02/27 07:38:48 explorer Exp
 *
 * xgraphics.c,v
 * Revision 1.1.1.1  1995/02/27 07:38:48  explorer
 * Import of Netshade
 *
 * Revision 1.1.1.1  1994/11/27  03:55:48  explorer
 * Initial import
 *
 * Revision 4.1  1994/08/09  08:06:31  explorer
 * Bump version to 4.1
 *
 * Revision 1.1.1.1  1994/08/08  04:52:27  explorer
 * Initial import.  This is a prerelease of 4.0.6enh3, or 4.1 possibly.
 *
 * Revision 4.0  91/07/17  17:37:32  kolb
 * Initial version.
 * 
 */

#include <stdio.h>
#include <math.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

char *display_name = NULL;
Display *dpy = NULL;
Screen *scrn;
Visual *vis;
Colormap cmap;
GC gc;
Window win;
int screen_height;
int gray;

unsigned long colormap[256];
int max_colors;
double one_over_gamma = 0.4;

/*****************************************************************
 * Sets the gray color map for the device.  A 2.5 gamma map is used
 * by default.
 */
static
setup_gray_gamma_map()
{
    int cc, col;
    int gamma_color;

    XColor xcolor;

    gray = 1;
    /* Use the default colormap if possible. */
    if ( vis == DefaultVisualOfScreen( scrn ) )
        cmap = DefaultColormapOfScreen( scrn );
    else
        cmap = XCreateColormap( dpy, RootWindowOfScreen( scrn ),
                               vis, AllocNone );

    /* try to share with current colormap */
    for (max_colors = 256; max_colors >= 16; max_colors = max_colors >> 1) {
        xcolor.flags= DoRed | DoGreen | DoBlue;
        for(col=0; col < max_colors; col++) {
            gamma_color = (pow((float) col / (float) max_colors,
                               one_over_gamma) * 65536);
            xcolor.red= gamma_color;
            xcolor.green= gamma_color;
            xcolor.blue= gamma_color;
            if (!XAllocColor(dpy, cmap, &xcolor)) {
                for (cc=0; cc < col; cc++)
                    XFreeColors(dpy, cmap, &colormap[cc], 1, 0);
                col = 0;
                break;
            }
            colormap[col] = xcolor.pixel;
        }
        if (col)
            return;
    }

    /* use new map */
    cmap = XCreateColormap( dpy, RootWindowOfScreen( scrn ),
                           vis, AllocNone );
    if (cmap == NULL)  {
        fprintf(stderr, "Could not create color map for visual\n");
        exit(-2);
    }
    for(cc=0; cc < 256; cc++)
        if (!XAllocColorCells(dpy, cmap, False, NULL, 0, &colormap[cc], 1))
            break;
    max_colors = cc;

    xcolor.flags= DoRed | DoGreen | DoBlue;
    for(col=0; col < max_colors; col++) {
        xcolor.pixel= colormap[col];
        gamma_color = (pow((float) col / (float) max_colors,
                           one_over_gamma) * 65536);
        xcolor.red= gamma_color;
        xcolor.green= gamma_color;
        xcolor.blue= gamma_color;
        XStoreColor(dpy, cmap, &xcolor);
    }
}

/*****************************************************************
 * Sets the color map for the device.  A 2.5 gamma map is used
 * by default, with a direct 6x7x5 colormap
 */
static
setup_color_gamma_map()
{
    static int first_try = 1;
    int cc, col;
    int r, g, b;
    int done;

    XColor xcolor;

    /* Use the default colormap if possible. */
    if (first_try && vis == DefaultVisualOfScreen(scrn)) {
        cmap = DefaultColormapOfScreen(scrn);
    } else {
        cmap = XCreateColormap(dpy, RootWindowOfScreen(scrn),
                               vis, AllocNone );
    }

    /* try to share with current colormap */
    xcolor.flags= DoRed | DoGreen | DoBlue;
    for (max_colors = 6; max_colors >= 2; --max_colors) {
	done = 1; /* gets unset on failure */
	col = 0;
	for (r = 0; r < max_colors; ++r) {
	    xcolor.red = pow((float) r / (float) max_colors,
		one_over_gamma) * 65536;
	    for (g = 0; g < max_colors; ++g) {
		xcolor.green = pow((float) g / (float) max_colors,
		    one_over_gamma) * 65536;
		for (b = 0; b < max_colors; ++b) {
		    xcolor.blue = pow((float) b / (float) max_colors,
					one_over_gamma) * 65536;
		    if (!XAllocColor(dpy, cmap, &xcolor)) {
			for (cc=0; cc < col; cc++)
			    XFreeColors(dpy, cmap, &colormap[cc], 1, 0);
			r = g = b = 99;
		        done = 0;
		        break;
		    }
		    colormap[col++] = xcolor.pixel;
		}
	    }
	}
	if (done)
	    break;
    }
    if (!done) {
	if (first_try) {
	    /* go back and try with a private color map */
	    first_try = 0;
	    setup_color_gamma_map();
	}
	/* our smallest color set (2x2x2 = 8) is smaller than the
	 * grayscale code (16), but we still try the gray map because
	 * it might be able to share colors.
	 */
        fprintf(stderr, "Could not create color map -- trying grayscale\n");
	setup_gray_gamma_map();
	return;
    }
    fprintf(stderr, "using standard %dx%dx%d colormap\n", max_colors, max_colors, max_colors);
    gray = 0;
}

GraphicsInit(xsize, ysize, name, gray)
int xsize, ysize;
char *name;
int gray;
{
    int win_size;
    XSetWindowAttributes attrs;
    XSizeHints sh;

    /* Open the display. */
    if ( ! dpy )
    {
        XVisualInfo vis_temp, *vis_list, *max_vis;
        int n_ret, i;

        dpy = XOpenDisplay( display_name );
        if ( ! dpy )
        {
            fprintf( stderr, "rayview: Can't open display %s\n",
                     XDisplayName( display_name ) );
            exit(1);
        }

        /* Get a PseudoColor visual that has the maximum number of planes. */
        vis_temp.class = PseudoColor;
        vis_list = XGetVisualInfo( dpy, VisualClassMask, &vis_temp, &n_ret );
        if ( n_ret == 0 )
        {
            fprintf(stderr,
                    "Can't find any PseudoColor visual from display %s.\n",
                    XDisplayName( display_name ));
            exit(1);
        }
        max_vis = &vis_list[0];
        for ( i = 1; i < n_ret; i++ )
        {
            if ( max_vis->depth < vis_list[i].depth )
                max_vis = &vis_list[i];
        }
        vis = max_vis->visual;
        scrn = ScreenOfDisplay( dpy, max_vis->screen );
        gc = DefaultGCOfScreen( scrn );

	if (gray) {
	    setup_gray_gamma_map();
	} else {
            setup_color_gamma_map();
	}

        XFree( (char *)vis_list );
    }

    screen_height = ysize;

    attrs.backing_store = Always;
    attrs.colormap = cmap;
    attrs.event_mask = ExposureMask;
    attrs.background_pixel = BlackPixelOfScreen(scrn);
    attrs.border_pixel = WhitePixelOfScreen(scrn);

    win = XCreateWindow( dpy, RootWindowOfScreen( scrn ),
                         0, 0, xsize, ysize, 2,
                         0, 0, vis,
                         CWBackingStore | CWColormap | CWEventMask |
                         CWBackPixel | CWBorderPixel,
                         &attrs );

    sh.flags = PSize | PMinSize | PMaxSize;
    sh.width = sh.min_width = sh.max_width = xsize;
    sh.height = sh.min_height = sh.max_height = ysize;
    XSetStandardProperties( dpy, win, name, name, None, NULL, 0, &sh );

    XMapWindow( dpy, win );

    XFlush( dpy );
}

/*
 * Draw the pixel at (xp, yp) in the color given by the rgb-triple,
 * 0 indicating 0 intensity, 255 max intensity.
 */
GraphicsDrawPixel(xp, yp, color)
int xp, yp;
unsigned char color[3];
{
    int val;

    if (gray) {
	val = (0.35*color[0] + 0.55*color[1] + 0.10*color[2]) / 256.0 * max_colors;
    } else {
	val = color[0] / 256.0 * max_colors;
	val *= max_colors;
	val += color[1] / 256.0 * max_colors;
	val *= max_colors;
	val += color[2] / 256.0 * max_colors;
    }
    XSetForeground( dpy, gc, colormap[val] );
    XFillRectangle( dpy, win, gc, xp, (screen_height - (yp + 1)),
                    1, 1 );
}

/*
 * Draw the rect with lower left corner (xp, yp) and upper right
 * corner (xp+ys, yp+ys).  The colors of the l-l, l-r, u-r, and u-l
 * corners are given as arrays of unsigned chars as above.
 */
GraphicsDrawRectangle(xp, yp, xs, ys, ll, lr, ur, ul)
int xp, yp, xs, ys;
unsigned char ll[3], lr[3], ur[3], ul[3];
{
    int val;

    ll[0] = (ll[0] + lr[0] + ur[0] + ul[0]) / 4;
    ll[1] = (ll[1] + lr[1] + ur[1] + ul[1]) / 4;
    ll[2] = (ll[2] + lr[2] + ur[2] + ul[2]) / 4;
    if (gray) {
	val = (0.35*ll[0] + 0.55*ll[1] + 0.10*ll[2]) / 256.0 * max_colors;
    } else {
	val = ll[0] / 256.0 * max_colors;
	val *= max_colors;
	val += ll[1] / 256.0 * max_colors;
	val *= max_colors;
	val += ll[2] / 256.0 * max_colors;
    }
    XSetForeground( dpy, gc, colormap[val] );
    XFillRectangle( dpy, win, gc, xp, (screen_height - (yp + ys + 1)),
                    xs+1, ys+1 );
    XFlush( dpy );
}

GraphicsLeftMouseEvent()
{
    Window root_ret, child_ret;
    int rx, ry, wx, wy;
    unsigned int mask;

    if (XQueryPointer(dpy, win, &root_ret, &child_ret,
                      &rx, &ry, &wx, &wy, &mask)) {
        return mask & Button1Mask;
    }
    else
        return 0;
}

GraphicsMiddleMouseEvent()
{
    Window root_ret, child_ret;
    int rx, ry, wx, wy;
    unsigned int mask;

    if (XQueryPointer(dpy, win, &root_ret, &child_ret,
                      &rx, &ry, &wx, &wy, &mask)) {
        return mask & Button2Mask;
    }
    else
        return 0;
}

GraphicsRightMouseEvent()
{
    Window root_ret, child_ret;
    int rx, ry, wx, wy;
    unsigned int mask;

    if (XQueryPointer(dpy, win, &root_ret, &child_ret,
                      &rx, &ry, &wx, &wy, &mask)) {
        return mask & Button3Mask;
    }
    else
        return 0;
}

GraphicsGetMousePos(x, y)
int *x, *y;
{
    Window root_ret, child_ret;
    int rx, ry, wx, wy;
    unsigned int mask;

    if (XQueryPointer(dpy, win, &root_ret, &child_ret,
                      &rx, &ry, &wx, &wy, &mask)) {
        *x = wx;
        *y = screen_height - wy - 1;
    }
    else {
        *x = 0;
        *y = 0;
    }
}

GraphicsRedraw()
{
        XEvent event;
        if (XCheckTypedEvent(dpy, Expose, &event)) {
                XSetForeground( dpy, gc, colormap[0] );
                XFillRectangle( dpy, win, gc, event.xexpose.x, event.xexpose.y,
                    event.xexpose.width, event.xexpose.height );
                XFlush( dpy );
                return 1;
        }
        else
                return 0;
}

