//  xhexpar2.c  MR Dec 2000, Jan 2001
// 


#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <assert.h>
#include <time.h>

#include "bool.h"
#include "rand.h"
#include "xgrp.h"



#define BGCOLOR CLR_DGREY


// --------------------------------------------------------------------------
//        DATA TYPES
// --------------------------------------------------------------------------

typedef enum { EMPTY, PARAS, HOST } cont_t;

typedef struct
	{
	cont_t cur; // Current contents of cell
	cont_t tmp; // Used temporarily to determine next contents of cell
	}
	cell_t;


// #define SIZEMAX 128
// #define SIZEMAX 200
#define SIZEMAX 512
typedef struct
	{
	cell_t cell[SIZEMAX][SIZEMAX];
	}
	world_t;



// --------------------------------------------------------------------------
//   GENERAL CELL-ARRAY MANIPULATION FUNCTIONS
// --------------------------------------------------------------------------



void w_clear( world_t * pW, int size )
	{
	int i, j;
	for ( i = 0; i < size; i++ ) 
		{
		for ( j = 0; j < size; j++ )
			{
			pW->cell[i][j].cur = EMPTY;
			pW->cell[i][j].tmp = EMPTY;
			}
		}
	}


void w_insrandom( world_t * pW, int size, unsigned int seed,
	int density )
	// Fill '.cur' with random contents
	{
	int i, j;

	srand( seed );

	assert( density >= 1 );
	
	for ( i = 0; i < size; i++ ) 
		{
		for ( j = 0; j < size; j++ )
			{
			if ( n_rand( density ) > 0 ) { continue; }
				
			pW->cell[i][j].cur = 
			   ( n_rand(2) == 0 ? HOST : PARAS );
			}
		}
	} 


cont_t getcur( const world_t * pW, int size, int i, int j )
	{
	if ( ! ( 0 <= i && i < size && 0 <= j && j < size ) )
		{ return EMPTY; }
	else
		{ return pW->cell[i][j].cur; }
	}
cont_t gettmp( const world_t * pW, int size, int i, int j )
        {
        if ( ! ( 0 <= i && i < size && 0 <= j && j < size ) )
                { return EMPTY; }
        else
                { return pW->cell[i][j].tmp; }
        }


void settmp( world_t * pW, int size, int i, int j, cont_t val )
	{
	if ( ! ( 0 <= i && i < size && 0 <= j && j < size ) )
		{ return; }
	else
		{ pW->cell[i][j].tmp = val; }
	}


// --------------------------------------------------------------------------
//    COMPUTATION OF NEXT STATE
// --------------------------------------------------------------------------


void w_tmptocur( world_t * pW, int size )
	//
	// Copy tmp to cur,  and kill parasites not neighbouring any host.
	//
	{
	int i, j;
	for ( i = 0; i < size; i++ ) 
		{
		for ( j = 0; j < size; j++ )
			{

			// Parasite dies if not next to any host
			if ( pW->cell[i][j].cur == PARAS )
				{
				cont_t left  = gettmp( pW, size, i-1, j );
				cont_t right = gettmp( pW, size, i+1, j );
				cont_t up    = gettmp( pW, size, i, j-1 );
				cont_t down  = gettmp( pW, size, i, j+1 );

				cont_t ur = gettmp( pW, size, i+1, j-1 );
				cont_t dl = gettmp( pW, size, i-1, j+1 );

				if ( ! ( left == HOST || right == HOST ||
				  up == HOST || down == HOST || 
				  ur == HOST || dl == HOST ) ) 
					{
					pW->cell[i][j].cur = EMPTY;
					}
				}
			else
				{

				pW->cell[i][j].cur = pW->cell[i][j].tmp;

				}

			}//for(j)
		}//for(i)
	}



typedef enum 
	{ INERT = 0, WRAP }
border_t;

#define N_BORDER 2
static const char * border_t_text[N_BORDER] = { "INERT", "WRAP" };

