/***************************************************************************

	hash_of_life.cpp	--	hash of life proof of concept

	Author:				Daniel creo Haslinger
	Website:			http://creo.blackmesa.at

	To understand what this application is doing, please read more
	about my project "HASH OF LIFE" on my website.


	This code simulates a cellular automaton and applies the conway
	universe rules (B3/S23) inside a torus of 32x20 cells.

	The cells preloaded in the beginning are representing the
	word "password" in binary.

****************************************************************************/



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <math.h>
#include <string.h>

using namespace std;

#define HEIGHT 20
#define WIDTH 32
#define LIFE_YES 1
#define LIFE_NO 0

// the following function calls
// should be easier to read...

typedef int TableType[HEIGHT][WIDTH];


void printTable(TableType table, TableType hashtable) 
{
        int height, width;		// dimensions of your field
	int bitcounter = 0;		// how many bits we already printed
	int blockcounter = 0;		// how many blocks we already printed

        for (height = 0; height < HEIGHT; height++) 
	{

                for (width = 0; width < WIDTH; width++) 
		{
                        if (table[height][width] == LIFE_YES) 
			{
                                printf("X");	// CELL is alive
                        } 
			else 
			{
                                printf(" ");	// CELL is dead
                        }
                }

		printf("\t|\t");

                for (width = 0; width < WIDTH; width++) 
		{

			if (hashtable[height][width] == 2)
			{
				printf(" ");	// CELL never existed
			}
			else
			{
                                printf("%d",hashtable[height][width]);	// CELL existed (even / odd)
			}
                }

                printf("\n");
        }

        printf("\n");



	// DISPLAY HASH VALUE and CALCULATE  BASE64 EQUIVALENT

	printf("BINARY HASH ============================================================================\n\n");

        for (height = 0; height < HEIGHT; height++) 
	{

                for (width = 0; width < WIDTH; width++) 
		{

			if (hashtable[height][width] == 2)
			{
				hashtable[height][width] = 0;
			}

			bitcounter++;		// increase the bitcounter

			printf("%d",hashtable[height][width]);

			if(bitcounter == 6)
			{
				printf(" ");
				blockcounter++;		// increase the blockcounter
				bitcounter = 0;		// the bitcounter resets on new blocks

				if(blockcounter == 12)
				{
					printf("\n");		// a new row will be printed
					blockcounter = 0;	// the blockcounter resets on new rows
				}
			}
                }
        }

	printf("\n\n");
}


void clearTables(TableType table, TableType hashtable) 
{
        int height, width;
        for (height = 0; height < HEIGHT; height++) 
	{
                for (width = 0; width < WIDTH; width++) 
		{
                        table[height][width] = LIFE_NO;
			hashtable[height][width] = 2;
                }
        }

}

void askUserIterations(int &maxgenerations) 
{
        printf("Enter the number of iterations to perform (max. 32767): ");
        scanf("%d", &maxgenerations);
}

int getNeighborValue(TableType table, int row, int col) 
{
        if (row < 0 || row >= HEIGHT || col < 0 || col >= WIDTH || table[row][col] != LIFE_YES )
        { 
                return 0;
        } 
	else
	{
                return 1;
        }
}


