package mior.model;

import java.util.Arrays;

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;
import org.perf4j.slf4j.Slf4JStopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @deprecated use {@link OCLParaCSRModel2} or {@link OCLParaCSRModel3} classes instead.
 */
public class OCLParaCSRModel extends AbstractMiorModel {
	
	/**
	 * Java view
	 */
	
	private final int nbMM;
	private final int nbOM;
	
	private final MiorMM [] mmList;
	private final MiorOM [] omList;
	private final MiorWorld [] worlds;
	private final int [] associations;
	
	/**
	 * OpenCL implementation
	 */
	final MCMContext context;
	final MCMCommandQueue queue;
	final MCMProgram program;
	
	final MCMKernel topoKernel;
	final MCMKernel autoliveKernel;
	
	final MCMMem mmMem;
	final MCMMem omMem;
	final MCMMem worldsMem;
	
	final MCMMem mmCSRMem;
	final MCMMem omCSRMem;
	final MCMMem associationsMem;
	
	final MCMMem partsMem;
	
	private final int [] mmCSR;
	private final int [] omCSR;
	
	private final int nbSim;
	
	private final static Logger logger = LoggerFactory.getLogger(OCLParaCSRModel.class);
	private final Slf4JStopWatch watch = new Slf4JStopWatch(logger);
	
	private final static String MODEL_SOURCE = "kernels/mior_model_multisim.cl";
	
	public OCLParaCSRModel(int nbMM, int nbOM, int nbSim) {
		logger.info("Launching a batch of " + nbSim + " simulations (size = " + nbMM + " MM x " + nbOM + " OM)");
		this.nbSim = nbSim;
		this.nbMM = nbMM;
		this.nbOM = nbOM;
		
		// Allocate Java MIOR structures
		this.mmList = new MiorMM[nbSim * nbMM];
		this.omList = new MiorOM[nbSim * nbOM];
		this.worlds = new MiorWorld[nbSim];
		this.associations = new int[nbSim * nbMM * nbOM];
		//final Random rand = new Random(); TODO: Remove
		
		// Initialise models
		for (int i = 0; i < nbSim; i++) {
			this.worlds[i] = new MiorWorld(nbMM, nbOM);
		}
		
		MiorUtils.initMMArray(worlds[0], mmList);
		MiorUtils.initOMArray(worlds[0], omList);
		
		/*
		
		for (int i = 0; i < mmList.length; i++) {
			mmList[i] = new MiorMM(
					rand.nextInt(worlds[0].width),
					rand.nextInt(worlds[0].width)
			);
		}
		
		for (int i = 0; i < omList.length; i++) {
			omList[i] = new MiorOM(
					rand.nextInt(worlds[0].width),
					rand.nextInt(worlds[0].width)
			);
		}*/
		
		Arrays.fill(associations, -1);
		
		// OpenCL allocations
		watch.start("OpenCL setup");
		
		this.context = new MCMContext();
		this.queue = context.createCommandQueue(MCMCommandQueueProperty.ENABLE_PROFILING);
		
		watch.stop();
		
		watch.start("Program compilation");
		this.program = MCMUtils.compileFile(context, MODEL_SOURCE);
		watch.stop();
		
		watch.start("Kernel allocation");
		
		this.topoKernel = program.createKernel("topology");
		this.autoliveKernel = program.createKernel("autolive");
		
		watch.stop();
		watch.start("Memory allocation");
		
		this.mmMem = context.newBuffer().Using(mmList).b();
		this.omMem = context.newBuffer().Using(omList).b();
		this.worldsMem = context.newBuffer().Using(worlds).b();
		
		this.mmCSR = allocateStorage(nbOM * nbSim, nbMM);
		this.mmCSRMem = context.newBuffer().Using(mmCSR).b();
		
		this.omCSR = allocateStorage(nbMM * nbSim, nbOM);
		this.omCSRMem = context.newBuffer().Using(omCSR).b();
		
		this.associationsMem = context.newBuffer().Using(associations).b();
		
		this.partsMem = context.newBuffer().Size(associations.length, MCM.INT).b();
		watch.stop();
	}
	