void w_curtotmp(
	world_t * pW,
	int size, 
	border_t borderType,
	double probHost,   // Host growing speed
	double probParas ) // Parasite growing speed
	//
	{
	int i, j;

	assert( 0.0 < probHost && probHost <= 1.0 );
	assert( 0.0 < probParas && probParas <= 1.0 );

	for ( i = 0; i < size; i++ ) 
		{
		for ( j = 0; j < size; j++ )
			{
			pW->cell[i][j].tmp = pW->cell[i][j].cur;
			}
		}

	for ( i = 0; i < size; i++ ) 
		{
		for ( j = 0; j < size; j++ )
			{
			cont_t target;
			cont_t own;
			double prob;

			cont_t left;
			cont_t right;
			cont_t up;
			cont_t down;
			cont_t ur;
			cont_t dl;


			if ( pW->cell[i][j].cur == EMPTY ) { continue; }

			switch ( pW->cell[i][j].cur )
				{
			case HOST:
				target = EMPTY;
				own = HOST;
				prob = probHost;
			break;
			case PARAS:
				target = HOST;
				own = PARAS;
				prob = probParas;
			break;
				}//switch

			left  = getcur( pW, size, i-1, j );
			right = getcur( pW, size, i+1, j );
			up    = getcur( pW, size, i, j-1 );
			down  = getcur( pW, size, i, j+1 );

			ur = getcur( pW, size, i+1, j-1 );
			dl = getcur( pW, size, i-1, j+1 );



			// Produce offspring in neighbouring cells

			if ( left == target && 
			   f_rand(1.0) <= prob )
				{ 
				settmp( pW, size, i-1, j, own );
				}
			if ( up == target && 
			   f_rand(1.0) <= prob )
				{ 
				settmp( pW, size, i, j-1, own );
				}
			if ( right == target && 
			   f_rand(1.0) <= prob )
				{ 
				settmp( pW, size, i+1, j, own );
				}
			if ( down == target && 
			   f_rand(1.0) <= prob )
				{ 
				settmp( pW, size, i, j+1, own );
				}

			if ( ur == target && 
			   f_rand(1.0) <= prob )
				{ 
				settmp( pW, size, i+1, j-1, own );
				}
			if ( dl == target && 
			   f_rand(1.0) <= prob )
				{ 
				settmp( pW, size, i-1, j+1, own );
				}


			}//for(j)
		}//for(i)
	}





// --------------------------------------------------------------------------
//     LOGICAL HEXAGONAL GRID AUXILIARY FUNCTIONS
// --------------------------------------------------------------------------

// Direction vectors, in terms of cell indexes:
#define NDRN 6
static const int GLOB_udrn_i[NDRN] = {  1,  0, -1, -1,  0,  1 };
static const int GLOB_udrn_j[NDRN] = {  0,  1,  1,  0, -1, -1 };
//         (0,-1)   (1,-1)
//          *-------*
//         / \     / \
//        /   \   /   \
//       /     \ /     \
// (-1,0*-------*(0,0)--*(1,0)    0---> x
//       \     / \     /           \
//        \   /   \   /             \
//         \ /     \ /               \
//          *-------*                 y
//         (-1,1)   (0,1)




// --------------------------------------------------------------------------
//     SCREEN I/O FUNCTIONS
// --------------------------------------------------------------------------


#define XHALFINCMAX 8
//                             xhalfinc =   0  1  2  3  4  5  6  7
static const GLOB_yhalfinc[XHALFINCMAX] = { 0, 1, 2, 2, 3, 4, 4, 5 };

// Needed square screen size in pixels is:
//   width  = 3 * size * xhalfinc
//   height = 2 * size * yhalfinc
// where yhalfinc = GLOB_yhalfinc[xhalfinc].

int need_screenwidth( int xhalfinc, int worldsize )
	{ return ( 3 * worldsize * xhalfinc ); }
int need_screenheight( int xhalfinc, int worldsize )
	{ return ( 2 * worldsize * GLOB_yhalfinc[ xhalfinc ] ); }


void screenpos( int i, int j, // Cell coordinates
	int * px, int * py,   // Screen pixel positions
	int xhalfinc )
	{
	*px = ( ( j + 1 ) + ( 2 * i ) ) * xhalfinc;
	*py = ( 1 + ( 2 * j ) ) * GLOB_yhalfinc[ xhalfinc ];
	}


void print_worldoutline( grp_t * pgrp,
	int xhalfinc,
	int worldsize )
	{
	int x00, y00, x01, y01, x10, y10, x11, y11;

	grp_paintrect( pgrp, 0, 0, 
	   pgrp->ourScreenWidth, pgrp->ourScreenHeight, 
	   GLOB_colorpixel[ BGCOLOR ] ); // Clear window
	
	screenpos( -1, -1,               &x00, &y00, xhalfinc );
	screenpos( worldsize, -1,        &x10, &y10, xhalfinc );
	screenpos( -1, worldsize,        &x01, &y01, xhalfinc );
	screenpos( worldsize, worldsize, &x11, &y11, xhalfinc );

	grp_paintline( pgrp, x00, y00, x01, y01, 
	   GLOB_colorpixel[ CLR_DGREY ] );
	grp_paintline( pgrp, x10, y10, x11, y11, 
	   GLOB_colorpixel[ CLR_DGREY ] );
	}



