#include <stdio.h>
#include <xview/xview.h>
#include <xview/canvas.h>
#include <xview/panel.h>
#include <xview/xv_xrect.h>
#include <xview/notice.h>
#include <X11/Xlib.h>
#include <strings.h>
#include "copyright.h"

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

#define HEIGHT  20  /* of game */
#define WIDTH   20
#define SWIDTH  40  /* of cells */
#define SHEIGHT 40 
#define BWIDTH  14  /* of border */
#define BHEIGHT 14

void keyboard();
void draw_screen();
void DrawCell();
void newscreen();
unsigned long getcolor();
int quit();

int myarr[HEIGHT][WIDTH];
int height;
int width;
int showmove;
int clearscreen = 0;
int update_x, update_y;
char colorname[4][64] =
{
    "notused","red","blue", "yellow"
};
Pixmap patpix[4];
Pixmap showpix[2];
Colormap cmap;
Display *display;
Window xwin;
Xv_Window xv_win;
GC gc;
int screen;
unsigned long foreground, background;
int mono = 0;


main(argc,argv)
    int argc;
    char *argv[];
{
    int i = 1;
    Frame frame;
    Canvas canvas;
    Panel panel;

    void canvas_proc();
    void event_proc();
    void clear_proc();
    void width_proc();
    void height_proc();
    void show_proc();
    Xv_opaque help_proc();
    Xv_opaque quit_proc();
    void startup();
    
    xv_init(XV_INIT_ARGC_PTR_ARGV, &argc, argv, NULL);

    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], "-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++;
        }
        else if (strcmp(argv[i], "-mono") == 0) {
            mono = 1;
        }
        else if (strcmp(argv[i], "-square") == 0) {
            width = atoi(argv[i+1]);
            height = width;
            i++;
        }
        i++;
    }
    width = (width > WIDTH) ? WIDTH : width;
    height = (height > HEIGHT) ? HEIGHT : height;

    
    frame = (Frame)xv_create(NULL, FRAME,
                             FRAME_LABEL, argv[0],
                             XV_WIDTH, 140 + width*SWIDTH, 
                             XV_HEIGHT, height*SHEIGHT + 2*BHEIGHT + 57, 
                             NULL);

    panel = xv_create(frame, PANEL, 
                      XV_HEIGHT, 55,
                      NULL);

    (void)xv_create(panel, PANEL_BUTTON,
                    PANEL_LABEL_STRING, "Quit",
                    PANEL_NOTIFY_PROC, quit_proc,
                    NULL);

    (void)xv_create(panel, PANEL_BUTTON,
                    PANEL_LABEL_STRING, "Clear",
                    PANEL_NOTIFY_PROC, clear_proc,
                    NULL);

    (void)xv_create(panel, PANEL_BUTTON,
                    PANEL_LABEL_STRING, "Show Moves",
                    PANEL_NOTIFY_PROC, show_proc,
                    NULL);

    (void)xv_create(panel, PANEL_BUTTON,
                    PANEL_LABEL_STRING, "Help",
                    PANEL_NOTIFY_PROC, help_proc,
                    NULL);
                    
    (void)xv_create(panel, PANEL_NUMERIC_TEXT,
                    PANEL_LABEL_STRING, "Width",
                    PANEL_VALUE, width,
                    PANEL_NEXT_ROW, -1,
                    PANEL_MIN_VALUE, 1,
                    PANEL_MAX_VALUE, 20,
                    PANEL_VALUE_DISPLAY_LENGTH, 1,
                    PANEL_NOTIFY_PROC, width_proc,
                    NULL);
    
    (void)xv_create(panel, PANEL_NUMERIC_TEXT,
                    PANEL_LABEL_STRING, "Height",
                    PANEL_VALUE, height,
                    PANEL_MIN_VALUE, 1,
                    PANEL_MAX_VALUE, 20,
                    PANEL_VALUE_DISPLAY_LENGTH, 1,
                    PANEL_NOTIFY_PROC, height_proc,
                    NULL);

    canvas = xv_create(frame, CANVAS,
                       CANVAS_REPAINT_PROC, canvas_proc,
                       CANVAS_X_PAINT_WINDOW, TRUE,
                       CANVAS_AUTO_EXPAND, TRUE,
                       CANVAS_AUTO_SHRINK, TRUE,
                       NULL);
    window_fit(frame);

    startup(frame, canvas);
    xv_set(canvas_paint_window(canvas),
           WIN_EVENT_PROC, event_proc,
           WIN_CONSUME_EVENTS,
             KBD_DONE, KBD_USE, LOC_DRAG, LOC_MOVE, LOC_WINENTER,
             LOC_WINEXIT, WIN_ASCII_EVENTS, WIN_MOUSE_BUTTONS,
             NULL,
           NULL);
    update_x = -1;
    xv_main_loop(frame);
}

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

