// AutomataHex.cpp : Defines the entry point for the application.
//

/*
	This is a program that implements a 2D cellular automata model of a leaf epidermis.
	The dependent variable being modeled is the cell water potential (p).  Each step
	considers water movement (or more accurately p changes) as determined by:
		1)	the p of neighboring cells (if p of a neighbor cell is higher that the
			cell under consideration, that cell's p increases and the neighbor's drops),
		2)	water uptake from xylem feeding all epidermal cells, and
		3)	loss to the air from cells designated as "stomata".
		4)	Stomata are variable as a function of cell p

	Cell p values are displayed graphically in the program window as a map with p coded
	in a gray scale from black (low p) to white (high p).

	The degree of stomatal opening (the value of flow out through stomata) is indicated
	using color values from blue through red.

	The program allows for the setting of all sorts of model, solution method, and display
	parameters through dialog boxes.
*/

/*
	                                   Program variables

		Name			Use

	a			Exponential term coeff as part of the logistic stomatal control function
	bAuto		Boolean variable used with the checkbox in the Display dialog to indicate
				automatic scaling of the map grayscale colors
	bStom		Boolean variable used with the checkbox in the Display dialog to indicate
				automatic scaling of the stomata color scale
	bNewStom	Boolean variable used to indicate that stomata locations have been altered
	bOffset		Boolean variable indicating the choice for pleaf as an offset from the meanp
	border		Minimum acceptable distance between stomata and the model edge (while randomizing)
	cThreadGo	Flag to start and stop the continuous thread for "move"
	cxClient	Size of client part of main window
	cyClient	Size of client part of main window
	cxChar		Font data - x size of characters
	cyChar		Font data - y size of characters
	cxMap		Size of map
	cyMap		Size of map
	cxStart		Starting location of map in client window
	cyStart		Starting location of map in client window
	cxSquare	Pixel size of each model cell in the map
	cySquare	Pixel size of each model cell in the map
	dp			Accumulates the various changes in p before they are applied to p
	Filename	String variable with the output file name
	hBrush		Vector for storing the handle to a set of 256 gray-scale brushes
	iColor		Gray scale value used in painting
	iDimSize	Size of model: number of rows and columns
	iDisplay	Map is redrawn every "iDisplay" number of moves
	iScale		Used in converting p to a grayscale value
	Jout		Vector of quantity lost from a cell designated as a stomata
	J0			Stomatal flow for p = pminj
	Jmax		Max value of stomatal flow
	kepi		Conductance between epidermis cells
	kin			Conductance between epidermis and underlying leaf cells
	maxp		Maximum value of p in the array
	maxJ		Highest outflow value among all stomata
	meanp		Mean value of p in the array
	minp		Minimum value of p in the array
	minJ		Lowest outflow value among all stomata
	ncells		Number of cells in the model
	ncols		Number of columns in the model array
	nrows		Number of rows in the model array
	nstomata	Number of stomata in array
	p			Basic array for storing cell water potentials
	pause		Boolean variable for pausing the moves
	pleaf		p value for bulk leaf
	pleafoff	Offset value to be applied to pleaf
	pminj		p value leading to stomatal flow of J0
	pnew		Array used to storing new cell p values until the entire new array is done
	restart		Boolean variable indicating need to restart the model
	s????		Almost all variables have a string variable version with "s" preceeding
				their name that is used in filling out dialog box edit controls
	stomX		Vector of stomata locations (in columns)
	stomY		Vector of stomata locations (in rows)
	stomXtemp	Temporary storage in case user cancels stomata location changes
	stomYtemp	-- see above --
	spacing		Minimum acceptable distance between stomata (while randomizing)
	t			Time step
	Tempborder	Temporary storage for border in case user cancels changes
	TempNstom	Temporary storage for nstomata in case user cancels changes
	Tempspacing	Temporary storage for spacing in case user cancels changes
	TempTint	Temporary storage for timer interval in case user cancels a change
	uTint		Timer interval between moves
*/

#include "stdafx.h"
#include "resource.h"
#include <stdio.h>
#include <stdlib.h>
#include <commdlg.h>
#include <io.h>
#include <float.h>
#include <time.h>
#include <math.h>
#include <process.h>

#define MAX_LOADSTRING 100
#define INIT_P 0
#define MAX_NSTOM 20000

// Global Variables:
HINSTANCE hInst;						// current instance
TCHAR szTitle[MAX_LOADSTRING];			// The title bar text
TCHAR szCurrentTitle[MAX_PATH];			// Text for updated title bar
TCHAR szWindowClass[MAX_LOADSTRING];	// The title bar text

double pdwn, pup, dp, spacing, TempSpacing;
double kepi, kin, Jmax, J0, a, pminj;
double **p, **pnew;
double pleaf, pleafoff, maxp, minp, meanp, minJ, maxJ, meanJ;
double *Jout, JoutTotal;
int *stomX, *stomY, *stomXtemp, *stomYtemp;
int nstomata, TempNstom, TempBorder, cxMap, cyMap;
int t, border, ncells, nrows, ncols, iDisplay, iDimSize;
unsigned int uTint, TempTint;
char snstomata[32], snrows[32], sncols[32], spdwn[32], spup[32], sJout[32], sTint[32];
char sminp[32], smaxp[32], sminJ[32], smaxJ[32], sspacing[32], sborder[32], sDisplay[31];
char skepi[32], skin[32], sJmax[32], sJ0[32], sa[32], spminj[32], spleafoff[32];
char Filename[MAX_PATH];
BOOL restart, pause, bAuto, bStom, bNewStom, bOffset, cThreadGo, bDataValid;
HANDLE hMutex;
HBRUSH hBrush[256];
HWND hWnd;
HMENU hMenu;

// Foward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK	DimensionDlgProc( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK	DisplayDlgProc( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK	StomataDlgProc( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK	ParamsDlgProc( HWND, UINT, WPARAM, LPARAM );

void FreeMem( void );		//Free malloced arrays for p, pnew
void Startup( void );		//Allocate arrays and set some initial values
void NewStomata( void );	//Reallocate memory for stomata locations
void Move( int t );			//Execute one move of model
void Randomize( void );		//Randomly select new locations for stomata
void MoveFull( void * ); //  *param );	//Execute moves as new thread
BOOL OpenNewFile( HWND );	//Get file name to open
void ReadFileData( void );	//Read data from file
void ShowMessageBox( int );

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	MSG msg;

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_SPACE, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow)) 
	{
		return FALSE;
	}

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0)) 
	{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
	}

	return msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage is only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this function
