/* xquinto.c: game, objective is to reverse color of all squares.
   complie with: cc -o xquinto xquinto.c -lX11
*/
   
   
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include "copyright.h"

#define HEIGHT 20
#define WIDTH 20

int myarr[HEIGHT][WIDTH];
int height;
int width;
int showmove = 0;
char colorname[4][64] =
{
    "notused","red","blue","yellow"
};

char *getenv();
void parseOptions();
void extractOptions();
void XStuff();
void newscreen();
void draw_screen();
void getXevent();
void CreatePixmaps();
void getXevent();
void DrawShadow();
void keyboard();
void redraw_array();
void DrawCell();

void main(argc,argv)
    int argc;
    char *argv[];
{
    int i = 1;
    int done = 0;

    height = 5;
    width = 5;
    while (i < argc && argv[i][0] == '-') {
        if (strcmp(argv[i], "-height") == 0) {
            height = atoi(argv[i+1]);
            i++;
        }
        else if (strcmp(argv[i], "-width") == 0) {
            width = atoi(argv[i+1]);
            i++;
        }
        else if (strcmp(argv[i], "-square") == 0) {
            width = atoi(argv[i+1]);
            height = width;
            i++;
        }
        else if (strcmp(argv[i], "-color1") == 0) {
            sprintf(colorname[1], argv[i+1]);
            i++;
        }
        else if (strcmp(argv[i], "-color2") == 0) {
            sprintf(colorname[2], argv[i+1]);
            i++;
        }
        else if (strcmp(argv[i], "-color3") == 0) {
            sprintf(colorname[3], argv[i+1]);
            i++;
        }
        i++;
    }

    width = (width > WIDTH) ? WIDTH : width;
    height = (height > HEIGHT) ? HEIGHT : height;

    parseOptions(argc,argv);
    extractOptions();
    XStuff(argc,argv);

    newscreen();
    draw_screen();
    while(!done) getXevent(); 
}

int InArray(x,y)
{
    return ((x >= 0 && x < width) && (y >= 0 && y < height)) ? 1 : 0;
}


void MarkCell(x,y)
{
    if (InArray(x,y)) {
        myarr[x][y] = 3 - myarr[x][y];
        DrawCell(x,y);
    }
    if (InArray(x-1,y)) {
        myarr[x-1][y] = (2 + myarr[x-1][y])%4;
        DrawCell(x-1,y);
    }
    if (InArray(x+1,y)) {
        myarr[x+1][y] = (2 + myarr[x+1][y])%4;
        DrawCell(x+1,y);
    }
    if (InArray(x,y+1)) {
        myarr[x][y+1] = (2 + myarr[x][y+1])%4;
        DrawCell(x,y+1);
    }
    if (InArray(x,y-1)) {
        myarr[x][y-1] = (2 + myarr[x][y-1])%4;
        DrawCell(x,y-1);
    }
}

int ReadCell(x,y)
{
    return myarr[x][y];
}

void newscreen()
{
    int i,j,k;

    for(i=0; i < width; i++)
        for(j=0; j < height; j++)
            myarr[i][j] = 0;
}

/* X include files */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/X.h>
#include <X11/Xresource.h>
#include <strings.h>

#include "gray.xbm"
#include "white.xbm"


Display *display;
int screen;
Window  window;
GC gc;
XSizeHints hint;
Pixmap patpix[4];

Colormap cmap;
unsigned long foreground, background;
unsigned long getcolor();
int depth;
int mono = 0;

#define SWIDTH  40 /* cell   */
#define SHEIGHT 40
#define BWIDTH  14 /* border */
#define BHEIGHT 14

int quit()
{
    exit(1);
}

static XrmDatabase rDB;
static XrmDatabase homeDB;
static XrmDatabase commandlineDB;