void print_w( grp_t * pgrp,
	int xhalfinc,
	const world_t * pW, int worldsize )
	// Print all atoms in whole ''world'' anew.
	{
	int i, j;
	int x, y;
	int xinc, yhalfinc, yinc, radius;
	assert ( 1 <= xhalfinc  && xhalfinc < XHALFINCMAX );

	xinc = 2 * xhalfinc;
	yhalfinc = GLOB_yhalfinc[xhalfinc];
	yinc = 2 * yhalfinc;

	//radius = ( yhalfinc > 1 ? (yhalfinc-1) : yhalfinc );
	radius = yhalfinc;

	for ( j = 0, y = yhalfinc; 
	   j < worldsize; 
	   j++, y += yinc )
		{
		for ( i = 0, x = ((j+1)*xhalfinc); 
		   i < worldsize; 
		   i++, x += xinc )
			{
			cell_t cc = pW->cell[i][j];
			int iclr;

			switch ( pW->cell[i][j].cur )
				{
			case EMPTY: iclr = CLR_BLACK; break;
			case HOST:  iclr = CLR_GREEN; break;
			case PARAS: iclr = CLR_RED;   break;
				}

			grp_paintcircle( pgrp, 
			   x, y, radius, 
			   GLOB_colorpixel[ iclr ] );
			}
		}
	}



void print_cursor( grp_t * pgrp, int xhalfinc,
	int cx, int cy ) // Cell indices
	{
	int yhalfinc = GLOB_yhalfinc[xhalfinc];
	int radius;
	int screenx, screeny;
	screenpos( cx, cy, &screenx, &screeny, xhalfinc );

	radius = 1 + ( yhalfinc > 1 ? (yhalfinc-1) : yhalfinc );
	grp_paintbox( pgrp, 
	   screenx, screeny, 
	   radius, 
	   GLOB_colorpixel[ CLR_WHITE ] );
	}







// -------------------------------------------------------------------------
//           Auxiliary X-win graphics function for ''back-buffer''
// -------------------------------------------------------------------------

void swap_bb_to_foregrond( Display * pD, 
	Window w, 
	XdbeBackBuffer bb )
	// Swap the ''back buffer'' to the foreground 
	{
	XdbeSwapInfo si;
	si.swap_window = w;
	//si.swap_action = XdbeUndefined;
	si.swap_action = XdbeBackground; //This immediately fills the
	                                 // new back-buffer with the
	                                 // background color.
	XdbeSwapBuffers( pD, &si, 1 );
	}

// -------------------------------------------------------------------------
//                Auxiliary high-level user I/O functions
// -------------------------------------------------------------------------

#define KEY_ONLY_EVT_MASK KeyPressMask

_bool really( const char * pstrWhat,
	grp_t * pgrp,
	Display * pD, 
	Window w,
	XdbeBackBuffer bb )
	{
	XEvent evt;
	char cmd;

	grp_strprintf( pgrp,
	   20,
	   pgrp->ourScreenHeight / 3,
	   GLOB_colorpixel[ CLR_WHITE ],
	   "$Really %s ? (y/n)", pstrWhat ); 
	swap_bb_to_foregrond( pD, w, bb );

 	XWindowEvent( pD, w, KEY_ONLY_EVT_MASK, &evt );
	XLookupString( &evt, &cmd, 1, NULL, NULL );

	return ( cmd == 'y' );
	}



// -------------------------------------------------------------------------
//                                 MAIN
// -------------------------------------------------------------------------




// #define MY_EVT_MASK KeyPressMask | VisibilityChangeMask |  \
//                     EnterWindowMask | LeaveWindowMask |  \
//                     FocusChangeMask | ResizeRedirectMask | \
//                     ExposureMask

#define MY_EVT_MASK KeyPressMask | VisibilityChangeMask 