//    so that the application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX); 

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, (LPCTSTR)IDI_SPACE);
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= (LPCSTR)IDR_MENU1;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

	return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HANDLE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
  // HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle,
	  WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED | WS_MINIMIZEBOX,
      CW_USEDEFAULT, 0, 866, 870, NULL, NULL, hInstance, NULL);		//Specify window size

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	RECT rt;
	HBRUSH hbr;
	TEXTMETRIC lptm;
	static int cxClient, cyClient, cxChar, cyChar;
	static int iLength, nxText, nyText, nTimerID;
	int cxSquare, cySquare, cxStart, cyStart;
	char PaintBuf[256];
	int i, j, iColor, iRed, iGreen, iBlue;
	float fScale, offsetX, offsetY;
	FILE *outfile;
	DWORD dwWaitResult;
//	HMENU hMenu;
	static HANDLE hThread;
	static unsigned threadID;
	char cMutexName[64];
	BOOL bFile;

	TCHAR szHello[MAX_LOADSTRING];
	LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);

	switch (message) 
	{
		case WM_COMMAND:
			wmId    = LOWORD(wParam); 
			wmEvent = HIWORD(wParam); 
			// Parse the menu selections:
			switch (wmId)
			{
				case IDM_EXIT:
				   DestroyWindow(hWnd);
				   break;

				case IDM_START:		//Start run using timer, disable menu items
					hMenu = GetMenu( hWnd );
					EnableMenuItem( hMenu, IDM_START, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STEP, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_RESTART, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_SAVE, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_DIM, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STOMATA, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_PARAMS, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STARTFULL, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STOPFULL, MF_GRAYED );
					DrawMenuBar( hWnd );
					pause = FALSE;	//Reset pause allowing move to advance
					break;

				case IDM_STOP:		//Stop run driven by timer, enable menu items
					pause = TRUE;	//Set pause, stopping advancement
					hMenu = GetMenu( hWnd );
					EnableMenuItem( hMenu, IDM_START, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STEP, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_RESTART, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_SAVE, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_DIM, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STOMATA, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_PARAMS, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STARTFULL, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STOPFULL, MF_ENABLED );
					DrawMenuBar( hWnd );
					InvalidateRect( hWnd, NULL, FALSE );
					break;

				case IDM_STEP:		//Advance model one step
					Move( t );
					t++;
					InvalidateRect( hWnd, NULL, FALSE );
					break;

				case IDM_RESTART:
					FreeMem();		//Reallocate memory
					Startup();		//Allocate memory and reset p
					InvalidateRect( hWnd, NULL, FALSE );
					break;

				case IDM_STARTFULL:	//Launch thread to run continuously
					hMenu = GetMenu( hWnd );	//Disable menus
					EnableMenuItem( hMenu, IDM_START, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STOP, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STEP, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_RESTART, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_SAVE, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_DIM, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STOMATA, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_PARAMS, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STARTFULL, MF_GRAYED );
					DrawMenuBar( hWnd );
					cThreadGo = TRUE;
					RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
					_beginthread( MoveFull, 0, NULL ); //(void *) &hWnd );
					break;

				case IDM_STOPFULL:
					cThreadGo = FALSE;	//Set flag to stop thread
					break;				//Enable menus in MoveFull function

				case IDM_SAVE:
					outfile = fopen( Filename, "w" );	//Write current p array to file
					fprintf( outfile, "%i %i %i %i %i\n", cxMap, cyMap, nrows, ncols, t );
					fprintf( outfile, "%6.4e %6.4e\n", kepi, kin );
					fprintf( outfile, "%i %i %5.2f %8.4f %8.4f %8.4f %8.4f %8.4f\n", nstomata, border, spacing,
									Jmax, J0, a, pminj, pleafoff);
					for( i=0; i<nstomata; i++ )
						fprintf( outfile, "%i %i %11.9f\n", stomX[i], stomY[i], Jout[i] );
					for( i=0; i<nrows; i++ )
						{
							for( j=0; j<ncols; j++ )
								{
								fprintf( outfile, "%8.4f ", p[i][j] );
								}
							fprintf( outfile, "\n" );
						}
					fclose( outfile );
					break;

				case IDM_DIM:	//Call dimension dialog to set model size
					DialogBox( hInst, MAKEINTRESOURCE(IDD_DIMENSION), hWnd, (DLGPROC) DimensionDlgProc );
					switch ( iDimSize )
					{
					case IDC_RADIO50:
						ncols = nrows = 50;
						break;
					case IDC_RADIO100:
						ncols = nrows = 100;
						break;
					case IDC_RADIO200:
						ncols = nrows = 200;
						break;
					case IDC_RADIO400:
						ncols = nrows = 400;
						break;
					case IDC_RADIO800:
						ncols = nrows = 800;
						break;
					}
					if( restart ) {		//If "Ok" reset things
						nstomata = 10;
						Startup();
						NewStomata();
						Randomize();	//And pick out new stomata locations
						InvalidateRect( hWnd, NULL, TRUE );
						}
					break;

				case IDM_STOMATA:	//Call stomata dialog (number and locations)
					DialogBox( hInst, MAKEINTRESOURCE(IDD_STOMATA), hWnd, (DLGPROC) StomataDlgProc );
					if( restart ) {		//If stomata moved, reset the model
						FreeMem();
						Startup(); }
					InvalidateRect( hWnd, NULL, FALSE );
					break;

				case IDM_PARAMS:	//Call the parameters dialog
					DialogBox( hInst, MAKEINTRESOURCE(IDD_PARAMS), hWnd, (DLGPROC) ParamsDlgProc );
					strcpy( szCurrentTitle, szTitle );
					strcat( szCurrentTitle, Filename );
					SetWindowText( hWnd, szCurrentTitle );
					break;

				case IDM_LOADPARAMS:	//Load model params from a file
					bFile = OpenNewFile( hWnd );
					if ( bFile ) {
						ReadFileData();
						if( bDataValid ) {
							hMenu = GetMenu( hWnd );	//Enable menus if data ok
							EnableMenuItem( hMenu, IDM_DIM, MF_ENABLED );
							EnableMenuItem( hMenu, IDM_STOMATA, MF_ENABLED );
							EnableMenuItem( hMenu, IDM_PARAMS, MF_ENABLED );
							EnableMenuItem( hMenu, IDM_DISPLAY, MF_ENABLED );
							DrawMenuBar( hWnd );
							InvalidateRect( hWnd, NULL, TRUE );
							strcpy( szCurrentTitle, szTitle );
							strcat( szCurrentTitle, Filename );
							SetWindowText( hWnd, szCurrentTitle ); } }
					else
						MessageBox( hWnd, "Invalid File name!!", "Error", MB_OK | MB_ICONINFORMATION );
					//	return 0;
					break;

				case IDM_DISPLAY:	//Call the display parameters dialog
					TempTint = uTint;
					DialogBox( hInst, MAKEINTRESOURCE(IDD_DISPLAY), hWnd, (DLGPROC) DisplayDlgProc );
					if( uTint != TempTint ) {	//If timer interval changed, restart timer
						KillTimer( hWnd, nTimerID );
						nTimerID = SetTimer( hWnd, 1L, uTint, NULL ); }
					InvalidateRect( hWnd, NULL, FALSE );
					break;

				default:
				   return DefWindowProc(hWnd, message, wParam, lParam);
			}
			break;

		case WM_CREATE:					//Set initial values for many variables
			strcpy( Filename, "output.dat" );
			strcpy( szCurrentTitle, szTitle );		//Set window title default file name
			strcat( szCurrentTitle, Filename );
			SetWindowText( hWnd, szCurrentTitle );

			sprintf( cMutexName, "%i", time( NULL ) );
			hMutex = CreateMutex( NULL, FALSE, cMutexName );	//Pick a unique mutex name
			GetClientRect( hWnd, &rt );	//otherwise multiple program instances will get
			cxClient = rt.right;		//stuck sharing the same semaphore
			cyClient = rt.bottom;
			cxMap = 800;
			cyMap = 800;
			hdc = BeginPaint( hWnd, &ps );
			GetTextMetrics( hdc, &lptm );
			EndPaint( hWnd, &ps );
			cxChar = lptm.tmAveCharWidth;
			cyChar = lptm.tmHeight;
			iDimSize = IDC_RADIO100;
			nrows = 100;
			ncols = 100;
			ncells = nrows * ncols;
			nstomata = 10;
			spacing = 1;
			border = 1;
			kepi = 0.1;
			kin = 0.01;
			Jmax = 1.0;
			J0 = 0.01;
			a = 1.0;
			pminj = -10.0;
			uTint = 500;
			iDisplay = 1;
			minJ = 0;
			maxJ = 1.0;
			minp = INIT_P - 5;
			maxp = INIT_P;
			pleafoff = INIT_P;
			restart = FALSE;
			bAuto = TRUE;
			bStom = TRUE;
			bOffset = FALSE;
			Startup();		//Malloc initial memory
			NewStomata();
			stomX[0] = 10; stomX[1] = 10; stomX[2] = 20; stomX[3] = 30; stomX[4] = 50;
			stomX[5] = 60; stomX[6] = 60; stomX[7] = 80; stomX[8] = 80; stomX[9] = 80;
			stomY[0] = 20; stomY[1] = 60; stomY[2] = 40; stomY[3] = 80; stomY[4] = 30;
			stomY[5] = 40; stomY[6] = 70; stomY[7] = 20; stomY[8] = 40; stomY[9] = 70;
			nTimerID = SetTimer( hWnd, 1L, uTint, NULL );
			srand( time( NULL ) );
			rand();		// Gets rid of that first not-so-random draw
			for( i=0; i<256; i++ )	//Create a set of 256 gray-scale brushes
				hBrush[i] = CreateSolidBrush( RGB( i, i, i ) );
			break;

		case WM_CHAR:	//If space bar pushed, toggle pause, but not if move thread running
			if( ( cThreadGo == FALSE ) && ( wParam == 0x20 ) ) {
				pause = !pause;
				if( !pause ) {
					hMenu = GetMenu( hWnd );	//Disable menus if running
					EnableMenuItem( hMenu, IDM_START, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STEP, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_RESTART, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_SAVE, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_DIM, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STOMATA, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_PARAMS, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STARTFULL, MF_GRAYED );
					EnableMenuItem( hMenu, IDM_STOPFULL, MF_GRAYED );
					DrawMenuBar( hWnd );
				}
				else {
					hMenu = GetMenu( hWnd );	//Enable menus if stopped
					EnableMenuItem( hMenu, IDM_START, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STEP, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_RESTART, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_SAVE, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_DIM, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STOMATA, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_PARAMS, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STARTFULL, MF_ENABLED );
					EnableMenuItem( hMenu, IDM_STOPFULL, MF_ENABLED );
					DrawMenuBar( hWnd );
				}
			}
			InvalidateRect( hWnd, NULL, FALSE );	//Any key press will repaint!
			break;

		case WM_TIMER:	//Message from timer
			if( !pause ) {		//If not paused, do one move
				Move( t );
				t++;
				if( (t % iDisplay) == 0 )	//Every iDisplay steps, repaint map
					InvalidateRect( hWnd, NULL, FALSE );
				}
			break;

		case WM_PAINT:		//Paint the map
			hdc = BeginPaint(hWnd, &ps);
			cxSquare = cxMap / ncols;		//Find size of each model cell
			cySquare = cyMap / nrows;
			cxStart = 40;	//Map starting points
			cyStart = 5;

			dwWaitResult = WaitForSingleObject( hMutex, 10000L );
			if( dwWaitResult == WAIT_TIMEOUT )
				MessageBox( NULL, "Can't get mutex to repaint!!", "Error", MB_OK );
			fScale = 255.0F / (float)(maxp - minp );

			for( i=0; i<nrows; i++ )			//Draw all cells
				{
					for( j=0; j<ncols; j++ )
						{
							if( (j % 2) == 0 ) cyStart = 8;
							else cyStart = 8 - cySquare / 2;
							rt.top = cyStart + i * cySquare;	//rt is map cell location
							rt.bottom = rt.top + cySquare;
							rt.left = cxStart + j * cxSquare;
							rt.right = rt.left + cxSquare;
							if( p[i][j] <= minp ) iColor = 0;			//make black
							else if ( p[i][j] >= maxp ) iColor = 255;	//make white
							else iColor = (int)( (p[i][j] - minp) * fScale );	//scale color
							FillRect( hdc, &rt, hBrush[iColor] );
						}
				}

			offsetY = cySquare / 2 - 2;
			offsetX = cxSquare / 2 - 2;
			for( i=0; i<nstomata; i++ )			//Draw colored stomata
				{
				if( (stomX[i] % 2) == 0 ) cyStart = 8;
				else cyStart = 8 - cySquare / 2;
				rt.top = cyStart + stomY[i] * cySquare + offsetY;	
				rt.bottom = rt.top + 4;
				rt.left = cxStart + stomX[i] * cxSquare + offsetX;
				rt.right = rt.left + 4;
				fScale = 255.0F / (float) (maxJ - minJ );
				iColor = (int)( (Jout[i] - minJ) * fScale );
				if( iColor < 64 )		//Blue
					iBlue = 255;
				else if( iColor < 128 )
					iBlue = 255 - 4 * ( iColor - 64 );
				else
					iBlue = 0;
				if( iColor < 64 )		//Green
					iGreen = 4 * iColor;
				else if( iColor < 192 )
					iGreen = 255;
				else
					iGreen = 255 - 4 * ( iColor - 192 );
				if( iColor < 128 )		//Red
					iRed = 0;
				else if( iColor < 192 )
					iRed = 4 * ( iColor - 128 );
				else
					iRed = 255;
				hbr = CreateSolidBrush( RGB( iRed, iGreen, iBlue ) );
				FillRect( hdc, &rt, hbr );
				DeleteObject( hbr );
				}
			rt.left = 14;
			rt.right = rt.left + 8;
			for( i=0; i<256; i++ )				//Draw color bar on left edge
			{
				rt.top = 150 + i * 2;	
				rt.bottom = rt.top + 2;
				iColor = i;
				if( iColor < 64 )		//Blue
					iBlue = 255;
				else if( iColor < 128 )
					iBlue = 255 - 4 * ( iColor - 64 );
				else
					iBlue = 0;
				if( iColor < 64 )		//Green
					iGreen = 4 * iColor;
				else if( iColor < 192 )
					iGreen = 255;
				else
					iGreen = 255 - 4 * ( iColor - 192 );
				if( iColor < 128 )		//Red
					iRed = 0;
				else if( iColor < 192 )
					iRed = 4 * ( iColor - 128 );
				else
					iRed = 255;
				hbr = CreateSolidBrush( RGB( iRed, iGreen, iBlue ) );
				FillRect( hdc, &rt, hbr );
				DeleteObject( hbr );
			}
			nxText = 3;							//Add text for max, min Jout
			nyText = (150 - cyChar);
			iLength = sprintf( PaintBuf, "%4.3f", minJ );
			TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			nyText = 665;
			iLength = sprintf( PaintBuf, "%4.3f", maxJ );
			TextOut( hdc, nxText, nyText, PaintBuf, iLength );

			nyText = (cyClient - cyChar);
			SetTextColor( hdc, 0x000000FF );	//Show move status in red
			if( ( pause == FALSE ) || ( cThreadGo == TRUE ) ) {
				iLength = sprintf( PaintBuf, "Running" );
				nxText = 5;
				TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			}
			else {
				iLength = sprintf( PaintBuf, "Stopped" );
				nxText = 5;
				TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			}
			SetTextColor( hdc, 0 );
			iLength = sprintf( PaintBuf, "Mean p = %5.2f     ", meanp );
			nxText = 185;
			TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			iLength = sprintf( PaintBuf, "Min p = %5.2f             ", minp );
			nxText = 330;
			TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			iLength = sprintf( PaintBuf, "Max p = %5.2f             ", maxp );
			nxText = 455;
			TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			iLength = sprintf( PaintBuf, "Time step = %i          ", t );
			nxText = 580;
			TextOut( hdc, nxText, nyText, PaintBuf, iLength );
			
			EndPaint(hWnd, &ps);
			ReleaseMutex( hMutex );
			break;

		case WM_DESTROY:
			cThreadGo = FALSE;
			CloseHandle( hMutex );
			KillTimer( hWnd, nTimerID );
			FreeMem();		//Free up all allocated memory
			free( stomX );
			free( stomY );
			free( stomXtemp );
			free( stomYtemp );
			free( Jout );
			PostQuitMessage(0);
			for( i=0; i<256; i++ )	//Delete the set of gray-scale brushes
				DeleteObject( hBrush[i] );
			break;

		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}

