#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <sys/time.h>
#include <sys/types.h>


#include "display.h"

#define P_RADIUS 1.0

struct particle {

  double x_pos,y_pos;
  double x_vel,y_vel;
  double mass;

};
typedef struct particle Particle;

struct cell {
  int num_particles; /* number of particles contained here */
  int size;  /* the array sizes */
  Particle* particles;
  int* flags;  

  /* coordinates of this cell are:
    lower left = <lx,ly>
    upper right = <ux,uy>
  */
  double lx,ly;
  double ux,uy;

  /* The following are non null if this cell has a wall. If it
     does, this pointers point to the vertical or horizontal coor */
  double* vwallp; 
  double* hwallp;
};
typedef struct cell Cell;


struct space {

  Cell***cells;
  int ncells_on_margin; /* on a side */
  int num_particles;
  double space_margin_size; /* square world, this is the upper right x and y coor */
  double cell_margin_size;
  double simulation_speed;
  double inflowRate;
  double valveSize;
};
typedef struct space Space;

#define STRING	"Particles"
#define BORDER	1
#define FONT	"fixed"
#define	ARG_FONT		"font"
#define	ARG_BORDER_COLOR	"bordercolor"
#define	ARG_FOREGROUND		"foreground"
#define	ARG_BACKGROUND		"background"
#define ARG_BORDER		"border"
#define	ARG_GEOMETRY		"geometry"
#define DEFAULT_GEOMETRY	""

/*
 * This structure forms the WM_HINTS property of the window,
 * letting the window manager know how to handle this window.
 * See Section 9.1 of the Xlib manual.
 */
XWMHints	xwmh = {
    (InputHint|StateHint),	/* flags */
    False,			/* input */
    NormalState,		/* initial_state */
    0,				/* icon pixmap */
    0,				/* icon window */
    0, 0,			/* icon location */
    0,				/* icon mask */
    0,				/* Window group */
};


/* global variables for this program */  
Display    *dpy;		/* X server connection */
Window      win;		/* Window ID */
GC          gc;		        /* GC to draw with */

struct part_points {
  int x,y;
};
typedef struct part_points PartPoints;
int cur_num_points = 0;
int part_points_size;
#define INCREASEFACTOR 5
#define INCREASEOFFSET 50

PartPoints*oldPoints;

void createDisplay(int argc,char *argv[],int wsize,int np) 
{

    /* local routine 1 */
    unsigned long ftw, fth, pad;/* Font size parameters */
    unsigned long fg, bg, bd;	/* Pixel values */
    unsigned long bw;		/* Border width */
    char       *tempstr;	/* Temporary string */
    XSizeHints  xsh;		/* Size hints for window manager */
    char       *geomSpec;	/* Window geometry string */
    XSetWindowAttributes xswa;	/* Temporary Set Window Attribute struct */
    XGCValues   gcv;		/* Struct for creating GC */
    Colormap    cmap;		/* Color map to use */
    XColor      color;		/* Temporary color */

    /*
     * Open the display using the $DISPLAY environment variable to locate
     * the X server.  See Section 2.1.
     */
    if ((dpy = XOpenDisplay(NULL)) == NULL) {
	fprintf(stderr, "%s: can't open %s\n", argv[0], XDisplayName(NULL));
	exit(1);
    }

    /*
     * Select colors for the border,  the window background,  and the
     * foreground.  We use the default colormap to allocate the colors in.
     * See Sections 2.2.1, 5.1.2, & 10.4.
     */
    cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_BORDER_COLOR)) == NULL ||
	XParseColor(dpy, cmap, tempstr, &color) == 0 ||
	XAllocColor(dpy, cmap, &color) == 0) {
	bd = WhitePixel(dpy, DefaultScreen(dpy));
    }
    else {
	bd = color.pixel;
    }
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_BACKGROUND)) == NULL ||
	XParseColor(dpy, cmap, tempstr, &color) == 0 ||
	XAllocColor(dpy, cmap, &color) == 0) {
	bg = WhitePixel(dpy, DefaultScreen(dpy));
    }
    else {
	bg = color.pixel;
    }
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_FOREGROUND)) == NULL ||
	XParseColor(dpy, cmap, tempstr, &color) == 0 ||
	XAllocColor(dpy, cmap, &color) == 0) {
	fg = BlackPixel(dpy, DefaultScreen(dpy));
    }
    else {
	fg = color.pixel;
    }
    /*
     * Set the border width of the window,  and the gap between the text
     * and the edge of the window, "pad".
     */
    pad = BORDER;
    if ((tempstr = XGetDefault(dpy, argv[0], ARG_BORDER)) == NULL)
	bw = 1;
    else
	bw = atoi(tempstr);

    /*
     * Deal with providing the window with an initial position & size.
     * Fill out the XSizeHints struct to inform the window manager. See
     * Sections 9.1.6 & 10.3.
     */
    geomSpec = XGetDefault(dpy, argv[0], ARG_GEOMETRY);
    if (geomSpec == NULL) {
	/*
	 * The defaults database doesn't contain a specification of the
	 * initial size & position - fit the window to the text and locate
	 * it in the center of the screen.
	 */
	xsh.flags = (PPosition | PSize);
	xsh.height = ((int)wsize)*2;
	xsh.width = ((int)wsize)*2;
	xsh.x = (DisplayWidth(dpy, DefaultScreen(dpy)) - xsh.width) / 2;
	xsh.y = (DisplayHeight(dpy, DefaultScreen(dpy)) - xsh.height) / 2;
    }
    else {
	int         bitmask;

	bzero((char*)&xsh, sizeof(xsh));
	bitmask = XGeometry(dpy, DefaultScreen(dpy), geomSpec, DEFAULT_GEOMETRY,
			    bw, ftw, fth, pad, pad, &(xsh.x), &(xsh.y),
			    &(xsh.width), &(xsh.height));
	if (bitmask & (XValue | YValue)) {
	    xsh.flags |= USPosition;
	}
	if (bitmask & (WidthValue | HeightValue)) {
	    xsh.flags |= USSize;
	}
    }

    /*
     * Create the Window with the information in the XSizeHints, the
     * border width,  and the border & background pixels. See Section 3.3.
     */
    win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
			      xsh.x, xsh.y, xsh.width, xsh.height,
			      bw, bd, bg);

    /*
     * Set the standard properties for the window managers. See Section
     * 9.1.
     */
    XSetStandardProperties(dpy, win, STRING, STRING, None, argv, argc, &xsh);
    XSetWMHints(dpy, win, &xwmh);

    /*
     * Ensure that the window's colormap field points to the default
     * colormap,  so that the window manager knows the correct colormap to
     * use for the window.  See Section 3.2.9. Also,  set the window's Bit
     * Gravity to reduce Expose events.
     */
    xswa.colormap = DefaultColormap(dpy, DefaultScreen(dpy));
    xswa.bit_gravity = CenterGravity;
    XChangeWindowAttributes(dpy, win, (CWColormap | CWBitGravity), &xswa);

    /*
     * Create the GC for writing the text.  See Section 5.3.
     */
    gcv.foreground = fg;
    gcv.background = bg;
    gc = XCreateGC(dpy, win, (GCForeground | GCBackground), &gcv);

    /*
     * Specify the event types we're interested in - only Exposures.  See
     * Sections 8.5 & 8.4.5.1
     */
    XSelectInput(dpy, win, ExposureMask);

    /*
     * Map the window to make it visible.  See Section 3.5.
     */
    XMapWindow(dpy, win);
    XSetFunction(dpy,gc,GXxor);
    part_points_size = INCREASEFACTOR*np+INCREASEOFFSET;
    oldPoints = (PartPoints*)malloc(part_points_size*sizeof(PartPoints));
    {
      int i;
      for (i=0;i<part_points_size;i++)
	oldPoints[i].x = oldPoints[i].y = -1;
    }
    cur_num_points = np;
}

