//  xpar2.c  MR Dec 2000, Jan 2001

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

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

// #define BGCOLOR CLR_GREY
#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 )
	//
	// (( Old-version comment:
	// Age eggs and transform eggs that reach ''hatching time''
	//  to 'npara' ( = active parasite ). 
	// ))
	//
	// 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 ul = gettmp( pW, size, i-1, j-1 );
				cont_t ur = gettmp( pW, size, i+1, j-1 );
				cont_t dl = gettmp( pW, size, i-1, j+1 );
				cont_t dr = gettmp( pW, size, i+1, j+1 );

				// if ( ! ( left == HOST || right == HOST ||
				//   up == HOST || down == HOST )) 
				   //  || ul == HOST || ur == HOST || 
				   //     dl == HOST || dr == HOST ) ) 

				int nnsq = ( 
				   ( left == HOST ) + 
				   ( right == HOST ) + 
				   ( up == HOST ) + 
				   ( down == HOST ) );
				int nnobl = (
				   ( ul == HOST ) + 
				   ( ur == HOST ) + 
				   ( dl == HOST ) + 
				   ( dr == HOST ) );
				// double nnd = 1.0 * nnsq + .7 * nnobl;
				if ( ( nnsq + nnobl ) < 1 )
				// if ( nnsq < 1 )
					{
					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
	//
	// 
	// Transform 'npara' to eggs : i.e., execute action of active
	//  parasite (= square which has npara == 1).
	//
	// (( comment for old version
	// For each square S where npara > 0 :
	//   1. If square S contains a host aged >= 'aT' :
	//        - //Kill host (set nhost := 0) in square S
	//          Degrade host (decrease nhost) in square S
	//        - Bury a new hibernating egg in square S
	//        - Enter an egg with nhibe == 0 (which will therefore
	//          hatch immediately in the next turn) in all 
	//          neighbouring squares of square S where nhost >= 'aT'
	//   2. Then always: Set npara of square S to 0.
	// ))
	{
	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 ul;
			cont_t ur;
			cont_t dl;
			cont_t dr;


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

			ul = getcur( pW, size, i-1, j-1 );
			ur = getcur( pW, size, i+1, j-1 );
			dl = getcur( pW, size, i-1, j+1 );
			dr = 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 );
				}

// #define OBLIQUEFACTOR 0.7
#define OBLIQUEFACTOR 1.0
// #define OBLIQUEFACTOR 0

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



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

	for ( i = 0; i < size; i++ ) 
		{
		for ( j = 0; j < size; j++ )
			{

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



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


void print_w( grp_t * pgrp,
	int pixsize,
	const world_t * pW, int size )
	{
	int i, j;
	int x, y;
	int pixinnersize = ( pixsize > 1 ? (pixsize-1) : pixsize );
	assert ( pixsize >= 1 );

	for ( i = 0, x = 1; i < size; i++, x += pixsize )
		{
		for ( j = 0, y = 1; j < size; j++, y += pixsize )
			{
			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_paintrect( pgrp, 
			   x, y, 
			   pixinnersize, pixinnersize,
			   GLOB_colorpixel[iclr] );
			}
		}
	}

void print_cursor( grp_t * pgrp, int pixsize,
	int cx, int cy )
	{
	int x0 = cx * pixsize;
	int x1 = ( cx + 1 ) * pixsize;
	int y0 = cy * pixsize;
	int y1 = ( cy + 1 ) * pixsize;
	long color = GLOB_colorpixel[CLR_WHITE];
	grp_paintrect( pgrp, x0, y0, pixsize+1, 1, color );
	grp_paintrect( pgrp, x0, y0, 1, pixsize+1, color );
	grp_paintrect( pgrp, x0, y1, pixsize+1, 1, color );
	grp_paintrect( pgrp, x1, y0, 1, pixsize+1, color );
	}








// -------------------------------------------------------------------------
//           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: xpar2 [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 pixsize = 5;

	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", &pixsize ) != 1 ||
				   ! ( 1 <= pixsize && pixsize <= 6 ) )
					{ usage(); return -1; }
			break;
			case 'v':
				pfl = stdout; 
			break;
			case 'l':
				pfl = fopen( "XPAR2.LOG", "w" );
				if ( pfl == NULL )
					{ fprintf( stderr, 
					    "fopen(XPAR2.LOG) failed\n" ); }
			break;
			default: usage(); return -1; break;
				}//switch
			}
		else
			{
			usage(); return -1; 
			}
		}//for


	assert( size < SIZEMAX );

	grp.ourScreenWidth = 1 + size * pixsize;
	grp.ourScreenHeight = 1 + size * pixsize;

	


	//
	// 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, pixsize, cx, cy );
			}

		print_w( &grp, pixsize, &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;
	}