LRESULT CALLBACK DimensionDlgProc( HWND hDlg, UINT message,  WPARAM wParam, LPARAM lParam )
{

	static int TempDimSize;
	static int iLength;
	HWND hCtrl;

	switch(message)
		{
		case WM_INITDIALOG:		//Load dialog values
			TempDimSize = iDimSize;
			hCtrl = GetDlgItem( hDlg, TempDimSize );
			SendMessage( hCtrl, BM_SETCHECK, TRUE, 0 );
			return TRUE;
			break;
		
		case WM_COMMAND:
			switch(LOWORD(wParam))
				{
				case IDOK:
					restart = TRUE;	//Restart after freeing memory
					FreeMem();		//Must be done before ncols is changed
					iDimSize = TempDimSize;
					EndDialog(hDlg, IDOK);
					break;

				case IDCANCEL:
					restart = FALSE;
					EndDialog(hDlg, IDCANCEL);
					break;

				case IDC_RADIO50:
				case IDC_RADIO100:
				case IDC_RADIO200:
				case IDC_RADIO400:
				case IDC_RADIO800:
					if( HIWORD( wParam ) == BN_CLICKED )
					{
						TempDimSize = LOWORD( wParam );
					}
					break;
				}
			break;
		
		default:
			return FALSE;
		}
	return TRUE;
}

