/*----------------------------------------------------------------*/
/* This file can be saved to your machine (almost) as you see it  */
/* by doing a "Save As..." from the File menu of your browser.    */
/*                                                                */
/* For this to work, you must TYPE a filename with .txt as the    */
/* file extent.  (Simply selecting .txt as the file type          */
/* doesn't seem to be sufficient.)  Remove the .txt extension     */
/* once the file is saved.  (IE inserts the page title as the     */
/* first line, which must be removed also.)                       */
/*----------------------------------------------------------------*/

/**************************************************/
/*
/*  Program:    hw5.c
/*              The fish simulation
/*  Author:     YOUR NAME HERE
/*  Date:       YOUR DATE HERE
/*
/*
/**************************************************/


#include "gp142.h"

#include <stdlib.h>
#include <time.h>


/***********************/
/* Program constants   */
/***********************/

/* size of the grid */
#define NUMROWS 50
#define NUMCOLS 50

/* fish breeding parameters */
#define MAX_FISH (NUMROWS*NUMCOLS)
#define FISH_BREED_TIME 15
#define NUM_INITIAL_FISH 10

/* various constants for figuring out the grid     */
/* coordinates                                     */
#define LEFTBORDER 10
#define RIGHTBORDER 10
#define TOPBORDER 10
#define BOTTOMBORDER 10

#define GRIDULX (-GP142_XMAX+LEFTBORDER)
#define GRIDULY (GP142_YMAX-TOPBORDER)
#define GRIDLRX (GP142_XMAX-RIGHTBORDER)
#define GRIDLRY (-GP142_YMAX+BOTTOMBORDER)
#define GRIDWIDTH (GRIDLRX-GRIDULX)
#define GRIDHEIGHT (GRIDULY-GRIDLRY)

#define BOXWIDTH (GRIDWIDTH/NUMCOLS)
#define BOXHEIGHT (GRIDHEIGHT/NUMROWS)

/* True and false */
#define TRUE 1
#define FALSE 0


/**************************/
/* Structure declarations */
/**************************/

/* store information about a square */
typedef struct {
    int occupied;
    int occupantID;
} GridSquare;

/* store information about a fish */
typedef struct {
    int processed;
    int alive;
    int breedTime;
} Fish;


/**************************/
/* Function declarations  */
/**************************/

GP142_point convertGridToGP142(int row, int col);
void initializeGrid(GridSquare theGrid[][NUMCOLS]);
void initializeFish(GridSquare theGrid[][NUMCOLS], Fish theFish[]);
void drawScreen(GridSquare grid[][NUMCOLS]);
void updateSimulation(GridSquare grid[][NUMCOLS], Fish theFish[]);
void processFish(int row, int col, GridSquare theGrid[][NUMCOLS], Fish theFish[]);
int findFreeAdjacentSquare(int row, int col, int *newRow, int *newCol, GridSquare theGrid[][NUMCOLS]);
int fishCrowded(int row, int col, GridSquare theGrid[][NUMCOLS]);
void createFish(int row, int col, GridSquare theGrid[][NUMCOLS], Fish theFish[]);


/**************************/
/* main funtion           */
/**************************/

int main(void) {
    /* event handling variables */
    int quit, event;
    char key_pressed;
    int mouse_x, mouse_y;

    /* Data structures */
    GridSquare theGrid[NUMROWS][NUMCOLS];
    Fish theFish[MAX_FISH];

    /* Initialize GP142 */
    GP142_open();
    GP142_logging(LOG_OFF);
    GP142_animate(ANI_RUN);
    GP142_clear();

    /* Initialize the random number generator */
    /* Uncomment for true randomness */
    srand(time(NULL));

    /* Initialize the data structures */
    initializeGrid(theGrid);
    initializeFish(theGrid, theFish);

    /* event loop */
    quit = FALSE;
    do {
        drawScreen(theGrid);
        /* grab an event */
        event = GP142_await_event(&mouse_x, &mouse_y, &key_pressed);
        
        /* handle the event */
        switch (event) {
            case GP142_QUIT:
                /* Quit selected */
                quit = TRUE;
                break;
            
            case GP142_KBD:
                /* Key pressed. Ignored in this application. */
                break;
            
            case GP142_MOUSE:
                /* Mouse click. Ignored in this application */
                break;
        
            case GP142_PERIODIC:
                /* Timer interval event. Update the simulation */
                updateSimulation(theGrid, theFish);
                break;
            
            default:
                printf("Received unknown event\n");
                /* unknown event */
                break;
            
        }  /* end switch */

    } while (!quit);
    
    /* Close everything down */
    GP142_close();
    
    return 0;
}