void usage( void )
	{
        fprintf( stderr, "Usage: xhexpar2 [options]\n" );
        fprintf( stderr,
           "Options (r = real value in [0,1], n = integer > 0):\n" );
        fprintf( stderr, "   -Pr   Parasite dispersion probability\n" );
        fprintf( stderr, "   -Hr   Host dispersion probability\n" );
        fprintf( stderr, "   -dn   Initialize by filling each nth cell "
             "with random a host or parasite\n" );
        fprintf( stderr, "   -sn   World (cell array) size\n" );
        fprintf( stderr, "   -pn   Diameter of entities ('pixels') as "
	     "shown on screen, in [1,6]\n" );
        fprintf( stderr, "   -v    Verbose messages to stdout\n" );
        fprintf( stderr, "   -l    Messages to logfile\n" );
	}

int main( int argc, char ** argv )
	{
	// X-Windows variables
	Display * pD;	
	Window w;
	XdbeBackBuffer bb;
	int screenNr;
	KeySym * pKS; int kcMin, kcMax; int ksPerKc;
	grp_t grp;


	// Logical game variables
	int size = 50;
	world_t world;
	border_t borderType = WRAP; //INERT;

	int density = 2;
	double probHost = 0.8;
	double probParas = 0.5;

	int xhalfinc = 3;

	int cx = 0;
	int cy = 0;

        int ngeneration = 0;


	// Flags for control of flow in main() function
	int k;
	_bool stop = 0;

	FILE * pfl = NULL;
	   // Verbose messages logging file

	enum { AUTO, SINGLESTEP } kmode = SINGLESTEP;
	   // Flag determining single-step mode




	// Cmd line args

	for ( k = 1; k < argc; k++ )
		{
		if ( argv[k][0] == '-' )
			{
			switch ( argv[k][1] )
				{
			case 'b': borderType = INERT; break;
			case 'B': borderType = WRAP; break;

			case 'P':
				if ( sscanf( argv[k]+2, "%le", 
				  &probParas ) != 1 ||
				   ! ( 0.0 < probParas && probParas <= 1.0 ) )
					{ usage(); return -1; }
			break;
			case 'H':
				if ( sscanf( argv[k]+2, "%le", 
				  &probHost ) != 1 ||
				   ! ( 0.0 < probHost && probHost <= 1.0 ) )
					{ usage(); return -1; }
			break;
			case 'd':
				if ( sscanf( argv[k]+2, "%d", 
				  &density ) != 1 ||
				   ! ( density > 0 ) )
					{ usage(); return -1; }
			break;

			case 's':
				if ( sscanf( argv[k]+2, "%d", &size ) != 1 ||
				   ! ( 5 <= size && size < SIZEMAX ) )
					{ usage(); return -1; }
			break;
			case 'p':
				if ( sscanf( argv[k]+2, "%d", &xhalfinc ) != 1 ||
				   ! ( 1 <= xhalfinc && xhalfinc <= 6 ) )
					{ usage(); return -1; }
			break;
			case 'v':
				pfl = stdout; 
			break;
			case 'l':
				pfl = fopen( "XHEXPAR2.LOG", "w" );
				if ( pfl == NULL )
					{ fprintf( stderr, 
					    "fopen(XHEXPAR2.LOG) failed\n" ); }
			break;
			default: usage(); return -1; break;
				}//switch
			}
		else
			{
			usage(); return -1; 
			}
		}//for


	assert( size < SIZEMAX );
	assert( 0 < xhalfinc && xhalfinc < XHALFINCMAX );

	grp.ourScreenWidth = need_screenwidth( xhalfinc, size );
	grp.ourScreenHeight = need_screenheight( xhalfinc, size );


	//
	// Initialize logical game stuff
	//
		
	w_clear( &world, size );

	w_insrandom( &world, size, time(NULL), density );
	



	/* 
	 * Open X-server connection, 
	 *  open a window, 
	 *  initialize keyboard input settings,
	 *  initialize a ''grp_t'' struct (which contains a ''GC'').
  	 */

	if ( ( pD = XOpenDisplay( NULL ) ) == NULL )
		{ fprintf( stderr, "XOpenDisplay() Failed\n" ); return -1; }

	screenNr = DefaultScreen(pD);


	// Initialize our global color array, used in the grp_...() drawing
	//  functions
	initcolors( pD );


	w = XCreateSimpleWindow( pD, DefaultRootWindow(pD), 
  	   0, 0, 
	   grp.ourScreenWidth, grp.ourScreenHeight,
	   2, 
	   0L, 
 	   GLOB_colorpixel[ BGCOLOR ] );  // Background color
 	   //BlackPixel(pD,screenNr) );  // Background color

	bb = XdbeAllocateBackBufferName( 
	   pD, w,
	   XdbeBackground ); //XdbeUndefined );

	XStoreName( pD, w, "xpar2" );

	XMapWindow( pD, w );



   	// Set some settings for how input is to be received, get a
	//  ptr to the ''keyboard mapping''
	XSelectInput( pD, w, MY_EVT_MASK );



	// Init our own ''grp_t'' struct
	grp_makenew( &grp, 
	   pD, 
	   bb );  // Draw on the back buffer !!, not on window 'w'.




	

	// 
	// MAIN LOOP 
	// 

	while ( ! stop )
		{

		// Draw new situation on screen

		if ( kmode == SINGLESTEP )
			{
			print_cursor( &grp, xhalfinc, cx, cy );
			}

		print_w( &grp, xhalfinc, &world, size );

		swap_bb_to_foregrond( pD, w, bb );



		// Sleep

		if ( kmode == AUTO )
			{
			// usleep( 500000 );
			usleep( 100000 );
			}



		// Get all keystrokes from queue, and execute them

		for(;;)
			{
			XEvent evt;
			char cmd;
			_bool cmdok = 0;
			

			cmdok = ( XCheckWindowEvent( pD, w, 
                                       MY_EVT_MASK, &evt ) == True );

			if ( evt.type == KeyPress && cmdok )
				{ 
				// Translate keypress-event 'evt'
				//  to ASCII character 'cmd'
				XLookupString( &evt, &cmd, 1, NULL, NULL );
				}


			if ( cmdok && ( cmd == 'q' ) ) 
				{
				// Quit
				if ( really( "quit", &grp, pD, w, bb ) ) 
					{
					stop = 1;
					break;
					}
				}

			if ( ! cmdok && kmode == AUTO )
				{
				// No more keystrokes ==> leave this 
				//  key-input loop, to display new situation
				//  on screen, and sleep awhile
				break;
				}
			if ( ! cmdok && kmode == SINGLESTEP )
				{ // Wait until one real keystroke received
				usleep( 100000 );
				continue;
				}



			switch ( cmd )
				{
			case 'b': // Toggle border behaviour
				borderType = ( borderType + 1 ) % N_BORDER;
			break;

			case 'a': kmode = AUTO; break;
			case 'w': kmode = SINGLESTEP; break;
				}//switch



			if ( kmode == SINGLESTEP )
				{
				switch ( cmd )
					{
				case 'h': if ( cx > 0 ) { cx--; } break;
				case 'k': if ( cy > 0 ) { cy--; } break;
				case 'l': if ( cx < size-1 ) { cx++; } break;
				case 'j': if ( cy < size-1 ) { cy++; } break;
	
				case '0': // Make empty
				case 'x': 
					world.cell[cx][cy].cur = EMPTY;
				break;
				case '1': // Put in a host
					world.cell[cx][cy].cur = HOST;
				break;
				case '2': // Put in a parasite
					world.cell[cx][cy].cur = PARAS;
				break;

				case '\r': // Update game one time-step
				case '\n':
					{
					w_curtotmp( &world, size, 
					   borderType,
					   probHost, 
					   probParas );
					w_tmptocur( &world, size );
					ngeneration++;
					}
				break;
				
				case 'c':
					if ( really( "clear", 
					   &grp, pD, w, bb ) ) 
						{ 
						w_clear( &world, size );
						}
				break;

                                case 'g':
                                        printf( "ngeneration = %d\n",
                                           ngeneration );
                                break;
					}//switch
				}



			if ( kmode == SINGLESTEP )
				{
				break; // Leave this loop after one keystroke
				       //  is received
				}

			}//for(;;)





		// Update game one time-step

		if ( kmode == AUTO )
			{
			w_curtotmp( &world, size, 
			   borderType,
			   probHost, 
			   probParas );
			w_tmptocur( &world, size );
			ngeneration++;
			}



		}//while(!stop) (MAIN LOOP)




	/* 
	 * Close/de-initialize grp, keyboard, window and X-server connection
	 */   
	grp_delete( &grp );
	XUnmapWindow( pD, w );
	XdbeDeallocateBackBufferName( pD, bb );
	XDestroyWindow( pD, w );
	XCloseDisplay ( pD );



	return 0;
	}