LRESULT CALLBACK StomataDlgProc( HWND hDlg, UINT message,  WPARAM wParam, LPARAM lParam )
{
	int i, iCount;
	static int iLength;
	char MLEBufferX[5*MAX_NSTOM+1];
	char MLEBufferY[5*MAX_NSTOM+1];
	char *pBuffIndex;
	BOOL bError;

	switch(message)
		{
		case WM_INITDIALOG:		//Load dialog data
			iLength = sprintf( snstomata, "%i", nstomata );
			SetDlgItemText( hDlg, IDC_NSTOMATA, snstomata );
			iLength = sprintf( sspacing, "%5.2f", spacing );
			SetDlgItemText( hDlg, IDC_SPACING, sspacing );
			iLength = sprintf( sborder, "%i", border );
			SetDlgItemText( hDlg, IDC_BORDER, sborder );

			memset( &MLEBufferX, 0x20, 5*MAX_NSTOM );	//Load column locations into MLE
			pBuffIndex = MLEBufferX;
			for( i=0; i<nstomata; i++ ) {
				sprintf( pBuffIndex, "%5i", stomX[i] );
				pBuffIndex += 5;
				}
			SetDlgItemText( hDlg, IDC_STOMX, MLEBufferX );
			memset( &MLEBufferY, 0x20, 5*MAX_NSTOM );	//Load row locations into MLE
			pBuffIndex = MLEBufferY;
			for( i=0; i<nstomata; i++ ) {
				sprintf( pBuffIndex, "%5i", stomY[i] );
				pBuffIndex += 5;
				}
			SetDlgItemText( hDlg, IDC_STOMY, MLEBufferY );

			for( i=0; i<nstomata; i++ ) {	//Backup the stomata locations
				stomXtemp[i] = stomX[i];
				stomYtemp[i] = stomY[i]; }
			TempSpacing = spacing;			//Backup in case of "cancel"
			TempBorder = border;

			return TRUE;
		
		case WM_COMMAND:
			switch(LOWORD(wParam))
				{
				case IDOK:
					restart = TRUE;		//If no "apply" must ignore change in nstomata
					bError = FALSE;
					GetDlgItemText( hDlg, IDC_SPACING, sspacing, 31 );
					spacing = (double) atof( sspacing );
					GetDlgItemText( hDlg, IDC_BORDER, sborder, 31 );
					border = atoi( sborder );
					if( (nstomata>MAX_NSTOM) || (nstomata>iDimSize*iDimSize) ) {
						MessageBox( NULL, "Too many stomata!!", "Error", MB_OK );
						bError = TRUE;
						break;
					}
					GetDlgItemText( hDlg, IDC_STOMX, MLEBufferX, 5*MAX_NSTOM );	//Read MLE data
					pBuffIndex = MLEBufferX;
					for( i=0; i<nstomata; i++ ) {
						sscanf( pBuffIndex, "%i%n", &stomX[i], &iCount );
						pBuffIndex += iCount;
						if( (stomX[i]<0) || (stomX[i]>=ncols) ) {
							MessageBox( NULL, "Illegal location for stomata!!", "Error", MB_OK );
							bError = TRUE; }
						}
					GetDlgItemText( hDlg, IDC_STOMY, MLEBufferY, 5*MAX_NSTOM );
					pBuffIndex = MLEBufferY;
					for( i=0; i<nstomata; i++ ) {
						sscanf( pBuffIndex, "%i%n", &stomY[i], &iCount );
						pBuffIndex += iCount;
						if( (stomY[i]<0) || (stomY[i]>=nrows) ) {
							MessageBox( NULL, "Illegal location for stomata!!", "Error", MB_OK );
							bError = TRUE; }
						}
					if( !bError ) EndDialog(hDlg, IDOK);
					break;

				case IDCANCEL:
					restart = FALSE;
					for( i=0; i<nstomata; i++ ) {	//Restore the stomata locations
						stomX[i] = stomXtemp[i];
						stomY[i] = stomYtemp[i]; }
					spacing = TempSpacing;			//Restore old data
					border = TempBorder;
					EndDialog(hDlg, IDCANCEL);
					break;

				case IDC_APPLY:						//Read number and specs for stomata
					GetDlgItemText( hDlg, IDC_NSTOMATA, snstomata, 31 );
					nstomata = atoi( snstomata );
					if( (nstomata>MAX_NSTOM) || (nstomata>iDimSize*iDimSize) ) {
						MessageBox( NULL, "Too many stomata!!", "Error", MB_OK );
						bError = TRUE;
						break;
					}
					GetDlgItemText( hDlg, IDC_SPACING, sspacing, 31 );
					spacing = (double) atof( sspacing );
					GetDlgItemText( hDlg, IDC_BORDER, sborder, 31 );
					border = atoi( sborder );
					NewStomata();					//Reallocate memory
					Randomize();					//Pick new locations
					memset( &MLEBufferX, 0x20, 5*MAX_NSTOM );	//Write new locations
					pBuffIndex = MLEBufferX;
					for( i=0; i<nstomata; i++ ) {
						sprintf( pBuffIndex, "%5i", stomX[i] );
						pBuffIndex += 5;
						}
					SetDlgItemText( hDlg, IDC_STOMX, MLEBufferX );
					memset( &MLEBufferY, 0x20, 5*MAX_NSTOM );
					pBuffIndex = MLEBufferY;
					for( i=0; i<nstomata; i++ ) {
						sprintf( pBuffIndex, "%5i", stomY[i] );
						pBuffIndex += 5;
						}
					SetDlgItemText( hDlg, IDC_STOMY, MLEBufferY );
					for( i=0; i<nstomata; i++ ) {	//Backup the stomata locations
							stomXtemp[i] = stomX[i];
							stomYtemp[i] = stomY[i]; }
					break;

				case IDC_RANDOM:
					GetDlgItemText( hDlg, IDC_SPACING, sspacing, 31 );
					spacing = (double) atof( sspacing );
					GetDlgItemText( hDlg, IDC_BORDER, sborder, 31 );
					border = atoi( sborder );
					Randomize();
					memset( &MLEBufferX, 0x20, 5*MAX_NSTOM );
					pBuffIndex = MLEBufferX;
					for( i=0; i<nstomata; i++ ) {
						sprintf( pBuffIndex, "%5i", stomX[i] );
						pBuffIndex += 5;
						}
					SetDlgItemText( hDlg, IDC_STOMX, MLEBufferX );
					memset( &MLEBufferY, 0x20, 5*MAX_NSTOM );
					pBuffIndex = MLEBufferY;
					for( i=0; i<nstomata; i++ ) {
						sprintf( pBuffIndex, "%5i", stomY[i] );
						pBuffIndex += 5;
						}
					SetDlgItemText( hDlg, IDC_STOMY, MLEBufferY );
					break;
				}
		break;
		
		default:
			return FALSE;
		}
	return TRUE;
}

