package mcmas.core.examples;
import mcmas.core.MCM;
import mcmas.core.MCMCommandQueue;
import mcmas.core.MCMContext;
import mcmas.core.MCMEvent;
import mcmas.core.MCMKernel;
import mcmas.core.MCMMem;
import mcmas.core.MCMProgram;

import org.jocl.Pointer;


public class MCMLife {
	
	public static final String source = 
		  "__kernel void life(                                                     \n"
		+ "   constant int* input,                                                 \n"
		+ "   global   int* output,                                                \n"
		+ "   const unsigned int height,                                           \n"
		+ "   const unsigned int width)                                            \n"
		+ "{                                                                       \n"
		+ "   int i = get_global_id(0);                                            \n"
		+ "   int rowUp = i - width;                                               \n"
		+ "   int rowDown = i + width;                                             \n"
		+ "   bool outOfBounds = (i < width);                                      \n"
		+ "   outOfBounds |= (i > (width * (height-1)));                           \n"
		+ "   outOfBounds |= (i % width == 0);                                     \n"
		+ "   outOfBounds |= (i % width == width-1);                               \n"
		+ "   if (outOfBounds) { output[i] = 0; return; }                          \n"
		+ "   int neighbours = input[rowUp-1] + input[rowUp] + input[rowUp+1];     \n"
		+ "   neighbours += input[i-1] + input[i+1];                               \n"
		+ "   neighbours += input[rowDown-1] + input[rowDown] + input[rowDown+1];  \n"
		+ "   if (neighbours == 3 || (input[i] && neighbours == 2))                \n"
		+ "       output[i] = 1;                                                   \n"
		+ "   else                                                                 \n"
		+ "       output[i] = 0;                                                   \n"
		+ "}                                                                       \n";
	
	private int   height;
	private int   width;
	private int[] board;
	
	private MCMContext 		context;
	private MCMCommandQueue	queue;
	private MCMKernel  		kernel;
	private MCMMem  		dInput;
	private MCMMem  		dOutput;
	
	public MCMLife(int height, int width) {
		this.height = height;
		this.width = width;
		
		this.board = new int[width * height];
		for (int i = 0; i < board.length; i++) {
			board[i] = (Math.random() > 0.5 ? 1 : 0); 
		}
		
		this.context = new MCMContext();
		this.queue = context.createCommandQueue();
		
		this.dInput = context.newBuffer().Using(board).b();
		//context.createBuffer(board.length, OCL.INT, OCL.MEM_RO, Pointer.to(board));
		this.dOutput = context.newBuffer().Size(board.length, MCM.INT).b();
		//context.createBuffer(board.length, OCL.INT, OCL.MEM_RW);
		
		MCMProgram program = context.createProgram(source);
		this.kernel = program.createKernel("life");
		kernel.setArguments(dInput, dOutput, height, width);
	}
	
	public void run(int iterations) {
		MCMEvent event;
		
		for (int i = 0; i < iterations; i++) {
			event = queue.enqueue1DKernel(kernel, board.length, 1);
			MCMEvent.waitFor(event);
			
			queue.enqueueCopyBuffer(dOutput, dInput, 0, 0, dInput.getSize());
		}
		
		queue.blockingReadBuffer(dOutput, Pointer.to(board), 0, dOutput.getSize());
	}
	
	public void print() {
		for (int x = 0; x < height; x++) {
			for (int y = 0; y < width; y++) {
				System.out.print(board[x * height + y] == 0 ? " " : "x");
			}
			System.out.println();
		}
		System.out.println("---------------------------");
	}
	
	public static void main(String[] args) {
		MCMLife game = new MCMLife(10, 20);
		for (int i = 0; i < 20; i++) {
			game.print();
			game.run(1);
		}
	}

}
