#parse("main/Header.vm")
package com.nativelibs4java.opencl;
import static com.nativelibs4java.opencl.CLException.error;
import static com.nativelibs4java.opencl.JavaCL.CL;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_FALSE;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_QUEUE_PROPERTIES;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_TRUE;

import java.util.EnumSet;

import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_command_queue;
import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_event;
import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_mem;
import org.bridj.*;
import static org.bridj.Pointer.*;

/**
 * OpenCL command queue.<br>
 * OpenCL objects such as memory, program and kernel objects are created using a context. <br>
 * Operations on these objects are performed using a command-queue. <br>
 * The command-queue can be used to queue a set of operations (referred to as commands) in order. <br>
 * Having multiple command-queues allows applications to queue multiple independent commands without requiring synchronization. <br>
 * Note that this should work as long as these objects are not being shared. <br>
 * Sharing of objects across multiple command-queues will require the application to perform appropriate synchronization.<br>
 * <br>
 * A queue is bound to a single device.
 * see {@link CLDevice#createQueue(com.nativelibs4java.opencl.CLContext, com.nativelibs4java.opencl.CLDevice.QueueProperties[]) } 
 * see {@link CLDevice#createOutOfOrderQueue(com.nativelibs4java.opencl.CLContext) }
 * see {@link CLDevice#createProfilingQueue(com.nativelibs4java.opencl.CLContext) }
 * see {@link CLContext#createDefaultQueue(com.nativelibs4java.opencl.CLDevice.QueueProperties[]) }
 * see {@link CLContext#createDefaultOutOfOrderQueue() }
 * see {@link CLContext#createDefaultProfilingQueue() }
 * @author Olivier Chafik
 *
 */
public class CLQueue extends CLAbstractEntity {

    #declareInfosGetter("infos", "CL.clGetCommandQueueInfo")

	final CLContext context;
	final CLDevice device;

    CLQueue(CLContext context, long entity, CLDevice device) {
        super(entity);
        this.context = context;
		this.device = device;
    }
    
    public CLContext getContext() {
        return context;
    }
	public CLDevice getDevice() {
		return device;
	}
	
	volatile Boolean outOfOrder;
	public synchronized boolean isOutOfOrder() {
		if (outOfOrder == null)
			outOfOrder = getProperties().contains(CLDevice.QueueProperties.OutOfOrderExecModeEnable);
		return outOfOrder;
	}

	@InfoName("CL_QUEUE_PROPERTIES")
	public EnumSet<CLDevice.QueueProperties> getProperties() {
		return CLDevice.QueueProperties.getEnumSet(infos.getIntOrLong(getEntity(), CL_QUEUE_PROPERTIES));
	}

	@SuppressWarnings("deprecation")
	public void setProperty(CLDevice.QueueProperties property, boolean enabled) {
		context.getPlatform().requireMinVersionValue("clSetCommandQueueProperty", 1.0, 1.1);
		error(CL.clSetCommandQueueProperty(getEntity(), property.value(), enabled ? CL_TRUE : CL_FALSE, 0));
	}
	

    @Override
    protected void clear() {
        error(CL.clReleaseCommandQueue(getEntity()));
    }

    /**
#documentCallsFunction("clFinish")
	 * Blocks until all previously queued OpenCL commands in this queue are issued to the associated device and have completed. <br>
	 * finish() does not return until all queued commands in this queue have been processed and completed. <br>
	 * finish() is also a synchronization point.
	 */
    public void finish() {
        error(CL.clFinish(getEntity()));
    }

    /**
#documentCallsFunction("clFlush")
	 * Issues all previously queued OpenCL commands in this queue to the device associated with this queue. <br>
	 * flush() only guarantees that all queued commands in this queue get issued to the appropriate device. <br>
	 * There is no guarantee that they will be complete after flush() returns.
	 */
    public void flush() {
        error(CL.clFlush(getEntity()));
    }

