package mior.model;

import java.util.Random;

import mcmas.core.MCM;
import mcmas.core.MCMCommandQueue;
import mcmas.core.MCMCommandQueueProperty;
import mcmas.core.MCMContext;
import mcmas.core.MCMEvent;
import mcmas.core.MCMKernel;
import mcmas.core.MCMMem;
import mcmas.core.MCMProgram;
import mcmas.core.MCMUtils;

import org.jocl.Pointer;


public class OCLMiorModel extends SimpleMiorModel {
	
	private MCMContext 		context;
	private MCMProgram 		program;
	private MCMCommandQueue queue;
	
	private MCMKernel		topoKernel;
	private MCMKernel		liveKernel;
	
	private MCMMem     		mmMem;
	private MCMMem     		omMem;
	private MCMMem     		associationsMem;
	private MCMMem     		worldMem;
	
	private MCMMem          omIndexesMem;
	private final Random    rand;
	
	private final static String MODEL_SOURCE = "kernels/mior_model.cl";
	
	public OCLMiorModel(MiorWorld world) {
		super(world);
		this.rand = new Random();
		
		this.context = new MCMContext();
		this.queue = context.createCommandQueue(MCMCommandQueueProperty.ENABLE_PROFILING);
		this.program = MCMUtils.compileFile(context, MODEL_SOURCE);
		
		this.topoKernel = program.createKernel("topology");
		this.liveKernel = program.createKernel("live");
		
		this.mmMem = context.newBuffer().Using(mmList).b();
		this.omMem = context.newBuffer().Using(omList).b();
		this.associationsMem = context.newBuffer().Size(associations.length, MCM.INT).b();
		this.worldMem = context.newBuffer().Using(world).b();
		
		this.omIndexesMem = context.newBuffer().Size(omList.length * mmList.length, MCM.INT).b();
	}
	
	
	@Override
	protected void resetImpl() {
		super.resetImpl();
		queue.enqueueWriteBuffer(mmMem, mmList, 0, mmMem.getSize());
		queue.enqueueWriteBuffer(omMem, omList, 0, omMem.getSize());
		queue.enqueueWriteBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		queue.enqueueWriteBuffer(worldMem, new MiorWorld[] { world }, 0, worldMem.getSize());
	}
	
	@Override
	public void doTopologyImpl() {
		topoKernel.setArguments(mmMem, omMem, associationsMem, worldMem);
		
		/*
		OCLEvent event = queue.enqueueKernel(
				topoKernel, 2,
				new long[] { 0, 0 },
				new long[] { mmList.length, omList.length },
				new long[] { 1, 1 }
		);*/
		
		MCMEvent event = queue.enqueueKernel(topoKernel, 2, new long[] { nbMM, nbOM });
		
		MCMEvent.waitFor(event);
		MCMUtils.printEventStats("topology", event);
		
		if (! isBatchModeEnabled()) {
			queue.blockingReadBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		}
	}
	
	@Override
	public void doLiveImpl() {
		if (isRandomEnabled()) {
			liveKernel.setArguments(mmMem, omMem, associationsMem, worldMem, omIndexesMem, rand.nextLong());
		} else {
			liveKernel.setArguments(mmMem, omMem, associationsMem, worldMem, omIndexesMem, 0L);
		}
		
		MCMEvent event = queue.enqueue1DKernel(liveKernel, nbMM);
		MCMEvent.waitFor(event);
		
		MCMUtils.printEventStats("live", event);
		
		if (! isBatchModeEnabled()) {
			queue.blockingReadBuffer(mmMem, mmList, 0, mmMem.getSize());
			queue.blockingReadBuffer(omMem, omList, 0, omMem.getSize());
			queue.blockingReadBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		}
		
		queue.blockingReadBuffer(worldMem, new MiorWorld[] {world}, 0, worldMem.getSize());
	}
	
	@Override
	protected void onSimulationFinished() {
		if (isBatchModeEnabled()) {
			queue.blockingReadBuffer(mmMem, mmList, 0, mmMem.getSize());
			queue.blockingReadBuffer(omMem, omList, 0, omMem.getSize());
			queue.blockingReadBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		}
		
		super.onSimulationFinished();
	}
	
	@Override
	protected void releaseImpl() {
		associationsMem.release();
		worldMem.release();
		mmMem.release();
		omMem.release();
		
		liveKernel.release();
		topoKernel.release();
		program.release();
		queue.release();
		context.release();
	}
	
}
