package org.kne.opencl64;

import java.io.IOException;
import java.io.InputStream;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout.OfLong;
import java.lang.ref.Cleaner;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;


import club.doki7.ffm.ptr.BytePtr;
import club.doki7.ffm.ptr.IntPtr;
import club.doki7.ffm.ptr.PointerPtr;
import club.doki7.opencl.CLConstants;
import club.doki7.opencl.CLFunctionTypes.Ipfn_notify_5;
import club.doki7.opencl.handle.CLDeviceId;
import club.doki7.opencl.handle.CLDeviceId.Ptr;
import club.doki7.opencl.handle.CLKernel;
import club.doki7.opencl.handle.CLProgram;

public class OpenCLProgram{
	private CLProgram cl_program;
	private OpenCLContext context;
	
	private static final Cleaner clr=Cleaner.create();
	{
		clr.register(this, ()->{
			close();
		});
	}
	
	

	public CLProgram getCl_program() {
		return cl_program;
	}

	public OpenCLContext getContext() {
		return context;
	}

	public OpenCLProgram(OpenCLContext context,String[]sources) {
		this.context=context;
		PointerPtr ptrs=PointerPtr.allocateStrings(Arena.ofAuto(), sources);
		IntPtr errcode=IntPtr.allocate(Arena.ofAuto());
		cl_program = OpenCLPlatform.opencl.createProgramWithSource(context.getCl_context(),
				sources.length, ptrs, null, errcode);
		 CLUtils.checkResult(errcode);
	}
	public OpenCLProgram(OpenCLContext context,String source) {
		this(context,new String[] {source});
	}
	public OpenCLProgram(OpenCLContext context,InputStream sourceinput) throws IOException {
		this(context,readInput(sourceinput));
	}
	private static String readInput(InputStream sourceinput) throws IOException {
	        try
	        {
	            return new String( sourceinput.readAllBytes(),Charset.forName("utf-8"));
	        }
	        finally
	        {
	           if(sourceinput!=null)
	        	   sourceinput.close();
	        }
	}
	
	private class CallbackImpl implements Ipfn_notify_5{
		
		private OpenCLBuildProgramCallback callback;
		private Object userData;

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

		@Override
		public void invoke(MemorySegment p0, MemorySegment p1) {
			if(callback!=null)
			callback.function(OpenCLProgram.this, userData);
		}
		
	}
	
	public void buildProgram(List<OpenCLDevice>devices,String options,OpenCLBuildProgramCallback callback,Object userData) {
		if(devices==null) {
			int errcode =OpenCLPlatform.opencl.buildProgram(cl_program, 0, null, null, new CallbackImpl( callback,userData), MemorySegment.NULL);
			 CLUtils.checkResult(this,null,errcode);
		}else {
		int size=devices.size();
		CLDeviceId.Ptr devid=CLDeviceId.Ptr.allocate(Arena.ofAuto(),size);
		for (int i = 0; i < size; i++) {
			CLDeviceId cid=devices.get(i).getCl_device_id();
			devid.write(i,cid);
		}
		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.buildProgram(cl_program, size, devid, options==null?null:BytePtr.allocateString(Arena.ofAuto(), options),new CallbackImpl( callback,userData), userDataSegment);
		CLUtils.checkResult(this,devices, errcode);
		}
		
	}
	

	public void buildProgram(List<OpenCLDevice>devices) {
		buildProgram(devices,null,null,null);
	}
	public void buildProgram(String options) {
		buildProgram(null,options,null,null);
	}
	public void buildProgram() {
		buildProgram((String)null);
	}
	
	public OpenCLKernel createKernel(String name) {
		return new OpenCLKernel(this, name);
	}
	
	public List<OpenCLKernel> createKernels(){
		IntPtr nums=IntPtr.allocate(Arena.ofAuto());
		int errcode=OpenCLPlatform.opencl.createKernelsInProgram(cl_program, 0, null, nums);
		 CLUtils.checkResult(errcode);
		CLKernel.Ptr kernels=CLKernel.Ptr.allocate(Arena.ofAuto(),nums.read());
		errcode=OpenCLPlatform.opencl.createKernelsInProgram(cl_program, nums.read(), kernels, nums);
		 CLUtils.checkResult(errcode);
		List<OpenCLKernel> ret=new ArrayList<>();
		for (int i = 0; i < nums.read(); i++) {
			ret.add(new OpenCLKernel(this, kernels.read(i)) );
		}
		return ret;
	}
	
	private OpenCLProgramReleaser releaser;
	public void close() {
		releaser.run();
	}
	
	public boolean isClosed() {
		return releaser.isReleased();
	}

	public String getBuildLog(OpenCLDevice dev) {
		PointerPtr iptr=PointerPtr.allocate(Arena.ofAuto(),8);
		CLDeviceId device=dev.getCl_device_id();
		int resultr =OpenCLPlatform.opencl.getProgramBuildInfo(getCl_program(), device, CLConstants.PROGRAM_BUILD_LOG, 0, MemorySegment.NULL ,iptr);
		CLUtils. checkResult(resultr);
		MemorySegment ms=Arena.ofAuto().allocate(iptr.segment().get(OfLong.JAVA_LONG, 0));
		int resultrx =OpenCLPlatform.opencl.getProgramBuildInfo(getCl_program(), device, CLConstants.PROGRAM_BUILD_LOG, ms.byteSize(),ms ,iptr);
		CLUtils.checkResult(resultrx);
		String log=ms.getString(0, Charset.defaultCharset());
		return log;
	}
}
class OpenCLProgramReleaser extends Releaser<CLProgram>{

	public OpenCLProgramReleaser(CLProgram resource) {
		super(resource);
	}

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