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 club.doki7.ffm.ptr.IntPtr;
import club.doki7.opencl.CL;
import club.doki7.opencl.CLConstants;
import club.doki7.opencl.CLFunctionTypes.Ipfn_notify_3;
import club.doki7.opencl.handle.CLMem;

public class OpenCLMemory{
	private CLMem cl_mem;
	private OpenCLContext context;
	private OpenCLMemoryRWFlag rwflag;
	private OpenCLMemoryCopyType copytype;
	private long size;

	private static final Cleaner clr=Cleaner.create();

	public OpenCLMemoryRWFlag getRwflag() {
		return rwflag;
	}
	public OpenCLMemoryCopyType getCopytype() {
		return copytype;
	}
	public long getSize() {
		return size;
	}

	public CLMem getCl_mem() {
		return cl_mem;
	}
	public OpenCLContext getContext() {
		return context;
	}
	protected OpenCLMemory(OpenCLContext context,long memflags,long size,MemorySegment initData) {
		this.context=context;
		this.size=size;
		if(initData==null)
			initData=MemorySegment.NULL;
		IntPtr errcode=IntPtr.allocate(Arena.ofAuto());
		cl_mem = OpenCLPlatform.opencl.createBuffer(context.getCl_context(), memflags, size, initData, errcode);
		CLUtils.checkResult(errcode);
		regCleaner();
	}
	private void regCleaner() {
		releaser=new OpenCLMemoryReleaser(cl_mem);
		clr.register(this, releaser);
	}
	public OpenCLMemory(OpenCLContext context,OpenCLMemoryRWFlag rwflag,OpenCLMemoryCopyType copytype,long size,MemorySegment initData) {
		this.rwflag=rwflag;
		this.copytype=copytype;
		this.context=context;
		this.size=size;
		if(initData==null)
			initData=MemorySegment.NULL;
		IntPtr errcode=IntPtr.allocate(Arena.ofAuto());
		long flag=rwflag.flagValue;
		if(initData!=null) {
		flag|=(copytype==null?0:copytype.flagValue);
		}
		cl_mem = OpenCLPlatform.opencl.createBuffer(context.getCl_context(), flag, size, initData, errcode);
		CLUtils.checkResult(errcode);
		regCleaner();
	}
	public OpenCLMemory(OpenCLContext context,long size) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,null,size,null);
	}
	/*public OpenCLMemory(OpenCLContext context,byte[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_char*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,short[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_short*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,char[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_char2*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,int[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_int*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,long[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_long*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,float[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_float*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,double[]initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,Sizeof.cl_double*initData.length,Pointer.to(initData));	
	}
	public OpenCLMemory(OpenCLContext context,Buffer initData) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,OpenCLMemoryCopyType.COPY_HOST_PTR,initData.capacity()*getBufferUnitSize(initData) ,Pointer.to(initData));	
	}*/
	public OpenCLMemory(OpenCLContext context,MemorySegment initData,OpenCLMemoryCopyType copyType) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,copyType,initData.byteSize() ,initData);	
	}
	public OpenCLMemory(OpenCLContext context,MemorySegment initData) {
		this(context,initData,OpenCLMemoryCopyType.COPY_HOST_PTR);
	}
	
	public OpenCLMemory(OpenCLContext context, OpenCLMemoryCopyType copyHostPtr, long size) {
		this(context,OpenCLMemoryRWFlag.READ_WRITE,copyHostPtr,size,null);
	}
	
	
	public long byteSize() {
		MemorySegment ms= Arena.ofAuto().allocate(8);
		OpenCLPlatform.opencl.getMemObjectInfo(cl_mem, CLConstants.MEM_SIZE, 8, ms, null);
		return ms.getAtIndex(OfLong.JAVA_LONG, 0);
	}
	
	public long address() {
		return cl_mem.segment().address();
	}
	
	
	
	private class CallbackImpl implements Ipfn_notify_3{

		private OpenCLMemReleaseCallback callback;
		private Object userData;
		
		public CallbackImpl(OpenCLMemReleaseCallback callback, Object userData) {
			this.callback=callback;
			this.userData=userData;
		}

		@Override
		public void invoke(MemorySegment p0, MemorySegment p1) {
			if(callback!=null)
			callback.function(OpenCLMemory.this,userData);
		}
	
	}
	public void setReleaseCallback(OpenCLMemReleaseCallback callback,Object userData) {
		MemorySegment userDataSegment=MemorySegment.NULL;
		if(userData instanceof Long) {
			userDataSegment=Arena.ofAuto().allocate(8);
			userDataSegment.setAtIndex(OfLong.JAVA_LONG, 0, (Long) userData);
		}
		int errcode=OpenCLPlatform.opencl.setMemObjectDestructorCallback(cl_mem, 
				new CallbackImpl(callback,userData), 
		            userDataSegment);
		CLUtils.checkResult(errcode);
	}
	
	private OpenCLMemoryReleaser releaser;
	public void close() {
		releaser.run();
	}
	
	public boolean isClosed() {
		return releaser.isReleased();
	}
}
class OpenCLMemoryReleaser extends Releaser<CLMem> implements Runnable{

	public OpenCLMemoryReleaser(CLMem resource) {
		super(resource);
	}

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