void parseOptions(argc,argv)
    int argc;
    char *argv[];
{
    XrmValue value;
    char *str_type[40];
    char filename[1024];
    char *sptr;
    char DisplayName[256];
    static int opTableEntries = 4;
    static XrmOptionDescRec opTable[] =
    {
        {"-display", ".display", XrmoptionSepArg, (caddr_t) NULL},
        {"-geometry",  ".geometry", XrmoptionSepArg, (caddr_t) NULL},
        {"-mono",  ".mono", XrmoptionIsArg, (caddr_t) NULL},
        {"-showmove",  ".showmove", XrmoptionIsArg, (caddr_t) NULL}
    };

    /* Get command line options */
    XrmParseCommand(&commandlineDB, opTable, opTableEntries, "xquinto",
        &argc, argv);

    DisplayName[0] = '\0';
    if(XrmGetResource(commandlineDB, "xquinto.display",
                      "Xquinto.Display", str_type, &value) == True)
        strncpy(DisplayName,value.addr,(int) value.size);
    if(!(display = XOpenDisplay(DisplayName))) {
        fprintf(stderr,"Error: can't open display\n");
        exit(1);
    } 
    screen = DefaultScreen(display);
    depth = DefaultDepth(display,screen);
    if(depth == 1) mono = 1;

    sptr = getenv("HOME");
    strncpy(filename,sptr,sizeof(filename));
    strncat(filename,"/.Xdefaults",sizeof(filename) - strlen(filename));
    filename[sizeof(filename)-1] = '\0';
    homeDB = XrmGetFileDatabase(filename);
    XrmMergeDatabases(homeDB,&rDB);
    XrmMergeDatabases(commandlineDB,&rDB);

}

void extractOptions()
{
    char *str_type[40];
    char str[20];
    XrmValue value;
    int i;

    /* default program-specified window position and size */
    hint.x = 20; hint.y = 30;
    hint.width = width*SWIDTH + 2*BWIDTH;
    hint.height = height*SHEIGHT + 2*BHEIGHT;

    if(XrmGetResource(rDB, "xquinto.mono",
                      "Xquinto.mono", str_type, &value) == True)
        mono = 1;

    if(XrmGetResource(rDB, "xquinto.showmove",
                      "Xquinto.showmove", str_type, &value) == True)
        showmove = 1;

    for(i=1; i < 4; i++) {
        sprintf(str,"xquinto.color%1d",i);
        if (XrmGetResource(rDB, str,
            "Xquinto.Color", str_type, &value) == True) {
            strncpy(colorname[i],value.addr,(int) value.size);
            colorname[i][value.size] = '\0';
        }
    }
}


void XStuff(argc,argv)
    int argc;
    char *argv[];
{

    /* default pixel values */
    cmap = DefaultColormap(display,screen);
    background = WhitePixel(display,screen);
    foreground = BlackPixel(display,screen);

    /* window creation */
    window = XCreateSimpleWindow (display,
                DefaultRootWindow(display),
                hint.x, hint.y, hint.width, hint.height,
                5, foreground, getcolor("gray", background));
    XSetStandardProperties(display, window, argv[0], argv[0],
                           None, argv, argc, &hint);

    /* GC creation and initialization */
    gc = XCreateGC(display, window, 0, 0);
    XSetBackground(display, gc, background);
    XSetForeground(display, gc, foreground);

    /* input event selection */
    XSelectInput(display, window,
        Button1MotionMask | Button2MotionMask | Button3MotionMask |
         ButtonPressMask | ButtonReleaseMask | 
        KeyPressMask | ExposureMask);

    CreatePixmaps();

    /* window mapping */
    XMapRaised(display, window);
    XFlush(display);
}


