package mcmas.plugins.vectors;

import java.util.Arrays;
import java.util.Random;

import org.jocl.Pointer;

import mcmas.api.MCMASContext;
import mcmas.api.MCMASPlugin;
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 mcmas.core.MCMProgramOptions;

public class ReducePlugin extends MCMASPlugin<ReducePlugin> {
	
	//public static final int NBLOCKS = 1;
	public static final int BLOCK_SIZE = 64;
	
	public static final String KERNEL_NAME = "kernels/plugins/reduce.cl";
			
	public static final ReduceOperation MIN = ReduceOperation.MIN;
	public static final ReduceOperation MAX = ReduceOperation.MAX;
	public static final ReduceOperation SUM = ReduceOperation.SUM;
	public static final ReduceOperation MUL = ReduceOperation.MUL;
	
	@Override
	public ReducePlugin newInstance(MCMASContext context) {
		return new ReducePlugin(context);
	}
	
	public ReducePlugin(MCMASContext context) {
		super(context);
	}
	
	public int reduce(int[] data, ReduceOperation operation) {
		MCMContext c = getContext().getContext();
	
		MCMCommandQueue q = getContext().getQueue();
		
		MCMProgramOptions options = new MCMProgramOptions();
		prepareOptions(options, operation);
		
		System.out.println(options);
		
		final int NBLOCKS = (int) Math.ceil(data.length / (float) BLOCK_SIZE);
		
		MCMProgram program = c.createProgramFromFile(KERNEL_NAME, options);
		//System.out.println(getSourcePath(KERNEL_NAME));
		//		c.createProgramFromFile("kernels/greduce.cl", options);
		MCMKernel reducer = program.createKernel("greduce");
		
		MCMMem dataMem   = c.newBuffer().Using(data).b();
		MCMMem resultMem = c.newBuffer().Size(NBLOCKS, MCM.INT).b();
		MCMMem sharedMem = c.newBuffer().Size(BLOCK_SIZE, MCM.INT).Local().b();
		
		int [] result = new int[NBLOCKS];
		
		reducer.setArguments(dataMem, resultMem, sharedMem, data.length);
		MCMEvent finished = q.enqueue1DKernel(reducer, NBLOCKS * BLOCK_SIZE, BLOCK_SIZE);
		q.blockingReadBuffer(resultMem, Pointer.to(result), 0, resultMem.getSize(), finished);
		
		dataMem.release();
		resultMem.release();
		
		reducer.release();
		program.release();
		
		System.out.println(Arrays.toString(result));
		
		int t = 0;
		for (int i = 0; i < NBLOCKS; i++) {
			t += result[i];
		}
		
		return t;
	}
	
	private void prepareOptions(MCMProgramOptions options, ReduceOperation operation) {
		switch (operation) {
		case MIN:
			options.define("OP", "MIN");
			options.define("NEUTRAL", "INT_MAX");
			break;
		case MAX:
			options.define("OP", "MAX");
			options.define("NEUTRAL", "INT_MIN");
			break;
		case SUM:
			options.define("OP", "SUM");
			options.define("NEUTRAL", 0);
			break;
		case MUL:
			options.define("OP", "MUL");
			options.define("NEUTRAL", 1);
			break;
		default:
			break;
		}
	}
	
	public static void main(String[] args) {
		int [] data = new int[3000];
		
		int total = 0;
		int total2 = 0;
		
		Random rng = new Random();
		
		for (int i = 0; i < data.length; i++) {
			data[i] = rng.nextInt(100) + 1;
			total += data[i];
		}
		
		for (int i = 0; i < BLOCK_SIZE; i++) {
			total2 += data[i];
		}
		
		System.out.println(Arrays.toString(data));
		System.out.println(total);
		System.out.println(total2);
		
		MCMASContext context = new MCMASContext();
		ReducePlugin reducer = new ReducePlugin(context);
		
		int result = reducer.reduce(data, ReducePlugin.SUM);
		System.out.println(result);
		
		result = reducer.reduce(data, ReducePlugin.MUL);
		System.out.println(result);
		
		result = reducer.reduce(data, ReducePlugin.MIN);
		System.out.println(result);
		
		result = reducer.reduce(data, ReducePlugin.MAX);
		System.out.println(result);
		
		System.out.println(reducer.getSourceURI("example.cl"));
		System.out.println(reducer.getSourceURI(ReducePlugin.KERNEL_NAME));
	}
}