int getNeighborCount(TableType table, int row, int col) 
{
        int neighbor = 0;

	if((row == 0) && (col == 0))								// POSITION: TOP LEFT
	{
		neighbor += getNeighborValue(table, HEIGHT - 1, WIDTH - 1);

		neighbor += getNeighborValue(table, HEIGHT - 1, 0);
		neighbor += getNeighborValue(table, HEIGHT - 1, 1);
		neighbor += getNeighborValue(table, 0, WIDTH - 1);
		neighbor += getNeighborValue(table, 1, WIDTH - 1);

		neighbor += getNeighborValue(table, 0, 1);
		neighbor += getNeighborValue(table, 1, 0);
		neighbor += getNeighborValue(table, 1, 1);
	}
	else if((row == 0) && (col == WIDTH - 1))						// POSITION: TOP RIGHT
	{

		neighbor += getNeighborValue(table, HEIGHT - 1, 0);

		neighbor += getNeighborValue(table, HEIGHT - 1, WIDTH - 1);
		neighbor += getNeighborValue(table, HEIGHT - 1, WIDTH - 2);
		neighbor += getNeighborValue(table, 0, 0);
		neighbor += getNeighborValue(table, 1, 0);

		neighbor += getNeighborValue(table, row, col - 1);
		neighbor += getNeighborValue(table, row + 1, col);
		neighbor += getNeighborValue(table, row + 1, col - 1);

	}
	else if((row == HEIGHT - 1) && (col == WIDTH - 1))					// POSITION: BOTTOM RIGHT
	{

		neighbor += getNeighborValue(table, 0, 0);

		neighbor += getNeighborValue(table, 0, WIDTH - 1);
		neighbor += getNeighborValue(table, 0, WIDTH - 2);
		neighbor += getNeighborValue(table, HEIGHT - 1, 0);
		neighbor += getNeighborValue(table, HEIGHT - 2, 0);

		neighbor += getNeighborValue(table, HEIGHT - 1, WIDTH - 2);
		neighbor += getNeighborValue(table, HEIGHT - 2, WIDTH - 1);
		neighbor += getNeighborValue(table, HEIGHT - 2, WIDTH - 2);

	}
	else if((row == HEIGHT - 1) && (col == 0))						// POSITION: BOTTOM LEFT
	{

		neighbor += getNeighborValue(table, 0, WIDTH - 1);

		neighbor += getNeighborValue(table, 0, 0);
		neighbor += getNeighborValue(table, 0, 1);
		neighbor += getNeighborValue(table, HEIGHT - 1, WIDTH - 1);
		neighbor += getNeighborValue(table, HEIGHT - 2, WIDTH - 1);

		neighbor += getNeighborValue(table, HEIGHT - 1, 1);
		neighbor += getNeighborValue(table, HEIGHT - 2, 0);
		neighbor += getNeighborValue(table, HEIGHT - 2, 1);

	}
	else if(row == 0)									// POSITION: GENERAL TOP 
	{

		neighbor += getNeighborValue(table, HEIGHT - 1, col - 1);
		neighbor += getNeighborValue(table, HEIGHT - 1, col);
		neighbor += getNeighborValue(table, HEIGHT - 1, col + 1);
		neighbor += getNeighborValue(table, row, col - 1);
		neighbor += getNeighborValue(table, row, col + 1);
		neighbor += getNeighborValue(table, row + 1, col - 1);
		neighbor += getNeighborValue(table, row + 1, col);
		neighbor += getNeighborValue(table, row + 1, col + 1);

	}
	else if(col == WIDTH - 1)								// POSITION: GENERAL RIGHT
	{

		neighbor += getNeighborValue(table, row - 1, col - 1);
		neighbor += getNeighborValue(table, row - 1, col);
		neighbor += getNeighborValue(table, row - 1, 0);
		neighbor += getNeighborValue(table, row, col - 1);
		neighbor += getNeighborValue(table, row, 0);
		neighbor += getNeighborValue(table, row + 1, col - 1);
		neighbor += getNeighborValue(table, row + 1, col);
		neighbor += getNeighborValue(table, row + 1, 0);

	}
	else if(row == HEIGHT - 1)								// POSITION: GENERAL BOTTOM
	{

		neighbor += getNeighborValue(table, row - 1, col - 1);
		neighbor += getNeighborValue(table, row - 1, col);
		neighbor += getNeighborValue(table, row - 1, col + 1);
		neighbor += getNeighborValue(table, row, col - 1);
		neighbor += getNeighborValue(table, row, col + 1);
		neighbor += getNeighborValue(table, 0, col - 1);
		neighbor += getNeighborValue(table, 0, col);
		neighbor += getNeighborValue(table, 0, col + 1);

	}
	else if(col == 0)									// POSITION: GENERAL LEFT
	{

		neighbor += getNeighborValue(table, row - 1, WIDTH - 1);
		neighbor += getNeighborValue(table, row - 1, col);
		neighbor += getNeighborValue(table, row - 1, col + 1);
		neighbor += getNeighborValue(table, row, WIDTH - 1);
		neighbor += getNeighborValue(table, row, col + 1);
		neighbor += getNeighborValue(table, row + 1, WIDTH - 1);
		neighbor += getNeighborValue(table, row + 1, col);
		neighbor += getNeighborValue(table, row + 1, col + 1);

	}
	else											// POSITION: IN BETWEEN
	{
		neighbor += getNeighborValue(table, row - 1, col - 1);
		neighbor += getNeighborValue(table, row - 1, col);
		neighbor += getNeighborValue(table, row - 1, col + 1);
		neighbor += getNeighborValue(table, row, col - 1);
		neighbor += getNeighborValue(table, row, col + 1);
		neighbor += getNeighborValue(table, row + 1, col - 1);
		neighbor += getNeighborValue(table, row + 1, col);
		neighbor += getNeighborValue(table, row + 1, col + 1);
	}

        return neighbor;
}

