/*-
 * Copyright (c) 2004 Matthias Schmidt <schmidtm@mathematik.uni-marburg.de>
 * 			and Chris merck <cmerck@stevens.edu>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Simple Brainfuck Interpreter -- originally written @ 21c3 Berlin 2004
 * 	with graphics support	-- added by Chris Merck @ SIT 2007
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <SDL/SDL.h>

#include <math.h>

#define XMAX 64
#define YMAX 48
#define ZOOM 10

//#define TOWER_MODE

int
main(int argc, char **argv)
{	
					
	/* Declare Variables */
	SDL_Surface 	*screen;
	SDL_Event 	event;
	SDL_Rect  	rect;
	
	rect.w = ZOOM;
	rect.h = ZOOM;
	
	int	memory[32768],
		space[32768];
	int	//fd,
		len,
		buf,
		spointer,
		kl = 0,
		mpointer,
		frame=0;
	FILE	* fd;
	int	pr[256],
		pg[256],
		pb[256];

	if (argc <= 1) {
		printf("Usage: %s <brainfuck file>\n", argv[0]);
		exit(1);
	}

	/* init palette */
	for (len = 0; len < 256; len++ ) {
		pr[len] = len * 1; 
		pg[len] = (sin(len*3.14/256)) * 512; 
		pb[len] = (1 - cos(2*len*3.14/256)) * 128;
		if (pr[len] > 255) pr[len] = 255;
		if (pg[len] > 255) pg[len] = 255;
		if (pb[len] > 255) pb[len] = 255;
#ifdef 	TOWER_MODE
		pr[len] = pb[len] = pg[len] = 255;
#endif
	}
	pr[0] = 0; pg[0] = 0; pb[0] = 0; //255;

 /* Initialize SDL, exit if there is an error. */
  if( SDL_Init(SDL_INIT_VIDEO) ) {
    fprintf(stderr, "Could not initialize SDL: %s\n", 
	    SDL_GetError());
    return -1;
  }
  
  /* When the program is through executing, call SDL_Quit */
  atexit(SDL_Quit);
  
  /* Grab a surface on the screen */
  screen = SDL_SetVideoMode(XMAX*ZOOM, YMAX*ZOOM, 32, SDL_SWSURFACE|SDL_ANYFORMAT);
  if( !screen ) {
    fprintf(stderr, "Couldn't create a surface: %s\n",
	    SDL_GetError());
    return -1;
  }

  	/* Clear screen */
 	SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format,pr[0],pg[0],pb[0]));
	SDL_Flip(screen);

	/* Open brainfuck file */	
	//if ((fd = open(argv[1], O_RDONLY)) < 0)
	//	exit(1);
	if ((fd = fopen(argv[1],"r"))==NULL) exit(1);

	/* Read the file byte by byte */
	len = spointer = 1;
	while (len > 0) {
		//len = read(fd, &buf, 1);
		buf = fgetc(fd);
		if (buf == EOF) break;
		space[spointer] = buf;
		spointer++;
	}

	//close(fd);
	fclose(fd);

	for (mpointer = 0; mpointer < 32768; mpointer++)
		memory[mpointer] = 0;

	
	len = spointer;	
	
	spointer = mpointer = 0;
	
	for (spointer = 0; spointer < len; spointer++) {
		switch(space[spointer]) {
		/* Increment pointer value */
		case '+':
			memory[mpointer]+=2;
		/* Decrement pointer value */
		case '-':
			memory[mpointer]--;
			if (memory[mpointer] < 0) memory[mpointer] = 255;
			else if (memory[mpointer] > 255) memory[mpointer] = 0;
			/* Update cell on screen */
			rect.x = ZOOM * (mpointer % XMAX);
			rect.y = ZOOM * (mpointer / XMAX);
#ifndef TOWER_MODE
			if (rect.y < YMAX*ZOOM) {
				SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format,
					pr[memory[mpointer]],pg[memory[mpointer]],pb[memory[mpointer]]));
				if (frame++ > 20) {
					SDL_Flip(screen);
					frame = 0;
				}
			}
#else
			if (rect.y == 0) { //we only have one row in towermode
				rect.h = YMAX * ZOOM * memory[mpointer] / 256;
				SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format,
					pr[memory[mpointer]],pg[memory[mpointer]],pb[memory[mpointer]]));
				rect.y = rect.h;
			       	rect.h = YMAX * ZOOM - rect.h;	
				SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format,
					pr[0],pg[0],pb[0]));

				if (frame++ > 100) {
					SDL_Flip(screen);
					frame = 0;
				}
			}
#endif	
			break;
		/* Increment pointer */
		case '>':
			mpointer++;
			break;
		/* Decrement pointer */
		case '<':
			mpointer--;
			break;
		/* Print current pointer value */
		case '.':
			putchar(memory[mpointer]); fflush(stdout);
			break;
		/* Read value and store in current pointer */
		case ',':
			memory[mpointer] = getchar();
			break;
		/* Start loop */
		case '[':
			if (memory[mpointer] == 0) {
				/* Find matching ] */
				spointer++;
				/* If kl == 0 and space[pointer] == ']' we found
				 * the matching brace */
				while (kl > 0 || space[spointer] != ']') {
					if (space[spointer] == '[') kl++;
					if (space[spointer] == ']') kl--;
					/* Go in right direction */
					spointer++;
				}
			}
			break;
		/* End loop */
		case ']':
			if (memory[mpointer] != 0) {
				/* Find matching [ */
				spointer--;
				while (kl > 0 || space[spointer] != '[') {
					if (space[spointer] == '[') kl--;
					if (space[spointer] == ']') kl++;
					/* Go left */
					spointer--;
				}
				spointer--;
			}
			break;
		}
	}

	putchar('\n');

	printf(" -- press return to continue --\n");
	getchar();
	
	return (0);
}

