package org.kne.opencl64;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout.OfLong;
import java.lang.ref.Cleaner;
import java.util.Arrays;

import club.doki7.ffm.ptr.IntPtr;
import club.doki7.ffm.ptr.PointerPtr;
import club.doki7.opencl.CLConstants;
import club.doki7.opencl.handle.CLCommandQueue;
public class OpenCLCommandQueue {
	private OpenCLContext context;
	private OpenCLDevice device;
	private CLCommandQueue cl_command_queue;
	
	private static final Cleaner clr=Cleaner.create();
	
	public OpenCLContext getContext() {
		return context;
	}
	
	public OpenCLDevice getDevice() {
		return device;
	}

	public CLCommandQueue getCl_command_queue() {
		return cl_command_queue;
	}
	public OpenCLCommandQueue(OpenCLContext context,OpenCLDevice device,OpenCLCommandQueueProperties properties) {
		this.context=context;
		this.device=device;
		IntPtr errcode=IntPtr.allocate(Arena.ofAuto());
		cl_command_queue= OpenCLPlatform. opencl.createCommandQueueWithProperties(
	            context.getCl_context(), device.getCl_device_id(), properties.toLongProperties(), errcode);
		CLUtils.checkResult(errcode);
		releaser=new OpenCLCommandQueueReleaser(cl_command_queue);
		clr.register(this, releaser);
	}
	
	
	
	
	public OpenCLCommandQueue(OpenCLContext context,OpenCLDevice device) {
		this(context,device,new OpenCLCommandQueueProperties());
	}

	public void executeNDRangeKernel(OpenCLKernel kernel,long[]globalOffset,long[]globalSize) {
		executeNDRangeKernel(kernel, globalOffset, globalSize, null);
	}
	
	public void executeNDRangeKernel(OpenCLKernel kernel,long[]globalOffset,long[]globalSize,long[]localSize) {
		if(globalSize==null) {
			throw new NullPointerException("globalSize is null!");
		}
		if(globalOffset!=null&&globalOffset.length != globalSize.length) {
			throw new IllegalArgumentException("globalOffset.length != globalSize.length");
		}
		if(localSize!=null&&localSize.length != globalSize.length) {
			throw new IllegalArgumentException("localSize.length != globalSize.length");
		}
		MemorySegment sizes=Arena.ofAuto().allocate(globalSize.length*3L*OfLong.JAVA_LONG.byteSize());
		if(globalOffset!=null)
		MemorySegment.copy(globalOffset, 0, sizes, OfLong.JAVA_LONG, OfLong.JAVA_LONG.byteSize()*0, globalSize.length);
		if(globalSize!=null)
		MemorySegment.copy(globalSize, 0, sizes, OfLong.JAVA_LONG, OfLong.JAVA_LONG.byteSize()*globalSize.length, globalSize.length);
		if(localSize!=null)
		MemorySegment.copy(localSize, 0, sizes, OfLong.JAVA_LONG, OfLong.JAVA_LONG.byteSize()*globalSize.length*2, globalSize.length);
		PointerPtr ptr0=globalOffset==null?null: new PointerPtr(sizes);
		PointerPtr ptr1=globalSize==null?null:new PointerPtr( sizes.asSlice(1L*OfLong.JAVA_LONG.byteSize()*globalSize.length));
		PointerPtr ptr2=localSize==null?null: new PointerPtr(sizes.asSlice(2L*OfLong.JAVA_LONG.byteSize()*globalSize.length));
		int errcode=OpenCLPlatform.opencl.enqueueNDRangeKernel(cl_command_queue, kernel.getCl_kernel(), globalSize.length,ptr0,ptr1,ptr2, 0, null, null);
		 CLUtils.checkResult(errcode);
	}
	
	public void execute1DRangeKernel(OpenCLKernel kernel,long globalOffset,long globalSize,long localSize) {
		executeNDRangeKernel(kernel,new long[] { globalOffset},new long[] {globalSize},(localSize==-1)?null:new long[] {localSize});
	}
	
	public void execute1DRangeKernel(OpenCLKernel kernel,long globalOffset,long globalSize) {
		execute1DRangeKernel(kernel, globalOffset, globalSize,-1);
	}
	
	
	