mymarkcell(x, y)
    int x;
    int y;

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

void clear_proc(item, event)
    Panel_item item;
    Event *event;
{
    clearscreen = 1;
    newscreen();
    canvas_proc((Canvas)NULL, xv_win, (Display*)xv_get(xv_win, XV_DISPLAY),
                xwin, (Xv_xrectlist *)NULL);

}

void width_proc(item, event)
    Panel_item item;
    Event *event;
{
    width = xv_get(item, PANEL_VALUE);
    clearscreen = 1;
    newscreen();
    canvas_proc((Canvas)NULL, xv_win, (Display*)xv_get(xv_win, XV_DISPLAY),
                xwin, (Xv_xrectlist *)NULL);

}

void height_proc(item, event)
    Panel_item item;
    Event *event;
{
    height = xv_get(item, PANEL_VALUE);
    clearscreen = 1;
    newscreen();
    canvas_proc((Canvas)NULL, xv_win, (Display*)xv_get(xv_win, XV_DISPLAY),
                xwin, (Xv_xrectlist *)NULL);

}

void show_proc(item, event)
    Panel_item item;
    Event *event;
{
    showmove = 1 - showmove;
    clearscreen = 1;
    if(showmove) { /* show the '.' on clicked squares */
        patpix[1] = showpix[0];
        patpix[3] = showpix[1];
    }
    else {
        patpix[1] = patpix[0];
        patpix[3] = patpix[2];
    }
    canvas_proc((Canvas)NULL, xv_win, (Display*)xv_get(xv_win, XV_DISPLAY),
                xwin, (Xv_xrectlist *)NULL);

}

Xv_opaque help_proc(item, event)
    Panel_item item;
    Event *event;
{
    int result;
    Panel panel = (Panel)xv_get(item, PANEL_PARENT_PANEL);
    
    result = notice_prompt(panel, NULL,
               NOTICE_MESSAGE_STRINGS,
                 "Xquinto is a puzzle on a rectangular board. Clicking on",
                 "any square reverses its color, and that of its neighbors",
                 "that share an edge (not the diagonal ones). The object,",
                 "starting from a red board, is to color each square blue.",
                 "   ",
                 "The 5 by 5 puzzle can be solved!",
                 "   ",
                 "Dev Joneja, dj7@cunixf.cc.columbia.edu",
                 NULL,
               NOTICE_BUTTON_YES, "OK",
               NULL);
    if (result == NOTICE_YES) return;
}


Xv_opaque quit_proc(item, event)
    Panel_item item;
    Event *event;
{
    int result;
    Panel panel = (Panel)xv_get(item, PANEL_PARENT_PANEL);
    
    result = notice_prompt(panel, NULL,
               NOTICE_MESSAGE_STRINGS,
                 "What... already?", NULL,
               NOTICE_BUTTON_YES, "jah",
               NOTICE_BUTTON_NO, "no mon",
               NULL);
    if (result == NOTICE_YES) quit();
    if (result == NOTICE_NO) return;
}

void event_proc(window, event)
    Xv_window window;
    Event *event;
{
    static int x,y;

    if (event_is_ascii(event)) {
        if (event_is_down(event)) 
            keyboard(event_action(event));
    }
    else
        switch (event_action(event)) {
        case ACTION_SELECT:
        case MS_LEFT:
        case ACTION_ADJUST:
        case MS_MIDDLE:
        case ACTION_MENU:
        case MS_RIGHT:
            if (event_is_down(event)) {
                x = (event_x(event)-BWIDTH)/SWIDTH;
                y = (event_y(event)-BHEIGHT)/SHEIGHT;
                if (InArray(x,y)) {
                    mymarkcell(x,y);
                    update_x = x;
                    update_y = y;
                }
            }
            else return;
            break;
        default:
            return;
        }
    canvas_proc((Canvas)NULL, window, (Display*)xv_get(window, XV_DISPLAY),
                xv_get(window, XV_XID), (Xv_xrectlist *)NULL);
}

