/*
 * BMPautomata.c
 * Marco Vitanza
 * 04/11/2009
 *
 * Public domain
 * (do whatever you want with it)
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// parameters for image and automata
struct params {
	unsigned int rule, randInitial;
	unsigned int imgw, imgh;
	int fgR, fgG, fgB;
	int bgR, bgG, bgB;
} p;

// BMP file header (4-byte values are little-endian)
unsigned char buf[54] = {
	0x42, 0x4D, 
	0x00, 0x00, 0x00, 0x00,		// file size: 54 + imgw*imgh*3
	0x00, 0x00, 0x00, 0x00,
	0x36, 0x00, 0x00, 0x00,
	0x28, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,		// imgw
	0x00, 0x00, 0x00, 0x00,		// imgh
	0x01, 0x00,
	0x18, 0x00,
	0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,		// imgw*imgh*3
	0x13, 0x0B, 0x00, 0x00,
	0x13, 0x0B, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00
};

// stderr stream (set in main)
FILE *err;


/*
 * compute cells row by row, then write BMP to stdout
 */
void runRule()
{
	unsigned char cells[p.imgh][p.imgw];
	int r, i;
	
	// fill first row with single ON cell
	if (p.randInitial == 0) {
		for (i = 0; i < p.imgw; i++)
			cells[0][i] = 0;
		cells[0][p.imgw/2] = 1;
	}
	// or fill first row with random cells
	else {
		for (i = 0; i < p.imgw; i++)
			cells[0][i] = random() & 0x1;
	}
	
	// compute row by row using wrap-around
	unsigned char uppers;
	for (r = 1; r < p.imgh; r++) {
		for (i = 0; i < p.imgw; i++) {
			// middle upper neighbor
			uppers = cells[r-1][i] << 1;
			// left upper neighbor
			if ( (i == 0 && cells[r-1][p.imgw-1]) || (i != 0 && cells[r-1][i-1]) )
				uppers |= 0x4;
			// right upper neighbor
			if ( (i == p.imgw-1 && cells[r-1][0]) || (i != p.imgw-1 && cells[r-1][i+1]) )
				uppers |= 0x1;
			// compute next cell value
			cells[r][i] = (p.rule >> uppers) & 0x1;
		}
	}

	/********** OUTPUT BMP FILE **********/
	
	// set image width field
	buf[18] = (p.imgw >>  0) & 0xFF;
	buf[19] = (p.imgw >>  8) & 0xFF;
	buf[20] = (p.imgw >> 16) & 0xFF;
	buf[21] = (p.imgw >> 24) & 0xFF;
	// set image height field
	buf[22] = (p.imgh >>  0) & 0xFF;
	buf[23] = (p.imgh >>  8) & 0xFF;
	buf[24] = (p.imgh >> 16) & 0xFF;
	buf[25] = (p.imgh >> 24) & 0xFF;
	// set raw bitmap size field
	i = p.imgw * p.imgh * 3;
	buf[34] = (i >>  0) & 0xFF;
	buf[35] = (i >>  8) & 0xFF;
	buf[36] = (i >> 16) & 0xFF;
	buf[37] = (i >> 24) & 0xFF;
	// set file size field
	i += 54;
	buf[2] = (i >>  0) & 0xFF;
	buf[3] = (i >>  8) & 0xFF;
	buf[4] = (i >> 16) & 0xFF;
	buf[5] = (i >> 24) & 0xFF;
	// write bitmap header
	FILE *fp = fdopen(1, "w");
	if (fp == NULL) {
		fprintf(err, "Could not write to stdout.\n");
		exit(-1);
	}
	fwrite(buf, 1, 54, fp);

	// write raw bitmap data (rows in reverse order)
	for (r = p.imgh-1; r >= 0; r--) {
		for (i = 0; i < p.imgw; i++) {
			if (cells[r][i]) {
				fputc(p.fgB, fp);
				fputc(p.fgG, fp);
				fputc(p.fgR, fp);
			}
			else {
				fputc(p.bgB, fp);
				fputc(p.bgG, fp);
				fputc(p.bgR, fp);
			}
		}
		// pad rows to be multiple of 4-bytes
		int bytesPerRow = p.imgw * 3;
		while (bytesPerRow % 4 != 0) {
			fputc(0, fp);
			bytesPerRow++;
		}
	}
	fclose(fp);
}


/*
 * simple string to int converter
 */
unsigned int strtoi(char *s)
{
	unsigned int ret = 0;
	while (*s) {
		if (*s < '0' || *s > '9')
			break;
		ret = ret * 10 + (*s - '0');
		s++;
	}
	return ret;
}


/*
 * simple hex char to int converter
 */
int hexchartoi(char *s)
{
	int c;
	if (*s >= '0' && *s <= '9')
		c = *s - '0';
	else if (*s >= 'A' && *s <= 'F')
		c = *s - 'A' + 10;
	else if (*s >= 'a' && *s <= 'f')
		c = *s - 'a' + 10;
	else {
		fprintf(err, "Invalid hex color code.\n");
		exit(-1);
	}
	return c;
}


/*
 * main() - parse args then run automata
 */
int main(int argc, char** argv)
{
	// check # of args
	err = fdopen(2, "w");
	if (argc < 4 || argc > 7) {
		fprintf(err, "Usage: BMPautomata [-r] rule imgW imgH [bgColor fgColor]\n");
		return -1;
	}
	
	// init temps and parameters
	unsigned int tmp, a = 1;
	memset(&p, 0, sizeof(p));
	p.bgR = p.bgG = p.bgB = 255;
	
	// check for random flag
	if (strcmp(argv[a],"-r") == 0) {
		p.randInitial = 1;
		a++;
	}
	// parse rule number
	tmp = strtoi(argv[a]);
	if (tmp < 256) {
		p.rule = tmp;
		a++;
	}
	else {
		fprintf(err, "Rule must be 0-255.\n");
		return -1;
	}
	// parse image width
	tmp = strtoi(argv[a]);
	if (tmp > 0) {
		p.imgw = tmp;
		a++;
	}
	else {
		fprintf(err, "Image width must be > 0.\n");
		return -1;
	}
	// parse image height
	tmp = strtoi(argv[a]);
	if (tmp > 0) {
		p.imgh = tmp;
		a++;
	}
	else {
		fprintf(err, "Image height must be > 0.\n");
		return -1;
	}
	// parse bg and fg colors, if specified
	if (a < argc) {
		if (argc - a != 2) {
			fprintf(err, "You must specific both FG and BG colors (or neither).\n");
			return -1;
		}
		if (strlen(argv[a]) != 6 || strlen(argv[a+1]) != 6) {
				fprintf(err, "Colors must be specified as 6-char hex: RRGGBB.\n");
				return -1;
		}
		p.fgR = hexchartoi(argv[a]  ) * 16 + hexchartoi(argv[a]+1);
		p.fgG = hexchartoi(argv[a]+2) * 16 + hexchartoi(argv[a]+3);
		p.fgB = hexchartoi(argv[a]+4) * 16 + hexchartoi(argv[a]+5);
		a++;
		p.bgR = hexchartoi(argv[a]  ) * 16 + hexchartoi(argv[a]+1);
		p.bgG = hexchartoi(argv[a]+2) * 16 + hexchartoi(argv[a]+3);
		p.bgB = hexchartoi(argv[a]+4) * 16 + hexchartoi(argv[a]+5);
	}
	
	// run automata and output BMP file
	runRule();
	
	fclose(err);
	return 0;
}