	public void executeReadBuffer(OpenCLMemory omem, MemorySegment jmem, boolean block,long omemoffset,long jmemoffset,long size) {
		if(omemoffset<0|omemoffset>=omem.getSize()) {
			throw new IndexOutOfBoundsException("Index "+omemoffset+" out of bounds for length "+omem.getSize());
		}
		int errcode =OpenCLPlatform.opencl.enqueueReadBuffer(cl_command_queue, omem.getCl_mem(), block?CLConstants.TRUE:CLConstants.FALSE, omemoffset, size,jmem, 0, null, null);
		 CLUtils.checkResult(errcode);
	}
	
	public void executeReadBuffer(OpenCLMemory omem, MemorySegment jmem, boolean block) {
		if(omem.getSize() != jmem.byteSize()) {
			throw new IllegalArgumentException("omem.getSize() != jmem.byteSize(),please give offsets and size manually!");
		}
		executeReadBuffer(omem, jmem, block, 0, 0, omem.getSize());
	}
	
	public void executeWriteBuffer(OpenCLMemory omem, MemorySegment jmem, boolean block,long omemoffset,long jmemoffset,long size) {
		if(omemoffset<0|omemoffset>=omem.getSize()) {
			throw new IndexOutOfBoundsException("Index "+omemoffset+" out of bounds for length "+omem.getSize());
		}
		int errcode =OpenCLPlatform.opencl.enqueueWriteBuffer(cl_command_queue, omem.getCl_mem(), block?CLConstants.TRUE:CLConstants.FALSE, omemoffset, size, jmem, 0, null, null);
		 CLUtils.checkResult(errcode);
	}
	
	public void executeWriteBuffer(OpenCLMemory omem, MemorySegment jmem, boolean block) {
		if(omem.getSize() != jmem.byteSize()) {
			throw new IllegalArgumentException("omem.getSize() != jmem.byteSize(),please give offsets and size manually!");
		}
		executeWriteBuffer(omem, jmem, block, 0, 0, omem.getSize());
	}
	public void executeCopyBuffer(OpenCLMemory src, OpenCLMemory dst, long srcoffset,long dstoffset,long size) {
		int errcode =OpenCLPlatform.opencl.enqueueCopyBuffer(cl_command_queue, src.getCl_mem(), dst.getCl_mem(), srcoffset, dstoffset, size, 0, null, null);
		 CLUtils.checkResult(errcode);
	}
	
	public MemorySegment mapBuffer(OpenCLMemory buffer,boolean block,OpenCLMapFlag mapFlag,long offset,long size) {
		IntPtr errcode=IntPtr.allocate(Arena.ofAuto());
		MemorySegment mapd= OpenCLPlatform.opencl.enqueueMapBuffer(cl_command_queue, buffer.getCl_mem(), block?CLConstants.TRUE:CLConstants.FALSE, mapFlag.flags, offset, size, 0, null, null, errcode);
		CLUtils.checkResult(errcode);
		return mapd;
	}
	
	public void unmapBuffer(OpenCLMemory buffer,MemorySegment mapped,boolean block) {
		int errcode =OpenCLPlatform.opencl.enqueueUnmapMemObject(cl_command_queue, buffer.getCl_mem(), mapped, 0, null, null);
		CLUtils.checkResult(errcode);
	}
	
	public void finish() {
		int errcode =OpenCLPlatform.opencl.finish(cl_command_queue);
		 CLUtils.checkResult(errcode);
	}
	
	public void flush() {
		int errcode=OpenCLPlatform.opencl.flush(cl_command_queue);
		 CLUtils.checkResult(errcode);	
	}
	
	private OpenCLCommandQueueReleaser releaser;
	
	public void close() {
		releaser.run();
	}
	
	public boolean isClosed() {
		return releaser.isReleased();
	}
	

}
class OpenCLCommandQueueReleaser extends Releaser<CLCommandQueue>{

	public OpenCLCommandQueueReleaser(CLCommandQueue resource) {
		super(resource);
	}

	@Override
	protected void release(CLCommandQueue resource) {
        int errcode=OpenCLPlatform.opencl.releaseCommandQueue(resource);
		 CLUtils.checkResult(errcode);
	}
	
}