void canvas_proc(canvas, pwin, dpy, x_win, xrects)
    Canvas canvas;
    Xv_Window pwin;
    Display *dpy;
    Window x_win;
    Xv_xrectlist *xrects;
{
    if (clearscreen) {
        XClearWindow(dpy, x_win);
        clearscreen = 0;
    }
    draw_screen();
}

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

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


int quit()
{
    exit(1);
}


void startup(frame, canvas)
    Frame frame;
    Canvas canvas;
{
    int i;
    int depth;
    char *pixstr = ".";
    unsigned long patbg, patfg;
    char fontname[80];
    XFontStruct *xfontinfo;

    display = (Display *)xv_get(frame, XV_DISPLAY);
    screen = DefaultScreen(display);
    depth = DefaultDepth(display,screen);
    if (depth < 2) mono = 1;
    gc = DefaultGC(display, screen);
    xv_win = canvas_paint_window(canvas);
    xwin = xv_get(xv_win, XV_XID);

    /* default pixel values */
    cmap = DefaultColormap(display, screen);

    background = WhitePixel(display, screen);
    foreground = BlackPixel(display, screen);
    XSetBackground(display, gc, background);
    XSetForeground(display, gc, foreground);

    sprintf(fontname, "8x13bold");
    if((xfontinfo = XLoadQueryFont(display,fontname)) == NULL)
    {
        fprintf(stderr,"Can't find font '%s'\n",fontname);
        xfontinfo = XQueryFont(display,XGContextFromGC(gc));
    }
    XSetFont(display,gc,xfontinfo->fid); 

    if(!mono) patbg = getcolor(colorname[1] ,background); 
    patpix[0] = XCreatePixmapFromBitmapData(display,xwin,
                        gray_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    showpix[0] = XCreatePixmapFromBitmapData(display,xwin,
                        gray_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    if(!mono) patbg = getcolor(colorname[2],background); 
    patpix[2] = XCreatePixmapFromBitmapData(display,xwin,
                        white_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    showpix[1] = XCreatePixmapFromBitmapData(display,xwin,
                        white_xbm_bits,SWIDTH,SHEIGHT,foreground,
                        patbg, depth);
    /* put the '.' on the showpix */
    patfg = getcolor(colorname[3],foreground);  
    XSetForeground(display, gc, patfg);
    XDrawString(display,showpix[0],gc,SWIDTH/2-3,SHEIGHT/2,pixstr,1);
    XDrawString(display,showpix[1],gc,SWIDTH/2-3,SHEIGHT/2,pixstr,1);
    XSetForeground(display, gc, foreground);

    if(showmove) { /* show the '.' on clicked squares */
        patpix[1] = showpix[0];
        patpix[3] = showpix[1];
    }
    else {
        patpix[1] = patpix[0];
        patpix[3] = patpix[2];
    }
}

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;
}

/* update_x update_y are used for efficiency in playing: instead of
   repainting the whole screen in case of a move, it only paints the
   adjoining cells.
*/
void draw_screen()
{
    int i, j;

    if (update_x > -1) {
        for ( i = update_y - 1; i < update_y + 2; i++)
            for (j = update_x - 1; j < update_x + 2; j++) 
                DrawCell(j, i);
        update_x = update_y = -1;
    }
    else {
        for (i = 0; i < height; i++)
            for (j = 0; j < width; j++)
                DrawCell(j,i);
        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);
    }
}


void keyboard(c)
    int c;
{
    switch(c){
        case 'q':
            quit();
            break;
        case 'c':
            break;
        default:
            return;
    }
    newscreen();
    clearscreen = 1;
}

void DrawCell(x, y)
    int x;
    int y;

{
    int val;

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

DrawShadow(x1,y1,x2,y2,dir)
    int x1;
    int y1;
    int x2;
    int y2;
    int dir;
{
    int i;
    GC highlightgc,shadowgc;
    unsigned long patbg, patfg;

    highlightgc = XCreateGC(display, xwin, 0, 0);
    shadowgc = XCreateGC(display, xwin, 0, 0);
    if(!mono) {
        patbg = getcolor("gray", background); 
        patfg = getcolor("black", foreground);
    }

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

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