LRESULT CALLBACK ParamsDlgProc( HWND hDlg, UINT message,  WPARAM wParam, LPARAM lParam )
{

	static int iLength;
	long hCheck;

	switch(message)
		{
		case WM_INITDIALOG:			//Load dialog data
			iLength = sprintf( skepi, "%6.4f", kepi );
			SetDlgItemText( hDlg, IDC_KEPI, skepi );
			iLength = sprintf( skin, "%7.5f", kin );
			SetDlgItemText( hDlg, IDC_KIN, skin );
			iLength = sprintf( sJmax, "%5.2f", Jmax );
			SetDlgItemText( hDlg, IDC_JMAX, sJmax );
			iLength = sprintf( sJ0, "%5.2f", J0 );
			SetDlgItemText( hDlg, IDC_J0, sJ0 );
			iLength = sprintf( sa, "%5.3f", a );
			SetDlgItemText( hDlg, IDC_A, sa );
			iLength = sprintf( spminj, "%5.2f", pminj );
			SetDlgItemText( hDlg, IDC_PMINJ, spminj );
			iLength = sprintf( spleafoff, "%5.2f", pleafoff );
			SetDlgItemText( hDlg, IDC_PLEAF, spleafoff );
			SetDlgItemText( hDlg, IDC_SAVEFILE, Filename );
			if( bOffset )											//Set checkbox state
				SendDlgItemMessage( hDlg, IDC_OFFSET, BM_SETCHECK, (WPARAM) BST_CHECKED, 0 );
			else SendDlgItemMessage( hDlg, IDC_OFFSET, BM_SETCHECK, (WPARAM) BST_UNCHECKED, 0 );
			return TRUE;
		
		case WM_COMMAND:
			switch(LOWORD(wParam))
				{
				case IDOK:
					GetDlgItemText( hDlg, IDC_KEPI, skepi, 31 );	//If ok, read new data
					kepi = (double) atof( skepi );
					GetDlgItemText( hDlg, IDC_KIN, skin, 31 );
					kin = (double) atof( skin );
					GetDlgItemText( hDlg, IDC_JMAX, sJmax, 31 );
					Jmax = (double) atof( sJmax );
					GetDlgItemText( hDlg, IDC_J0, sJ0, 31 );
					J0 = (double) atof( sJ0 );
					GetDlgItemText( hDlg, IDC_A, sa, 31 );
					a = (double) atof( sa );
					GetDlgItemText( hDlg, IDC_PMINJ, spminj, 31 );
					pminj = (double) atof( spminj );
					GetDlgItemText( hDlg, IDC_PLEAF, spleafoff, 31 );
					pleafoff = (double) atof( spleafoff );
					GetDlgItemText( hDlg, IDC_SAVEFILE, Filename, 65 );
					hCheck = SendDlgItemMessage( hDlg, IDC_OFFSET, BM_GETCHECK, 0, 0 );
					if( hCheck == BST_CHECKED ) bOffset = TRUE;	//Read checkbox state
					else bOffset = FALSE;
					EndDialog(hDlg, IDOK);
					break;

				case IDCANCEL:
					EndDialog(hDlg, IDCANCEL);
					break;
				}
		break;
		
		default:
			return FALSE;
		}
	return TRUE;
}

