package mcmas.core;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import mcmas.utils.Objects7;

import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_event;


public class MCMEvent extends MCMObject implements Future<Void> {
	
	private final cl_event event;
	
	public static MCMEvent createUserEvent(MCMContext context) {
		context = Objects7.requireNonNull(context, "Can't create an user event using a null context");
		int v = context.getPlatforms()[0].getSupportedVersionInt();
		
		if (v <= MCM.CL_VERSION_1_1) {
			throw new RuntimeException("This function is not supported on OpenCL < 1.1");
		}
		
		return new MCMEvent(MCMHelpers.createUserEvent(context.getContext()));
	}
	
	public MCMEvent() {
		this.event = new cl_event();
	}
	
	private MCMEvent(cl_event e) {
		e = Objects7.requireNonNull(e, "Can't create a null event");
		this.event = e;
	}
	
	public cl_event getEvent() {
		return event;
	}
	
	public MCMEventStatus getStatus() {
		int [] status = new int[1];
		CL.clGetEventInfo(event, CL.CL_EVENT_COMMAND_EXECUTION_STATUS, Sizeof.cl_int, Pointer.to(status), null);
		return MCMEventStatus.wrap(status[0]);
	}
	
	public long getProfilingInfo(MCMProfilingInfo info) {
		long [] result = new long[1];
		CL.clGetEventProfilingInfo(event, info.value, Sizeof.cl_ulong, Pointer.to(result), null);
		return result[0];
	}
	
	public long getWaitDuration() {
		return getProfilingInfo(MCMProfilingInfo.COMMAND_START) - getProfilingInfo(MCMProfilingInfo.COMMAND_QUEUED);
	}
	
	public long getExecutionDuration() {
		return getProfilingInfo(MCMProfilingInfo.COMMAND_END) - getProfilingInfo(MCMProfilingInfo.COMMAND_START);
	}
	
	protected void releaseImpl() {
		CL.clReleaseEvent(event);
	}
	
	/*
	public static void waitFor(OCLEvent event) {
		System.out.println("waitFor1");
		cl_event e = event.getEvent();
		CL.clWaitForEvents(1, new cl_event[] { e });
		System.out.println("waitFor2");
	}*/
	
	public static void waitFor(MCMEvent... events) {
		CL.clWaitForEvents(events.length, events_arr(events));
	}
	
	public void addListener(MCMEventStatus status, MCMEventListener listener, Object data) {
		CL.clSetEventCallback(event, status.value, listener, data);
	}
	
	public static cl_event[] events_arr(MCMEvent... events) {
		if (events.length == 0) return null;
		
		cl_event[] array = new cl_event[events.length];
		for (int i = 0; i < events.length; i++) {
			array[i] = events[i].event;
		}
		return array;
	}
	
	@Override
	public boolean cancel(boolean interruptIfRunning) {
		return false;
	}

	@Override
	public Void get() throws InterruptedException, ExecutionException {
		waitFor(this);
		return null;
	}

	@Override
	public Void get(long timeout, TimeUnit unit) throws InterruptedException,
			ExecutionException, TimeoutException {
		// throw new UnsupportedOperationException("This feature isn't supported by OpenCL");
		
		long msWaited = 0;
		final long msDelay = 100;
		final long msTimeout = unit.convert(timeout, TimeUnit.MILLISECONDS);
		
		while (msWaited <= msTimeout) {
			if (isCancelled()) {
				throw new CancellationException();
			} else if (isDone()) {
				break;
			}
			
			msWaited += msDelay;
			Thread.sleep(msDelay);
		}
		
		if (msWaited > msTimeout) {
			throw new TimeoutException();
		}
		
		return null;
	}

	@Override
	public boolean isCancelled() {
		return false;
	}

	@Override
	public boolean isDone() {
		return getStatus() == MCMEventStatus.COMPLETE;
	}
	
}