void CreatePixmaps()
{
    char *pixstr = ".";
    unsigned long patbg, patfg;

    if(!mono) patbg = getcolor(colorname[1] ,background); 
    patpix[0] = XCreatePixmapFromBitmapData(display,window,
                        gray_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    patpix[1] = XCreatePixmapFromBitmapData(display,window,
                        gray_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    if(!mono) patbg = getcolor(colorname[2],background); 
    patpix[2] = XCreatePixmapFromBitmapData(display,window,
                        white_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    patpix[3] = XCreatePixmapFromBitmapData(display,window,
                        white_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);

    if(showmove) { /* show the '.' on clicked squares */
        patfg = getcolor(colorname[3],foreground);  
        XSetForeground(display, gc, patfg);
        XDrawString(display,patpix[1],gc,SWIDTH/2-3,SHEIGHT/2,pixstr,1);
        XDrawString(display,patpix[3],gc,SWIDTH/2-3,SHEIGHT/2,pixstr,1);
        XSetForeground(display, gc, foreground);
    }
}


unsigned long getcolor(s, monocolor)
    char *s;
    unsigned long monocolor;
{
    XColor exact_def;

    if(!mono) {
        XParseColor(display,cmap,s,&exact_def);
        XAllocColor(display,cmap,&exact_def);
        return exact_def.pixel;
    }
    else return monocolor;
}

void getXevent()
{
    char text[10];
    static int x,y;
    int newx,newy;
    KeySym  key;
    XEvent event;

    XNextEvent(display, &event);
    switch(event.type)
    {
        case Expose:
            if(event.xexpose.window == window)
                if(event.xexpose.count == 0)
                    draw_screen();
            break;
        case KeyPress:
            if(event.xkey.window == window) {
                XLookupString(&event, text, 1, &key, 0);
                keyboard(text[0]);
                text[0] = '0';
            }
            break;
        case MotionNotify:
            newx = (event.xmotion.x - BWIDTH)/SWIDTH;
            newy = (event.xmotion.y - BHEIGHT)/SHEIGHT;
            if(newx != x || newy != y) {
                x = newx; y = newy;
            }
            break;
        case ButtonPress:
            break;
        case ButtonRelease:
            if(event.xkey.window == window) {
                x = (event.xbutton.x - BWIDTH)/SWIDTH;
                y = (event.xbutton.y - BHEIGHT)/SHEIGHT;
                if(InArray(x,y)) MarkCell(x,y);
            }
            break;
        default:
            break;
    }
}


void draw_screen()
{
    XClearWindow(display, window);
    DrawShadow(0, 0, width*SWIDTH + 2*BWIDTH - 1, 
               height*SHEIGHT + 2*BHEIGHT - 1, 3); 
    DrawShadow(BWIDTH-4, BHEIGHT-4, BWIDTH + width*SWIDTH + 3,
               BHEIGHT + height*SHEIGHT +3, -3); 
    redraw_array();
}

void DrawShadow(x1,y1,x2,y2,dir)
    int x1;
    int y1;
    int x2;
    int y2;
    int dir;
{
    int i;
    GC highlightgc,shadowgc;

    highlightgc = XCreateGC(display, window, 0, 0);
    shadowgc = XCreateGC(display, window, 0, 0);

    if(dir < 0) {
        XSetForeground(display, highlightgc, foreground);
        XSetForeground(display, shadowgc, background);
        dir = -dir;
    }
    else {
        XSetForeground(display, shadowgc, foreground);
        XSetForeground(display, highlightgc, background);
    }

    for(i=0; i < dir; i++) {
        XDrawLine(display,window,highlightgc,x1,y1,x1,y2);
        XDrawLine(display,window,shadowgc,x1,y2,x2,y2);
        XDrawLine(display,window,shadowgc,x2,y1,x2,y2);
        XDrawLine(display,window,highlightgc,x1,y1,x2,y1);
        x1++; x2--; y1++; y2--;
    }
}

void keyboard(c)
    int c;
{
    switch(c){
        case 'q':
            quit();
            break;
        case 'w':
            width--;
            break; 
        case 'W':
            width++;
            break; 
        case 'h':
            height--;
            break; 
        case 'H':
            height++;
            break; 
        case 's':
            width--;
            height--;
            break; 
        case 'm':
            width++;
            height++;
            break; 
        case 'c':
            break;
        default:
            return;
    }
    width = (width > WIDTH) ? WIDTH : width;
    height = (height > HEIGHT) ? HEIGHT : height;
    width = (width < 2) ? 2 : width;
    height = (height < 2) ? 2: height;
    newscreen();
    draw_screen();


}

void redraw_array()
{
    int i, j;

    for(i=0; i <height; i++)
        for(j=0; j < width; j++) 
            DrawCell(j,i); 
}

void DrawCell(x,y)
    int x;
    int y;
{
    int val = ReadCell(x,y);

    if(InArray(x,y)) 
        XCopyArea(display, patpix[val], window, gc, 0, 0, SWIDTH
                  ,SHEIGHT, x*SWIDTH + BWIDTH, y*SHEIGHT + BHEIGHT);
}