/* Convert a grid row and column to its corresponding   */
/* upper left x,y coordinate                            */
GP142_point convertGridToGP142(int row, int col) {
    GP142_point gpPoint;

    gpPoint.x = (col * BOXWIDTH) + GRIDULX;
    gpPoint.y = -((row * BOXHEIGHT) - GRIDULY);

    return gpPoint;
}


/* Initialize the grid to be empty */
void initializeGrid(GridSquare grid[][NUMCOLS]) {
    int row, col;

    for (row = 0; row < NUMROWS; row++) {
        for (col = 0; col < NUMCOLS; col++) {
            grid[row][col].occupied = FALSE;
        }
    }
}


/* Create the initial fish and clear the rest */
void initializeFish(GridSquare theGrid[][NUMCOLS], Fish theFish[]) {
    int i, success, row, col;

    /* Set defaults for fish */
    for (i = 0; i < MAX_FISH; i++) {
        theFish[i].alive = FALSE;
    }

    /* Create the initial fish */
    for (i = 0; i < NUM_INITIAL_FISH; i++) {
        success = FALSE;
        /* keep trying to get an unoccupied square */
        do {
            row = rand() % NUMROWS;
            col = rand() % NUMCOLS;
            if (!theGrid[row][col].occupied) {
                createFish(row, col, theGrid, theFish);
                success = TRUE;
            }
        } while (!success);
    }
}


/* Create a single fish */
void createFish(int row, int col, GridSquare theGrid[][NUMCOLS], Fish theFish[]) {
    int i;

    /* Search through to find a free spot */
    for (i = 0; i < MAX_FISH; i++) {
        if (theFish[i].alive == FALSE) {
            /* Set all the parameters */
            theFish[i].alive = TRUE;
            theFish[i].breedTime = 0;
            theGrid[row][col].occupied = TRUE;
            theGrid[row][col].occupantID = i;
            break;
        }
    }
}


/* Draw the grid and fish */
void drawScreen(GridSquare theGrid[][NUMCOLS]) {
    int row, col;
    GP142_point ulPoint;

    GP142_clear();

    /* Loop through all squares */
    for (row = 0; row < NUMROWS; row++) {
        for (col = 0; col < NUMCOLS; col++) {
            ulPoint = convertGridToGP142(row, col);
            
            /* Draw fish if occupied */
            if (theGrid[row][col].occupied) {
                GP142_rectangleXY(GREEN, 
                    ulPoint.x, ulPoint.y, 
                    ulPoint.x + BOXWIDTH, ulPoint.y - BOXHEIGHT, 
                    0);
            }

            /* Draw grid box */
            GP142_rectangleXY(BLACK, 
                ulPoint.x, ulPoint.y, 
                ulPoint.x + BOXWIDTH, ulPoint.y - BOXHEIGHT, 
                1);
        }
    }
}


/* Update the simulation, i.e. process fish */
void updateSimulation(GridSquare theGrid[][NUMCOLS], Fish theFish[]) {
    int i, row, col;

    /* We're starting a new round, nothing's processed yet */
    for (i = 0; i < MAX_FISH; i++) {
        theFish[i].processed = FALSE;
    }
    
    /* loop through all the squares */
    for (row = 0; row < NUMROWS; row++) {
        for (col = 0; col < NUMCOLS; col++) {
            if (theGrid[row][col].occupied) {
                processFish(row, col, theGrid, theFish);
            }
        }
    }
}