LRESULT CALLBACK DisplayDlgProc( HWND hDlg, UINT message,  WPARAM wParam, LPARAM lParam )
{

	static int iLength;
	long hCheck;

	switch(message)
		{
		case WM_INITDIALOG:		//Load dialog data
			iLength = sprintf( sminp, "%4.1f", minp );
			SetDlgItemText( hDlg, IDC_MINP, sminp );
			iLength = sprintf( smaxp, "%4.1f", maxp );
			SetDlgItemText( hDlg, IDC_MAXP, smaxp );
			iLength = sprintf( sminJ, "%4.3f", minJ );
			SetDlgItemText( hDlg, IDC_MINJ, sminJ );
			iLength = sprintf( smaxJ, "%4.3f", maxJ );
			SetDlgItemText( hDlg, IDC_MAXJ, smaxJ );
			iLength = sprintf( sTint, "%u", uTint );
			SetDlgItemText( hDlg, IDC_TIMEINT, sTint );
			iLength = sprintf( sDisplay, "%i", iDisplay );
			SetDlgItemText( hDlg, IDC_INTERVAL, sDisplay );
			if( bAuto )											//Set checkbox state
				SendDlgItemMessage( hDlg, IDC_AUTO, BM_SETCHECK, (WPARAM) BST_CHECKED, 0 );
			else SendDlgItemMessage( hDlg, IDC_AUTO, BM_SETCHECK, (WPARAM) BST_UNCHECKED, 0 );
			if( bStom )											//Set checkbox state
				SendDlgItemMessage( hDlg, IDC_SAUTO, BM_SETCHECK, (WPARAM) BST_CHECKED, 0 );
			else SendDlgItemMessage( hDlg, IDC_SAUTO, BM_SETCHECK, (WPARAM) BST_UNCHECKED, 0 );
		return TRUE;
		
		case WM_COMMAND:
			switch(LOWORD(wParam))
				{
				case IDOK:
					GetDlgItemText( hDlg, IDC_MINP, sminp, 31 );
					minp = (float) atof( sminp );
					GetDlgItemText( hDlg, IDC_MAXP, smaxp,31 );
					maxp = (float) atof( smaxp );
					GetDlgItemText( hDlg, IDC_MINJ, sminJ, 31 );
					minJ = (float) atof( sminJ );
					GetDlgItemText( hDlg, IDC_MAXJ, smaxJ,31 );
					maxJ = (float) atof( smaxJ );
					GetDlgItemText( hDlg, IDC_TIMEINT, sTint, 31 );
					uTint = atoi( sTint );
					GetDlgItemText( hDlg, IDC_INTERVAL, sDisplay, 31 );
					iDisplay = atoi( sDisplay );
					hCheck = SendDlgItemMessage( hDlg, IDC_AUTO, BM_GETCHECK, 0, 0 );
					if( hCheck == BST_CHECKED ) bAuto = TRUE;	//Read checkbox state
					else bAuto = FALSE;
					hCheck = SendDlgItemMessage( hDlg, IDC_SAUTO, BM_GETCHECK, 0, 0 );
					if( hCheck == BST_CHECKED ) bStom = TRUE;	//Read checkbox state
					else bStom = FALSE;
					EndDialog(hDlg, IDOK);
					break;
				case IDCANCEL:
					EndDialog(hDlg, IDCANCEL);
					break;
				}
		break;
		
		default:
			return FALSE;
		}
	return TRUE;
}