	@Override
	public int getNbSimulations() {
		return nbSim;
	}
	
	@Override
	protected void resetImpl() {
		Arrays.fill(mmCSR, 1, mmCSR.length, 0);
		Arrays.fill(omCSR, 1, omCSR.length, 0);
		Arrays.fill(associations, -1);
		
		queue.enqueueWriteBuffer(mmMem, mmList, 0, mmMem.getSize());
		queue.enqueueWriteBuffer(omMem, omList, 0, omMem.getSize());
		queue.enqueueWriteBuffer(worldsMem, worlds, 0, worldsMem.getSize());
		queue.enqueueWriteBuffer(mmCSRMem, Pointer.to(mmCSR), 0, mmCSRMem.getSize());
		queue.enqueueWriteBuffer(omCSRMem, Pointer.to(omCSR), 0, omCSRMem.getSize());
		queue.enqueueWriteBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		queue.finish();
	}
	
	@Override
	protected void doTopologyImpl() {
		// System.out.println(topoKernel.getArgumentsNumber());
		topoKernel.setArguments(mmMem, omMem, worldsMem, mmCSRMem, omCSRMem, associationsMem);
		
		MCMEvent event = queue.enqueueKernel(topoKernel, 2,
				new long[] { nbSim * nbMM , nbOM}
		);
		
		MCMEvent.waitFor(event);
		MCMUtils.printEventStats("topology", event);
		
		if (! isBatchModeEnabled()) {
			queue.blockingReadBuffer(mmCSRMem, Pointer.to(mmCSR), 0, mmCSRMem.getSize());
			queue.blockingReadBuffer(omCSRMem, Pointer.to(omCSR), 0, omCSRMem.getSize());
			queue.blockingReadBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		}
	}
	
	@Override
	protected void doLiveImpl() {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void doAutoLive() {
		resetImpl();
		doTopologyImpl();
		
		autoliveKernel.setArguments(mmMem, omMem, worldsMem, mmCSRMem, omCSRMem, partsMem);
		
		MCMEvent event = queue.enqueueKernel(autoliveKernel, 2,
				new long[] { nbSim, Math.max(nbOM, nbMM) }
		);
		
		
		MCMEvent.waitFor(event);
		MCMUtils.printEventStats("autolive", event);
		
		onSimulationFinished();
	}
	
	@Override
	protected void onSimulationFinished() {
		if (isBatchModeEnabled()) {
			queue.blockingReadBuffer(mmMem, mmList, 0, mmMem.getSize());
			queue.blockingReadBuffer(omMem, omList, 0, omMem.getSize());
			queue.blockingReadBuffer(worldsMem, worlds, 0, worldsMem.getSize());
			queue.blockingReadBuffer(associationsMem, Pointer.to(associations), 0, associationsMem.getSize());
		}
		
		super.onSimulationFinished();
	}
	
	@Override
	protected void releaseImpl() {
		partsMem.release();
		
		associationsMem.release();
		omCSRMem.release();
		mmCSRMem.release();
		
		worldsMem.release();
		omMem.release();
		mmMem.release();
		
		autoliveKernel.release();
		topoKernel.release();
		
		program.release();
		queue.release();
		context.release();
	}
	
	private int[] allocateStorage(int size, int capacity) {
		int [] result = new int[1 + size * (capacity + 1)];
		result[0] = capacity;
		return result;
	}

	@Override
	public MiorWorld getWorld() {
		return worlds[0];
	}

	@Override
	public MiorOM[] getOMList() {
		return Arrays.copyOfRange(omList, 0, nbOM);
	}

	@Override
	public MiorMM[] getMMList() {
		return Arrays.copyOfRange(mmList, 0, nbMM);
	}

	@Override
	public boolean isAccessible(int iMM, int iOM) {
		return associations[iMM * nbOM + iOM] != -1;
	}

	@Override
	public boolean isAccessible(int iMM, int iOM, int iSim) {
		return associations[iSim * nbMM * nbOM + iMM * nbOM + iOM] != -1;
	}
	
}