/* Process a single fish */
void processFish(int row, int col, GridSquare theGrid[][NUMCOLS], Fish theFish[]) {
    int ID = theGrid[row][col].occupantID;
    int newRow, newCol;

    /* Move the fish */
    if (findFreeAdjacentSquare(row, col, &newRow, &newCol, theGrid)) {
        theGrid[newRow][newCol].occupied = TRUE;
        theGrid[newRow][newCol].occupantID = theGrid[row][col].occupantID;
        theGrid[row][col].occupied = FALSE;
        row = newRow;
        col = newCol;
    }

    /* Update breeding time and breed if necessary */
    theFish[ID].breedTime++;
    if (theFish[ID].breedTime >= FISH_BREED_TIME) {
        /* Can we breed? */
        if (findFreeAdjacentSquare(row, col, &newRow, &newCol, theGrid)) {
            createFish(newRow, newCol, theGrid, theFish);
        }
        theFish[ID].breedTime = 0;
    }

    /* Check if fish should die now */
    if (fishCrowded(row, col, theGrid)) {
        theFish[ID].alive = FALSE;
        theGrid[row][col].occupied = FALSE;
    }

    /* Don't process this fish again */
    theFish[ID].processed = TRUE;
}


/* Return TRUE if the adjacent squares of the given row and column  */
/* are occupied, FALSE otherwise                                    */
int fishCrowded(int row, int col, GridSquare theGrid[][NUMCOLS]) {
    int upperRow, lowerRow, leftCol, rightCol;
    int numSurrounding;

    /* Figure out what the row above is */
    if (row == 0) {
        upperRow = NUMROWS - 1;
    } else {
        upperRow = row - 1;
    }

    /* Figure out what the row below is */
    lowerRow = (row + 1) % NUMROWS;

    /* Figure out what the column to the left is */
    if (col == 0) {
        leftCol = NUMCOLS - 1;
    } else {
        leftCol = col - 1;
    }

    /* Figure out what the column to the right is */
    rightCol = (col + 1) % NUMCOLS;

    /* Check all the adjacent squares */
    numSurrounding = 0;
    if (theGrid[upperRow][col].occupied) {
        numSurrounding++;
    }

    if (theGrid[lowerRow][col].occupied) {
        numSurrounding++;
    }

    if (theGrid[row][leftCol].occupied) {
        numSurrounding++;
    }

    if (theGrid[row][rightCol].occupied) {
        numSurrounding++;
    }

    /* If all are full, die */
    if (numSurrounding == 4) {
        return TRUE;
    } else {
        return FALSE;
    }
}


/* Find a random free adjacent square.  Set newRow and newCol   */
/* and return TRUE if one exists, otherwise return FALSE        */
int findFreeAdjacentSquare(int row, int col, int *newRow, int *newCol, GridSquare theGrid[][NUMCOLS]) {
    int upperRow, lowerRow, leftCol, rightCol;
    int freeArray[4][3];
    int i, j, dir;

    /* Set up the array of adjacent squares */
    for (i = 0; i < 4; i++) {
        freeArray[i][0] = FALSE;
    }

    /* Find the upper row */
    if (row == 0) {
        upperRow = NUMROWS - 1;
    } else {
        upperRow = row - 1;
    }

    /* Find the lower row */
    lowerRow = (row + 1) % NUMROWS;

    /* Find the left column */
    if (col == 0) {
        leftCol = NUMCOLS - 1;
    } else {
        leftCol = col - 1;
    }

    /* Find the right column */
    rightCol = (col + 1) % NUMCOLS;

    /* Figure out if upper square is occupied */
    if (!theGrid[upperRow][col].occupied) {
        freeArray[0][0] = TRUE;
        freeArray[0][1] = upperRow;
        freeArray[0][2] = col;
    }

    /* Figure out if lower square is occuped */
    if (!theGrid[lowerRow][col].occupied) {
        freeArray[1][0] = TRUE;
        freeArray[1][1] = lowerRow;
        freeArray[1][2] = col;
    }

    /* Figure out if left square is occupied */
    if (!theGrid[row][leftCol].occupied) {
        freeArray[2][0] = TRUE;
        freeArray[2][1] = row;
        freeArray[2][2] = leftCol;
    }

    /* Figure out if right square is occupied */
    if (!theGrid[row][rightCol].occupied) {
        freeArray[3][0] = TRUE;
        freeArray[3][1] = row;
        freeArray[3][2] = rightCol;
    }

    /* Pick a random direction */
    dir = rand() % 4;

    /* Move clockwise until we find an unoccupied square */
    for (i = dir, j = 0; j < 4; i = (i + 1) % 4, j++) {
        if (freeArray[i][0] == TRUE) {
            *newRow = freeArray[i][1];
            *newCol = freeArray[i][2];
            return TRUE;
        }
    }
    
    /* Didn't find one */
    return FALSE;   
}