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.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import club.doki7.ffm.ptr.IntPtr;
import club.doki7.opencl.CLFunctionTypes.Ipfn_notify_0;
import club.doki7.opencl.handle.CLContext;
import club.doki7.opencl.handle.CLDeviceId;


public class OpenCLContext {
	
	private CLContext cl_context;
	private List<OpenCLDevice> devices;
	private OpenCLContextProperties properties;
	private OpenCLPlatform platform;
	
	
	
	public OpenCLPlatform getPlatform() {
		return platform;
	}

	protected CLContext getCl_context() {
		return cl_context;
	}
	
	public List<OpenCLDevice> getDevices() {
		return devices;
	}
	
	public OpenCLContextProperties getProperties() {
		return properties;
	}

	private static final Cleaner clr=Cleaner.create();
	
	private class CallbackImpl implements Ipfn_notify_0{
		
		private OpenCLCreateContextCallback callback;
		private Object userData;

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

		@Override
		public void invoke( MemorySegment p0,  MemorySegment p1, MemorySegment p2,
				 MemorySegment p3) {
			if(callback!=null)
			callback.function(p0.getString(0, Charset.forName("utf-8")), p1.getAtIndex(OfLong.JAVA_LONG, 0) , p2.getAtIndex(OfLong.JAVA_LONG, 0), userData);
		}
		
	}
	
	public OpenCLContext(OpenCLContextProperties properties,List<OpenCLDevice> devices,OpenCLCreateContextCallback callback,Object userData) {
		if(devices==null) {
			throw new IllegalArgumentException("ContextPlatform in properties is null!");
		}
			devices=Collections.unmodifiableList( new ArrayList<OpenCLDevice>(devices));
		
		this.devices= devices;
		int size=devices.size();
		CLDeviceId.Ptr devs=CLDeviceId.Ptr.allocate(Arena.ofAuto(),size);
		for (int i = 0; i < size; i++) {
			devs.write(i, devices.get(i).getCl_device_id());
		}
		
		this.properties=properties;
		this.platform=properties.getContextPlatform();
		MemorySegment userDataSegment=MemorySegment.NULL;
		if(userData instanceof Long) {
			userDataSegment=Arena.ofAuto().allocate(8);
			userDataSegment.setAtIndex(OfLong.JAVA_LONG, 0, (Long) userData);
		}
		IntPtr errcode=IntPtr.allocate(Arena.ofAuto());
		cl_context=OpenCLPlatform.opencl.createContext(properties.toPtrProperties(), size,devs , new CallbackImpl(callback,userData),userDataSegment, errcode);
		CLUtils.checkResult(errcode);
		releaser=new OpenCLContextReleaser(cl_context);
		clr.register(this, releaser);
	}
	public OpenCLContext(OpenCLPlatform platform,List<OpenCLDevice> devices,OpenCLCreateContextCallback callback,Object userData) {
		 
		this(createDefaultProperties(platform),devices,callback,userData);
	}
	public OpenCLContext(OpenCLPlatform platform,List<OpenCLDevice> devices) {
		this(platform,devices,null,null);
	}

	public OpenCLContext(OpenCLPlatform platform, OpenCLDevice device) {
		this(platform,List.of( device));
	}
	private static OpenCLContextProperties createDefaultProperties(OpenCLPlatform platform) {
		OpenCLContextProperties contextProperties = new OpenCLContextProperties();
	        contextProperties.setContextPlatform( platform);
		return contextProperties;
	}

	private OpenCLContextReleaser releaser;
	
	public void close() {
		
		releaser.run();
	}
	public boolean isClosed() {
		return releaser.isReleased();
	}
}
class OpenCLContextReleaser extends Releaser<CLContext>{

	public OpenCLContextReleaser(CLContext resource) {
		super(resource);
	}

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