void Move( int t )		//Execute one "move"
{
	int i, j, k;

	if ( bOffset ) pleaf = meanp + pleafoff;	//If checked, pleafoff is an offset from meanp
	else	pleaf = pleafoff;	//Oterhwise pleafoff is just a constant value
	for( i=1; i<nrows-1; i++ )	//Do all interior cells
		{
			for( j=1; j<ncols-1; j++ )
				{
				if( (j % 2) == 0 )	//column is even
					{
					dp = -kepi * ( 6 * p[i][j] - p[i-1][j] - p[i+1][j] - p[i][j-1]
									 - p[i][j+1] - p[i+1][j-1] - p[i+1][j+1] );
					}
				else				//column is odd
					{
					dp = -kepi * ( 6 * p[i][j] - p[i-1][j] - p[i+1][j] - p[i-1][j-1]
									 - p[i-1][j+1] - p[i][j-1] - p[i][j+1] );
					}
				dp += kin * ( pleaf - p[i][j] );
				pnew[i][j] = p[i][j] + dp;	//Temp store values in pnew
				}
		}
	j = 0;						//Do left column, except for corners
	for( i=1; i<nrows-1; i++ )
		{
			dp = -kepi * ( 4 * p[i][j] - p[i-1][j] - p[i+1][j] - p[i][j+1] - p[i+1][j+1] );
			dp += kin * ( pleaf - p[i][j] );
			pnew[i][j] = p[i][j] + dp;
		}
	j = ncols-1;				//Do right column, except for corners
	for( i=1; i<nrows-1; i++ )
		{
			dp = -kepi * ( 4 * p[i][j] - p[i-1][j] - p[i+1][j] - p[i][j-1] - p[i+1][j-1] );
			dp += kin * ( pleaf - p[i][j] );
			pnew[i][j] = p[i][j] + dp;
		}
	i = 0;						//Do top row, except for corners
	for( j=1; j<ncols-1; j++ )
		{
		if( (j % 2) == 0 )	//even column in top row
			{
			dp = -kepi * ( 5 * p[i][j] - p[i+1][j] - p[i][j-1] - p[i][j+1] 
							- p[i+1][j-1] - p[i+1][j+1] );
			}
		else				//odd column in top row
			{
			dp = -kepi * ( 3 * p[i][j] - p[i+1][j] - p[i][j-1] - p[i][j+1] );
			}
		dp += kin * ( pleaf - p[i][j] );
		pnew[i][j] = p[i][j] + dp;
		}
	i = nrows-1;				//Do bottom row, except for corners
	for( j=1; j<ncols-1; j++ )
		{
		if( (j % 2) == 0 )	//even column in bottom row
			{
			dp = -kepi * ( 3 * p[i][j] - p[i-1][j] - p[i-1][j-1] - p[i-1][j+1] );
			}
		else				//odd column in bottom row
			{
			dp = -kepi * ( 5 * p[i][j] - p[i-1][j] - p[i-1][j-1] - p[i-1][j+1] 
							- p[i][j-1] - p[i][j+1] );
			}
		dp += kin * ( pleaf - p[i][j] );
		pnew[i][j] = p[i][j] + dp;
		}
											//Upper-left corner
	pnew[0][0] = p[0][0] - kepi * ( 3 * p[0][0] - p[0][1] - p[1][1] - p[1][0] ) + 
					kin * ( pleaf - p[0][0] );
											//Upper-right corner
	pnew[0][ncols-1] = p[0][ncols-1] - kepi * ( 2 * p[0][ncols-1] - p[1][ncols-1] 
						- p[0][ncols-2] ) + kin * ( pleaf - p[0][ncols-1] );
											//Lower-left corner
	pnew[nrows-1][0] = p[nrows-1][0] - kepi * ( 2 * p[nrows-1][0] - p[nrows-2][0]
						- p[nrows-1][1] ) + kin * ( pleaf - p[nrows-1][0] );
											//Lower-right corner
	pnew[nrows-1][ncols-1] = p[nrows-1][ncols-1] - kepi * ( 3 * p[nrows-1][ncols-1]
							- p[nrows-1][ncols-2] - p[nrows-2][ncols-2]
							- p[nrows-2][ncols-1] )
							+ kin * ( pleaf - p[nrows-1][ncols-1] );

	if( bStom ) {
		maxJ = 0;		//If auto is checked for stomata, want min and max J
		minJ = Jmax; }
	for( k=0; k<nstomata; k++ ) {
		Jout[k] = Jmax * J0 / ( J0 + ( Jmax - J0 )	//logistic control function
					* exp( -a * ( p[stomY[k]][stomX[k]] - pminj ) ) );
		//Use the next line to only subtract off the stomatal trans
		pnew[stomY[k]][stomX[k]] -= Jout[k];
		//Use the next line to subtract off both the stomatal trans and inflow from the leaf
//		pnew[stomY[k]][stomX[k]] -= ( Jout[k] + kin * ( pleaf - p[stomY[k]][stomX[k]] ) );
		if( bStom ) {				//If auto is checked for stomata, want min and max J
			minJ = min( minJ, Jout[k] );
			maxJ = max( maxJ, Jout[k] ); }
		}											
	if( bAuto ) {
		minp = FLT_MAX;		//If auto is checked, want min and max p
		maxp = -FLT_MAX; }
	meanp = 0;
	for( i=0; i<nrows; i++ )
		{
		for( j=0; j<ncols; j++ )
			{
			p[i][j] = pnew[i][j];	//Overwrite old p values with new p values
			meanp += p[i][j];
			if( bAuto ) {
				minp = min( minp, p[i][j] );
				maxp = max( maxp, p[i][j] ); }
			}
		}
	meanp /= ncells;
	return;
}

void MoveFull( void * )
{

	DWORD dwWaitResult;
//	static HWND hWnd = *( (HWND *) param );
//	HMENU hMenu;
	int steps, tmod;
	switch ( nrows * ncols )	//Pick the numbers of steps between repaintings so that
	{								//a few seconds is typical.  Larger dim arrays take
	case 2500:						//longer to "move"
		steps = 10000;
		break;
	case 10000:
		steps = 10000;
		break;
	case 40000:				//This function is launched as a new thread
		steps = 2500;		//that must use semaphores to avoid colliding with the 
		break;				//use of the data arrays by the repainting process
	case 160000:
		steps = 1000;
		break;
	case 640000:
		steps = 250;
		break;
	default:
		steps = 1000;
	}
	do
	{
		dwWaitResult = WaitForSingleObject( hMutex, 5000L );
		if( dwWaitResult == WAIT_TIMEOUT )
			MessageBox( NULL, "Can't get mutex to move!!", "Error", MB_OK );
		Move ( t );
		t++;
		ReleaseMutex( hMutex );
		tmod = ( t % steps );
		if( tmod == 0 )	{	//Every iDisplay steps, repaint map
				InvalidateRect( hWnd, NULL, FALSE );
			//	RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
		}
		if( ( minp < -10000 ) || _isnan( minp ) ) {	//Model unstable, terminate run
			cThreadGo = FALSE;
			tmod = 0; }
	}	while ( cThreadGo || tmod );	//Stop at round step when user says to stop
	hMenu = GetMenu( hWnd );	//Enable menus only after moving has finished
	EnableMenuItem( hMenu, IDM_START, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_STOP, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_STEP, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_RESTART, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_SAVE, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_DIM, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_STOMATA, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_PARAMS, MF_ENABLED );
	EnableMenuItem( hMenu, IDM_STARTFULL, MF_ENABLED );
	DrawMenuBar( hWnd );
	InvalidateRect( hWnd, NULL, FALSE );
	_endthread();
}

void FreeMem( void )
{
	int j;
	for( j=0; j<ncols; j++ ) {
		free( p[j] );
		free( pnew[j] ); }
	free( p );
	free( pnew );
	return;
}

