package mcmas.plugins.gridsearch;

import java.util.Arrays;

import org.jocl.Pointer;
import org.perf4j.StopWatch;
import org.perf4j.slf4j.Slf4JStopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import mcmas.api.MCMASContext;
import mcmas.api.MCMASPlugin;
//import mcmas.core.MCMChrono;
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 mcmas.core.MCMProgramOptions;
import mcmas.core.MCMUtils;
import mcmas.utils.IntMatrix;

public class GridSearchPlugin extends MCMASPlugin<GridSearchPlugin> {
	
	public static final String KERNEL_SOURCE = "kernels/plugins/grid_search2.cl";
	public static final String KERNEL_MAX = "grid_search_max";
	
	private MCMKernel kernel = null;
	
	private final static Logger logger = LoggerFactory.getLogger(GridSearchPlugin.class);
	private final StopWatch watch = new Slf4JStopWatch().setNormalPriority(Slf4JStopWatch.DEBUG_LEVEL);
	private final StopWatch runWatch = new Slf4JStopWatch().setNormalPriority(Slf4JStopWatch.TRACE_LEVEL);
	
	@Override
	public GridSearchPlugin newInstance(MCMASContext context) {
		return new GridSearchPlugin(context);
	}
	
	public GridSearchPlugin(MCMASContext context) {
		super(context);
	}
	
	public void findMaxCell(IntMatrix grid, int radius, int[] xPositions, int[] yPositions, int[] xResults, int[] yResults) {
		watch.start("gridsearch_findmax");
		MCMContext c = getContext().getContext();
		MCMCommandQueue q = getContext().getQueue();
		
		//OCLProgram program = null;
		//OCLKernel kernel = null;
		MCMMem gridMem = null;
		MCMMem xPositionsMem = null;
		MCMMem yPositionsMem = null;
		MCMMem xResultsMem = null;
		MCMMem yResultsMem = null;
		
		MCMProgramOptions options = new MCMProgramOptions();
		options.define("WIDTH", grid.getWidth());
		options.define("HEIGHT", grid.getHeight());
		options.define("TYPE", "int");
		
		//System.out.println(options);
		
		MCMProgram program = null;
		//OCLKernel kernel = null;
		MCMEvent finished = null;
		
		try {
			runWatch.start("gridsearch_build");
			if (kernel == null) {
				program = c.createProgramFromFile(KERNEL_SOURCE, options);
				kernel = program.createKernel(KERNEL_MAX);
			}
			runWatch.stop();
			
			runWatch.start("gridsearch_kernel");
			
			gridMem = c.newBuffer().Using(grid.getStorage()).b();
			xPositionsMem = c.newBuffer().Using(xPositions).b();
			yPositionsMem = c.newBuffer().Using(yPositions).b();
			xResultsMem = c.newBuffer().Size(xPositionsMem.getSize()).b();
			yResultsMem = c.newBuffer().Size(yPositionsMem.getSize()).b();
			
			kernel.setArguments(gridMem, radius, xPositionsMem, yPositionsMem, xResultsMem, yResultsMem);
			
			finished = q.enqueue1DKernel(kernel, xPositions.length);
			
			q.blockingReadBuffer(xResultsMem, Pointer.to(xResults), 0, xResultsMem.getSize(), finished);
			q.blockingReadBuffer(yResultsMem, Pointer.to(yResults), 0, yResultsMem.getSize(), finished);
			
			runWatch.stop();
			
			logger.debug(MCMUtils.formatEventStats("gridsearch_opencl", finished));
			
			watch.stop();
		} catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
		} finally {
			gridMem.release();
			xPositionsMem.release();
			yPositionsMem.release();
			xResultsMem.release();
			yResultsMem.release();
			finished.release();
			//kernel.release();
			if (program != null) {
				program.release();
			}
		}
	}
	
	/*
	public void findNearestCellNot(float[] vector, int radius, float value, int[] xPositions, int[] yPositions, int[] xResults, int[] yResults) {
		OCLContext c = getContext().getContext();
		OCLCommandQueue q = getContext().getQueue();
		
		OCLProgram program = null;
		OCLKernel kernel = null;
		OCLMem xPositionsMem = null;
		OCLMem yPositionsMem = null;
		OCLMem xResultsMem = null;
		OCLMem yResultsMem = null;
		
		try {
			program = c.createProgramFromFile(SOURCE_NEAREST);
			kernel = program.createKernel("search_float_nearest");
			
			xPositionsMem = c.newBuffer().Using(xPositions).b();
			yPositionsMem = c.newBuffer().Using(yPositions).b();
			xResultsMem = c.newBuffer().Size(xResults.length, OCLType.FLOAT).b();
			yResultsMem = c.newBuffer().Size(yResults.length, OCLType.FLOAT).b();
			
			kernel.setArguments(vector, radius, value, xPositionsMem, yPositionsMem, xResultsMem, yResultsMem);
			OCLEvent finished = q.enqueue1DKernel(kernel, vector.length);
			
			OCLEvent r1 = q.enqueueReadBuffer(xResultsMem, Pointer.to(xResults), 0, xResultsMem.getSize(), finished);
			OCLEvent r2 = q.enqueueReadBuffer(yResultsMem, Pointer.to(yResults), 0, yResultsMem.getSize(), finished);
			
			OCLEvent.waitFor(r1, r2);
		} finally {
			xPositionsMem.release();
			yPositionsMem.release();
			xResultsMem.release();
			yResultsMem.release();
			kernel.release();
			program.release();
		}
	}*/
	
	public static void main(String[] args) {
		MCMASContext context = new MCMASContext();
		GridSearchPlugin plugin = new GridSearchPlugin(context);
		
		IntMatrix grid = new IntMatrix(5, 5);
		int [] xPositions = new int[] { 2 };
		int [] yPositions = new int[] { 2 };
		int [] xResults   = new int[1];
		int [] yResults   = new int[1];
		
		grid.set(2, 2, 1);
		grid.set(3, 4, 7);
		grid.set(4, 4, 6);
		
		plugin.findMaxCell(grid, 2, xPositions, yPositions, xResults, yResults);
		
		System.out.println("After");
		System.out.println(grid);
		System.out.println(Arrays.toString(xResults));
		System.out.println(Arrays.toString(yResults));
		
		for (int i = 0; i < xPositions.length; i++) {
			System.out.println("Move: (" + xPositions[i] + ", " + yPositions[i] + ") => (" +
					xResults[i] + ", " + yResults[i] + ")");	
		}
	}

}