	/**
#documentCallsFunction("clEnqueueWaitForEvents")
	 * Enqueues a wait for a specific event or a list of events to complete before any future commands queued in the this queue are executed.
	 */
	public void enqueueWaitForEvents(CLEvent... eventsToWaitFor) {
		context.getPlatform().requireMinVersionValue("clEnqueueWaitForEvents", 1.1, 1.2);
		#declareReusablePtrs()
		#declareEventsIn()
        if (eventsIn == null)
            return;
        error(CL.clEnqueueWaitForEvents(getEntity(), #eventsInArgsRaw()));
	}
	

	/**
#documentCallsFunction("clEnqueueMigrateMemObjects")
	 * Enqueues a command to indicate which device a set of memory objects should be associated with.
	 */
	public CLEvent enqueueMigrateMemObjects(CLMem[] memObjects, EnumSet<CLMem.Migration> flags, CLEvent... eventsToWaitFor) {
		context.getPlatform().requireMinVersionValue("clEnqueueMigrateMemObjects", 1.2);
		#declareReusablePtrsAndEventsInOut()
		int[] n = ptrs.int1Array;
		Pointer<SizeT> pMems = pointerToEntities(memObjects, n);
		error(CL.clEnqueueMigrateMemObjects(
			getEntity(),
			n[0],
			getPeer(pMems),
			CLMem.Migration.getValue(flags),
			#eventsInOutArgsRaw()
		));
		#returnEventOut("this")
	}

	// 
	public interface NativeKernel {
		void execute(Pointer[] bufferPointers);
	}

	private static Pointer<SizeT> pointerToEntities(CLAbstractEntity[] entities, int[] n) {
		int nn = 0;
		Pointer<SizeT> pEntities = allocateSizeTs(entities.length);
		for (CLAbstractEntity entity : entities) {
			if (entity != null) {
				pEntities.setSizeTAtIndex(nn++, entity.getEntity());
			}
		}
		n[0] = nn;
		return pEntities;
	}
	/**
#documentCallsFunction("clEnqueueNativeKernel")
	 * Enqueues a command to execute a Java callback with direct access to buffer memory.
	 */
	/*
	public CLEvent enqueueNativeKernel(NativeKernel kernel, CLMem[] buffers, CLEvent... eventsToWaitFor) {
		// TODO check 1.1 or 1.2?
		context.getPlatform().requireMinVersionValue("clEnqueueNativeKernel", 1.2);
		#declareReusablePtrsAndEventsInOut()
		int[] n = ptrs.int1Array;
		Pointer<SizeT> pMems = pointerToEntities(buffers, n);
		error(CL.clEnqueueNativeKernel(
			getEntity(),
			n[0],
			getPeer(pMems),
			CLMem.Migration.getValue(flags),
			#eventsInOutArgsRaw()
		));
		#returnEventOut("this")
	}
	*/

	/**
#documentCallsFunction("clEnqueueBarrierWithWaitList")
	 * Enqueue a barrier operation.<br>
	 * The enqueueBarrier() command ensures that all queued commands in command_queue have finished execution before the next batch of commands can begin execution. <br>
	 * enqueueBarrier() is a synchronization point.
#documentEventsToWaitForAndReturn()
	 */
	public CLEvent enqueueBarrier(CLEvent... eventsToWaitFor) {
		if (context.getPlatform().getVersionValue() >= 1.2 ||
			eventsToWaitFor != null && eventsToWaitFor.length > 0)
		{
			context.getPlatform().requireMinVersionValue("clEnqueueBarrierWithWaitList", 1.2);
			#declareReusablePtrsAndEventsInOut()
			error(CL.clEnqueueBarrierWithWaitList(
				getEntity(),
				#eventsInOutArgsRaw()
			));
			#returnEventOut("this")	
		} else {
			context.getPlatform().requireMinVersionValue("clEnqueueBarrier", 1.1, 1.2);
			error(CL.clEnqueueBarrier(getEntity()));
			return null;
		}
	}

	/**
#documentCallsFunction("clEnqueueMarkerWithWaitList")
	 * Enqueue a marker command to command_queue. <br>
	 * The marker command returns an event which can be used by to queue a wait on this marker event i.e. wait for all commands queued before the marker command to complete.
#documentEventsToWaitForAndReturn()
	 */
	@Deprecated
	public CLEvent enqueueMarker(CLEvent... eventsToWaitFor) {
		if (context.getPlatform().getVersionValue() >= 1.2 ||
			eventsToWaitFor != null && eventsToWaitFor.length > 0)
		{
			context.getPlatform().requireMinVersionValue("clEnqueueMarkerWithWaitList", 1.2);
			#declareReusablePtrsAndEventsInOut()
	    	error(CL.clEnqueueMarkerWithWaitList(
				getEntity(),
				#eventsInOutArgsRaw()
			));
			#returnEventOut("this")
		} else {
			context.getPlatform().requireMinVersionValue("clEnqueueMarker", 1.1, 1.2);
			#declareReusablePtrs()
			Pointer<cl_event> eventOut = ptrs.event_out;
			error(CL.clEnqueueMarker(getEntity(), getPeer(eventOut)));
			#returnEventOut("this")
		}
	}

	/**
#documentCallsFunction("clEnqueueAcquireGLObjects")
	 * Used to acquire OpenCL memory objects that have been created from OpenGL objects. <br>
	 * These objects need to be acquired before they can be used by any OpenCL commands queued to a command-queue. <br>
	 * The OpenGL objects are acquired by the OpenCL context associated with this queue and can therefore be used by all command-queues associated with the OpenCL context.
	 * @param objects CL memory objects that correspond to GL objects.
#documentEventsToWaitForAndReturn()
	 */
	public CLEvent enqueueAcquireGLObjects(CLMem[] objects, CLEvent... eventsToWaitFor) {
        #declareReusablePtrsAndEventsInOut()
		Pointer<SizeT> mems = allocateSizeTs(objects.length);
		for (int i = 0; i < objects.length; i++) {
			mems.setSizeTAtIndex(i, objects[i].getEntity());
		}
        error(CL.clEnqueueAcquireGLObjects(
			getEntity(), 
			objects.length,
			getPeer(mems),
			#eventsInOutArgsRaw()
		));
		#returnEventOut("this")
	}

	/**
#documentCallsFunction("clEnqueueReleaseGLObjects")
	 * Used to release OpenCL memory objects that have been created from OpenGL objects. <br>
	 * These objects need to be released before they can be used by OpenGL. <br>
	 * The OpenGL objects are released by the OpenCL context associated with this queue.
	 * @param objects CL memory objects that correpond to GL objects.
#documentEventsToWaitForAndReturn()
	 */
	public CLEvent enqueueReleaseGLObjects(CLMem[] objects, CLEvent... eventsToWaitFor) {
        #declareReusablePtrsAndEventsInOut()
		Pointer<?> mems = getEntities(objects, (Pointer)allocateSizeTs(objects.length));
        error(CL.clEnqueueReleaseGLObjects(
			getEntity(), 
			objects.length, 
			getPeer(mems),
			#eventsInOutArgsRaw()
		));
		#returnEventOut("this")
	}
}