void Startup( void )
{

	int i, j;

	p = (double **) malloc( nrows * sizeof( double * ) );
	for( j=0; j<nrows; j++ )
		{
		p[j] = (double *) malloc( (ncols) * sizeof( double ) );
		}
	pnew = (double **) malloc( nrows * sizeof( double * ) );
	for( j=0; j<nrows; j++ )
		{
		pnew[j] = (double *) malloc( (ncols) * sizeof( double ) );
		}
	Jout = (double *) malloc( nstomata * sizeof( double ) );
	for( i=0; i<ncols; i++ )	//Set initial p for all cells
		{
		for( j=0; j<nrows; j++ )
			{
			p[i][j] = INIT_P;
			}
		}
	maxJ = 0;
	minJ = FLT_MAX;
	for( i=0; i<nstomata; i++ ) {
		Jout[i] = Jmax * J0 / ( J0 + ( Jmax - J0 ) * exp( -a * ( INIT_P - pminj ) ) );
		minJ = min( minJ, Jout[i] );
		maxJ = max( maxJ, Jout[i] );
		}											
	ncells = nrows * ncols;
	minp = INIT_P - 5;
	maxp = INIT_P + 5;
	meanp = INIT_P;
	t = 0;
	pause = TRUE;
	return;
}

void NewStomata( void )
{

	free( stomX );
	free( stomY );
	free( stomXtemp );
	free( stomYtemp );
	free( Jout );
	stomX = (int *) malloc( nstomata * sizeof( int ) );
	stomY = (int *) malloc( nstomata * sizeof( int ) );
	stomXtemp = (int *) malloc( nstomata * sizeof( int ) );
	stomYtemp = (int *) malloc( nstomata * sizeof( int ) );
	Jout = (double *) malloc( nstomata * sizeof( double ) );
	return;
}

void Randomize ( void )		//Randomly locate stomata
{

	int i, j, maxxint, maxyint, minxint, minyint, trap;
	double x, y, dd;
	int xint, yint;
	BOOL bUnique;

	maxxint = ncols-1-border;	//Locations must exclude borders if border>0
	maxyint = nrows-1-border;
	minxint = border;
	minyint = border;

	i=0; trap = 0;
	do
	{
		x = rand() / (RAND_MAX + 1.0);								//Guess location
		xint = (int) floor( (maxxint-minxint+1) * x ) + minxint;
		y = rand() / (RAND_MAX + 1.0);
		yint = (int) floor( (maxyint-minyint+1) * y ) + minyint;
		bUnique = TRUE;
		for( j=0; j<i; j++ )	//Search thru previous locations
			{
				dd = sqrt( double((stomX[j]-xint) * (stomX[j]-xint) +	//Calculate distance
							(stomY[j]-yint) * (stomY[j]-yint)) );
				if( dd < spacing ) {
					bUnique = FALSE;	//New location must be unique, or
					break; }			//away from the edge or other stomata
			}
		if( bUnique ) {
			stomX[i] = xint;
			stomY[i] = yint;
			i++;
			}
		trap++;
	}
	while ( i<nstomata && trap<1000000L );	//If run out of space, don't keep trying!
	if( trap >= 1000000L )
		MessageBox( NULL, "Randomize Failure!!", "Error", MB_OK );

	return;
}

BOOL OpenNewFile( HWND hWnd )
{

	OPENFILENAME OpenFileName;
	char szFile[MAX_PATH];
	char CurrentDir[MAX_PATH];

	szFile[0] = 0;
	GetCurrentDirectory( MAX_PATH, CurrentDir );

	OpenFileName.lStructSize = sizeof( OPENFILENAME );
	OpenFileName.hwndOwner = hWnd;
	OpenFileName.lpstrFilter = "Data Files\0*.dat\0\0";
	OpenFileName.lpstrCustomFilter = NULL;
	OpenFileName.nMaxCustFilter = 0;
	OpenFileName.nFilterIndex = 0;
	OpenFileName.lpstrFile = szFile;
	OpenFileName.nMaxFile = sizeof( szFile );
	OpenFileName.lpstrFileTitle = NULL;
	OpenFileName.nMaxFileTitle = 0;
	OpenFileName.lpstrInitialDir = CurrentDir;
	OpenFileName.lpstrTitle = "Open a file";
	OpenFileName.nFileOffset = 0;
	OpenFileName.nFileExtension = 0;
	OpenFileName.lpstrDefExt = NULL;
	OpenFileName.lCustData = 0;
	OpenFileName.lpfnHook = NULL;
	OpenFileName.lpTemplateName = NULL;
	OpenFileName.Flags = OFN_EXPLORER;

	if( GetOpenFileName( &OpenFileName ) )
	{
		strcpy( Filename, szFile );
		return TRUE;
	}
	else
		return FALSE;
}

void ReadFileData( void )
{
    FILE *infile;
	int i, tempncols;

	bDataValid = FALSE;
    if( (infile = fopen( Filename, "r" )) == NULL ) {
        ShowMessageBox( 1 );
        return; }
    if( fscanf( infile, "%i %i %i %i %i", &cxMap, &cyMap, &nrows, &tempncols, &t ) != 5 )  {
        ShowMessageBox( 2 );
        fclose( infile );
        return; }
    if( fscanf( infile, "%le %le", &kepi, &kin ) != 2 )  {
        ShowMessageBox( 2 );
        fclose( infile );
        return; }
    if( fscanf( infile, "%i %i %lf %lf %lf %lf %lf %lf\n", &nstomata, &border, &spacing,
									&Jmax, &J0, &a, &pminj, &pleafoff ) != 8 )  {
        ShowMessageBox( 2 );
        fclose( infile );
        return; }
	NewStomata();
	FreeMem();	//Free calls must use the old value of ncols
	ncols = tempncols;	//Now, new mallocs use the newly read value from the file
	Startup();
	for( i=0; i<nstomata; i++ )
		fscanf( infile, "%i %i %lf", &stomX[i], &stomY[i], &Jout[i] );
	fclose( infile );

	switch ( ncols )
	{
	case 50:
		iDimSize = IDC_RADIO50;
		break;
	case 100:
		iDimSize = IDC_RADIO100;
		break;
	case 200:
		iDimSize = IDC_RADIO200;
		break;
	case 400:
		iDimSize = IDC_RADIO400;
		break;
	case 800:
		iDimSize = IDC_RADIO800;
		break;
	default:
		ShowMessageBox( 2 );
		fclose( infile );
		return;
	}

	bDataValid = TRUE;
	return;
}

void ShowMessageBox( int msgnum )
{

    char ErrBuf[MAX_PATH+81];

    switch ( msgnum ) {
           case 1:
                sprintf( ErrBuf, "Cannot find file %s", Filename );
                MessageBox( NULL, ErrBuf, NULL, MB_OK | MB_ICONERROR );
                break;
           case 2:
                sprintf( ErrBuf, "Error in data file %s", Filename );
                MessageBox( NULL, ErrBuf, NULL, MB_OK | MB_ICONERROR );
                break;
           }
	bDataValid = FALSE;
	return;
}