void calculate(TableType tableA, TableType hashtable)
{
        TableType tableB;
        int neighbor, height, width;

        for (height = 0; height < HEIGHT; height++)
	{
                for (width = 0; width < WIDTH; width++)
		{
                        neighbor = getNeighborCount(tableA, height, width);
                        // change this around to remove the ? : notation
                        if (neighbor==3) 
			{

				if(tableA[height][width] == LIFE_NO)
				{
					if(hashtable[height][width] == 0)
					{
						hashtable[height][width] = 1;
					}
					else
					{
						hashtable[height][width] = 0;
					}

					/*if(hashtable[height][width] <= 8)
					{
						hashtable[height][width]++;
					}*/
						
				}

                                tableB[height][width] = LIFE_YES;
                        } 
			else if (neighbor == 2 && tableA[height][width] == LIFE_YES) 
			{
                                tableB[height][width] = LIFE_YES;
                        }
			else
			{
                                tableB[height][width] = LIFE_NO;
                        }
                }
        }

        // Thats it - now we swap tableA with tableB...

        for (height = 0; height < HEIGHT; height++)
	{
                for (width = 0; width < WIDTH; width++)
		{
                        tableA[height][width] = tableB[height][width];
                }
        }
}

// user entry is a pain for testing
// here's some code to load test data
// in our case - the simple word "password" encoded in binary

void loadTestData(TableType table)
{

	// The word "password" as binary

	table[10][10] = LIFE_NO;
	table[10][11] = LIFE_YES;
	table[10][12] = LIFE_YES;
	table[10][13] = LIFE_NO;
	table[10][14] = LIFE_YES;
	table[10][15] = LIFE_NO;
	table[10][16] = LIFE_NO;
	table[10][17] = LIFE_NO;
	table[10][18] = LIFE_YES;
	table[10][19] = LIFE_NO;
	table[10][20] = LIFE_YES;
	table[10][21] = LIFE_NO;

	table[11][10] = LIFE_NO;
	table[11][11] = LIFE_YES;
	table[11][12] = LIFE_YES;
	table[11][13] = LIFE_YES;
	table[11][14] = LIFE_NO;
	table[11][15] = LIFE_YES;
	table[11][16] = LIFE_NO;
	table[11][17] = LIFE_YES;
	table[11][18] = LIFE_YES;
	table[11][19] = LIFE_YES;
	table[11][20] = LIFE_NO;
	table[11][21] = LIFE_YES;

	table[12][10] = LIFE_YES;
	table[12][11] = LIFE_NO;
	table[12][12] = LIFE_NO;
	table[12][13] = LIFE_NO;
	table[12][14] = LIFE_NO;
	table[12][15] = LIFE_YES;
	table[12][16] = LIFE_NO;
	table[12][17] = LIFE_YES;
	table[12][18] = LIFE_YES;
	table[12][19] = LIFE_NO;
	table[12][20] = LIFE_NO;
	table[12][21] = LIFE_YES;

	table[13][10] = LIFE_NO;
	table[13][11] = LIFE_YES;
	table[13][12] = LIFE_YES;
	table[13][13] = LIFE_YES;
	table[13][14] = LIFE_NO;
	table[13][15] = LIFE_YES;
	table[13][16] = LIFE_YES;
	table[13][17] = LIFE_YES;
	table[13][18] = LIFE_YES;
	table[13][19] = LIFE_YES;
	table[13][20] = LIFE_YES;
	table[13][21] = LIFE_NO;


}

int main(void)
{
        TableType table;		// oh my god, its a table
	TableType hashtable;		// oh my god, its a hashtable

        char end;			// switch for breaking out the while statement
        int generation = 0;		// generation counter
	int maxgenerations;		// max. generations

        clearTables(table,hashtable);

        askUserIterations(maxgenerations);	// let the user define the number of iterations

	cout << endl;

        loadTestData(table);			// get data for test environment

        do
	{


               	calculate(table,hashtable);

                //printTable(table,hashtable);			// print every generation for debugging purposes

		generation++;

		if((generation == maxgenerations) || (generation == 32767))		// halt after(!) last generation
		{
               		printf("--- PERMUTED TABLE (Generation %d) ---|--- PERMUTATION FIELD USAGE (Fingerprint) ---\n", generation);
                	printTable(table,hashtable);
			end = true;
		}
	
        } while (end != true) ;

	cout << endl;

        return 0;
}