void updateDisplay(Space*s)
{

    /* local routine 2 */
    XEvent      event;		/* Event received */
    int i,j,k;
    XWindowAttributes xwa;	/* Temp Get Window Attribute struct */
    int         x, y;
    int pnum = 0;
    int new_num_points;
    /* if there's an expose event, clear window. */
    if (XCheckTypedEvent(dpy, Expose, &event))
      XClearWindow(dpy, win);

    /* get rid of remaining events */
    while (XCheckMaskEvent(dpy,~(unsigned long)0,&event));

    /*
     * Find out how big the window is now.
     */
    XGetWindowAttributes(dpy, win, &xwa);

    /* XClearWindow(dpy, win);  */
    XSetFunction(dpy,gc,GXset);
    for (i=0;i<s->ncells_on_margin;i++) /* x cols, increasing right */
      for (j=0;j<s->ncells_on_margin;j++) /* y rows, increasing up */
	for (k=0;k<s->cells[i][j]->num_particles;k++) {



	  x = ((s->cells[i][j]->particles[k].x_pos-P_RADIUS)/s->space_margin_size)
	    *xwa.width;
	  y = ((s->space_margin_size - 
		s->cells[i][j]->particles[k].y_pos-P_RADIUS)/s->space_margin_size)
	    *xwa.height;


	  /* erase old points and draw new ones */

	  if (pnum < cur_num_points) {
	    /* erase old point */
	    XSetFunction(dpy,gc,GXclear);
	    XDrawArc(dpy, win, gc, oldPoints[pnum].x, oldPoints[pnum].y,
		     (int)((P_RADIUS*2.0/s->space_margin_size)*xwa.width),
		     (int)((P_RADIUS*2.0/s->space_margin_size)*xwa.height),
		     0*64,
		     360*65);
	    XSetFunction(dpy,gc,GXset);
	  }
	  /* draw new one */
	  XDrawArc(dpy, win, gc, x, y,
		   (int)((P_RADIUS*2.0/s->space_margin_size)*xwa.width),
		   (int)((P_RADIUS*2.0/s->space_margin_size)*xwa.height),
		   0*64,
		   360*65);
	  oldPoints[pnum].x = x;
	  oldPoints[pnum].y = y;
	  pnum++;
	  if (pnum >= part_points_size) {
	    PartPoints*tmp;
	    fprintf(stderr,"Warning: doubling part_points_size from %d\n",
		    part_points_size);
	    tmp = (PartPoints*)
	      malloc(2*part_points_size*sizeof(PartPoints));
	    bcopy((char*)oldPoints,(char*)tmp,part_points_size*sizeof(PartPoints));
	    free(oldPoints);
	    oldPoints = tmp;
	    part_points_size *= 2;
	  }

	}

    /* clear up any old particles */
    new_num_points = pnum;
    XSetFunction(dpy,gc,GXclear);
    for (;pnum<cur_num_points;pnum++) {
	  XDrawArc(dpy, win, gc, oldPoints[pnum].x, oldPoints[pnum].y,
		   (int)((P_RADIUS*2.0/s->space_margin_size)*xwa.width),
		   (int)((P_RADIUS*2.0/s->space_margin_size)*xwa.height),
		   0*64,
		   360*65);
    }
    cur_num_points = new_num_points;
}