package org.kne.opencl64;


import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import club.doki7.opencl.CLConstants;
import club.doki7.opencl.handle.CLDeviceId;

public class OpenCLDevice {
	private OpenCLPlatform platform;
	private CLDeviceId cl_device_id;

	private static final Cleaner clr=Cleaner.create();
	
	
	public static List<OpenCLDevice> getAllDevices() {
		List<OpenCLDevice> devs = new ArrayList<>();
		List<OpenCLPlatform> ocp = OpenCLPlatform.getOpenCLPlatforms();
		for (OpenCLPlatform openCLPlatform : ocp) {
			List<OpenCLDevice> devices = openCLPlatform.getOpenCLDevices();
			for (OpenCLDevice openCLDevice : devices) {
				devs.add(openCLDevice);
			}
		}
		return devs;
	}


	public static List<OpenCLDevice> getAllGPU() {
		List<OpenCLDevice> gpus = new ArrayList<>();
		List<OpenCLPlatform> ocp = OpenCLPlatform.getOpenCLPlatforms();
		for (OpenCLPlatform openCLPlatform : ocp) {
			List<OpenCLDevice> devices = openCLPlatform.getOpenCLDevices();
			for (OpenCLDevice openCLDevice : devices) {
				if(openCLDevice.getDeviceType()==DeviceType.GPU)
				gpus.add(openCLDevice);
			}
		}
		return gpus;
	}
	
	public static List<OpenCLDevice> getAllCPU() {
		List<OpenCLDevice> cpus = new ArrayList<>();
		List<OpenCLPlatform> ocp = OpenCLPlatform.getOpenCLPlatforms();
		for (OpenCLPlatform openCLPlatform : ocp) {
			List<OpenCLDevice> devices = openCLPlatform.getOpenCLDevices();
			for (OpenCLDevice openCLDevice : devices) {
				if(openCLDevice.getDeviceType()==DeviceType.CPU)
				cpus.add(openCLDevice);
			}
		}
		return cpus;
	}
	
	protected OpenCLDevice(OpenCLPlatform platform,CLDeviceId cl_device_id) {
		this.platform=platform;
		this.cl_device_id=cl_device_id;
		this.releaser=new OpenCLDeviceReleaser(cl_device_id);
		clr.register(this, releaser);
	}
	
	public OpenCLPlatform getPlatform() {
		return platform;
	}

	public CLDeviceId getCl_device_id() {
		return cl_device_id;
	}
	
	public String getName() {
		String deviceName = OpenCLPlatform.openclUtils. getString(cl_device_id, CLConstants. DEVICE_NAME);
		return deviceName;
	}
	
	public String getVendor() {
		String deviceVendor = OpenCLPlatform.openclUtils.getString(cl_device_id, CLConstants.DEVICE_VENDOR);
		return deviceVendor;
	}
	
	public String getDriverVersion() {
		String driverVersion = OpenCLPlatform.openclUtils.getString(cl_device_id, CLConstants.DRIVER_VERSION);
		return driverVersion;
	}
	
	public DeviceType getDeviceType() {
        long deviceType = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_TYPE);
        if( (deviceType & CLConstants.DEVICE_TYPE_CPU) != 0)
            return DeviceType.CPU;
        if( (deviceType & CLConstants.DEVICE_TYPE_GPU) != 0)
            return DeviceType.GPU;
        if( (deviceType & CLConstants.DEVICE_TYPE_ACCELERATOR) != 0)
            return DeviceType.ACCELERATOR;
        if( (deviceType & CLConstants.DEVICE_TYPE_DEFAULT) != 0)
        	return DeviceType.DEFAULT;
    	return DeviceType.UNKNOWN;
	}
	
	public int getMaxComputeUnits() {
		int maxComputeUnits = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_MAX_COMPUTE_UNITS);
		return maxComputeUnits;
	}
	
	public long getMaxWorkItemDimensions(){
        long maxWorkItemDimensions = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_MAX_WORK_ITEM_DIMENSIONS);
        return maxWorkItemDimensions;
	}
	
	public long[] getWorkItemSizes() {
		 long maxWorkItemSizes[] = OpenCLPlatform.openclUtils.getSizes(cl_device_id, CLConstants.DEVICE_MAX_WORK_ITEM_SIZES, (int) getMaxWorkItemDimensions());
		 return maxWorkItemSizes;
	}
	public long getMaxWorkGroupSize() {
	long maxWorkGroupSize = OpenCLPlatform.openclUtils.getSize(cl_device_id, CLConstants.DEVICE_MAX_WORK_GROUP_SIZE);
	return maxWorkGroupSize;
	}
	
	public long getMaxClockFrequency() {
	long maxClockFrequency = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_MAX_CLOCK_FREQUENCY);
	return maxClockFrequency;
	}
	
	public int getAddressBits() {
		int addressBits = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_ADDRESS_BITS);
		return addressBits;
	}
	
	public long getMaxMemAllocSize() {
	long maxMemAllocSize = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_MAX_MEM_ALLOC_SIZE);
	return maxMemAllocSize;
	}
	public long getGlobalMemSize() {
		long globalMemSize = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_GLOBAL_MEM_SIZE);
		return globalMemSize;
	}
	public boolean isErrorCorrectionSupport() {
	int errorCorrectionSupport = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_ERROR_CORRECTION_SUPPORT);
	return errorCorrectionSupport!=0;
	}
	public LocalMemType getLocalMemType() {
	int localMemType = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_LOCAL_MEM_TYPE);
	if(localMemType==1) {
		return LocalMemType.LOCAL;
	}else {
		return LocalMemType.GLOBAL;
	}
	}
	
	public long getLocalMemSize() {
	long localMemSize = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_LOCAL_MEM_SIZE);
	return localMemSize;
	}
	public long getMaxConstantBufferSize() {
	long maxConstantBufferSize = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_MAX_CONSTANT_BUFFER_SIZE);
	return maxConstantBufferSize;
	}
	
	public List<QueueProperty> getQueueProperties() {
	long queueProperties = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_QUEUE_ON_HOST_PROPERTIES);
	ArrayList<QueueProperty>qp=new ArrayList<>();
	  if(( queueProperties & CLConstants.QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE ) != 0)
		  qp.add(QueueProperty.QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE);
	  if(( queueProperties & CLConstants.QUEUE_PROFILING_ENABLE ) != 0)
		  qp.add(QueueProperty.QUEUE_PROFILING_ENABLE);
	return Collections.unmodifiableList( qp);
	}
	public boolean isImageSupport(){
		int imageSupport = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_IMAGE_SUPPORT);
		return imageSupport!=0;
	}
	
	public int getMaxReadImageArgs() {
	int maxReadImageArgs = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_MAX_READ_IMAGE_ARGS);
	return maxReadImageArgs;
	}
	public int getMaxWriteImageArgs(){
	 int maxWriteImageArgs = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_MAX_WRITE_IMAGE_ARGS);
	 return maxWriteImageArgs;
	}
	public List<FpConfig> getSingleFpConfig(){
	long singleFpConfig = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_SINGLE_FP_CONFIG);
	List<FpConfig>sfp=new ArrayList<>();
	
	 if ((singleFpConfig & CLConstants.FP_DENORM) != 0) sfp .add(FpConfig.FP_DENORM);
     if ((singleFpConfig & CLConstants.FP_INF_NAN) != 0) sfp .add(FpConfig.FP_INF_NAN);
     if ((singleFpConfig & CLConstants.FP_ROUND_TO_NEAREST) != 0) sfp .add(FpConfig.FP_ROUND_TO_NEAREST);
     if ((singleFpConfig & CLConstants.FP_ROUND_TO_ZERO) != 0) sfp .add(FpConfig.FP_ROUND_TO_ZERO);
     if ((singleFpConfig & CLConstants.FP_ROUND_TO_INF) != 0) sfp .add(FpConfig.FP_INF_NAN);
     if ((singleFpConfig & CLConstants.FP_FMA) != 0) sfp .add(FpConfig.FP_FMA);
     if ((singleFpConfig & CLConstants.FP_SOFT_FLOAT) != 0) sfp .add(FpConfig.FP_SOFT_FLOAT);
     if ((singleFpConfig & CLConstants.FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) != 0) sfp .add(FpConfig.FP_CORRECTLY_ROUNDED_DIVIDE_SQRT);
     return Collections.unmodifiableList(sfp);
	}
	
	public List<FpConfig> getDoubleFpConfig(){
		long singleFpConfig = OpenCLPlatform.openclUtils.getLong(cl_device_id, CLConstants.DEVICE_DOUBLE_FP_CONFIG);
		List<FpConfig>sfp=new ArrayList<>();
		
		 if ((singleFpConfig & CLConstants.FP_DENORM) != 0) sfp .add(FpConfig.FP_DENORM);
	     if ((singleFpConfig & CLConstants.FP_INF_NAN) != 0) sfp .add(FpConfig.FP_INF_NAN);
	     if ((singleFpConfig & CLConstants.FP_ROUND_TO_NEAREST) != 0) sfp .add(FpConfig.FP_ROUND_TO_NEAREST);
	     if ((singleFpConfig & CLConstants.FP_ROUND_TO_ZERO) != 0) sfp .add(FpConfig.FP_ROUND_TO_ZERO);
	     if ((singleFpConfig & CLConstants.FP_ROUND_TO_INF) != 0) sfp .add(FpConfig.FP_INF_NAN);
	     if ((singleFpConfig & CLConstants.FP_FMA) != 0) sfp .add(FpConfig.FP_FMA);
	     if ((singleFpConfig & CLConstants.FP_SOFT_FLOAT) != 0) sfp .add(FpConfig.FP_SOFT_FLOAT);
	     if ((singleFpConfig & CLConstants.FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) != 0) sfp .add(FpConfig.FP_CORRECTLY_ROUNDED_DIVIDE_SQRT);
	     return Collections.unmodifiableList(sfp);
		}
	
	public long getImage2dMaxWidth() {
		 long image2dMaxWidth = OpenCLPlatform.openclUtils.getSize(cl_device_id, CLConstants.DEVICE_IMAGE2D_MAX_WIDTH);
		 return image2dMaxWidth;
	}
	
	public long getImage2dMaxHeight() {
	 long image2dMaxHeight = OpenCLPlatform.openclUtils.getSize(cl_device_id, CLConstants.DEVICE_IMAGE2D_MAX_HEIGHT);
	 return image2dMaxHeight;
	}
	public long getImage3dMaxWidth() {
	 long image3dMaxWidth = OpenCLPlatform.openclUtils.getSize(cl_device_id, CLConstants.DEVICE_IMAGE3D_MAX_WIDTH);
	 return image3dMaxWidth;
	}
	public long getImage3dMaxHeight() {
	long image3dMaxHeight = OpenCLPlatform.openclUtils.getSize(cl_device_id, CLConstants.DEVICE_IMAGE3D_MAX_HEIGHT);
	return image3dMaxHeight;
	}
	public long getImage3dMaxDepth() {
		long image3dMaxDepth = OpenCLPlatform.openclUtils. getSize(cl_device_id, CLConstants.DEVICE_IMAGE3D_MAX_DEPTH);
		return image3dMaxDepth;
	}
	public int getPreferredVectorWidthChar() {
		int preferredVectorWidthChar = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_PREFERRED_VECTOR_WIDTH_CHAR);
		return preferredVectorWidthChar;
	}
	public int getPreferredVectorWidthShort() {
		 int preferredVectorWidthShort = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_PREFERRED_VECTOR_WIDTH_SHORT);
		 return preferredVectorWidthShort;
	}
	public int getPreferredVectorWidthInt() {
		int preferredVectorWidthInt = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_PREFERRED_VECTOR_WIDTH_INT);
		return preferredVectorWidthInt;
	}
	public int getPreferredVectorWidthLong() {
		int preferredVectorWidthLong = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_PREFERRED_VECTOR_WIDTH_LONG);
		return preferredVectorWidthLong;
	}
	public int getPreferredVectorWidthFloat() {
		 int preferredVectorWidthFloat = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT);
		 return preferredVectorWidthFloat;
	}
	public int getPreferredVectorWidthDouble() {
		int preferredVectorWidthDouble = OpenCLPlatform.openclUtils.getInt(cl_device_id, CLConstants.DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE);
		return preferredVectorWidthDouble;
	}

	@Override
	public String toString() {
	    return String.format(
	        "OpenCLDevice[%s | %s | %s | CUs=%d | Freq=%dMHz | Global=%.2fGB | MaxAlloc=%.2fGB | Local=%.2fKB | MaxWG=%d | MaxDim=%d | ECC=%s]",
	        getName(),
	        getVendor().replace(" Corporation", ""),
	        getDeviceType(),
	        getMaxComputeUnits(),
	        getMaxClockFrequency(),
	        getGlobalMemSize() / (1024.0 * 1024.0 * 1024.0),
	        getMaxMemAllocSize() / (1024.0 * 1024.0 * 1024.0),
	        getLocalMemSize() / 1024.0,
	        getMaxWorkGroupSize(),
	        getMaxWorkItemDimensions(),
	        isErrorCorrectionSupport() ? "ON" : "OFF"
	    );
	}
	
	public String toStringFull() {
		return "OpenCLDevice [getName()=" + getName() + ", getVendor()=" + getVendor() + ", getDriverVersion()="
				+ getDriverVersion() + ", getDeviceType()=" + getDeviceType() + ", getMaxComputeUnits()="
				+ getMaxComputeUnits() + ", getMaxWorkItemDimensions()=" + getMaxWorkItemDimensions()
				+ ", getWorkItemSizes()=" + Arrays.toString(getWorkItemSizes()) + ", getMaxWorkGroupSize()="
				+ getMaxWorkGroupSize() + ", getMaxClockFrequency()=" + getMaxClockFrequency() + ", getAddressBits()="
				+ getAddressBits() + ", getMaxMemAllocSize()=" + getMaxMemAllocSize() + ", getGlobalMemSize()="
				+ getGlobalMemSize() + ", isErrorCorrectionSupport()=" + isErrorCorrectionSupport()
				+ ", getLocalMemType()=" + getLocalMemType() + ", getLocalMemSize()=" + getLocalMemSize()
				+ ", getMaxConstantBufferSize()=" + getMaxConstantBufferSize() + ", getQueueProperties()="
				+ getQueueProperties() + ", isImageSupport()=" + isImageSupport() + ", getMaxReadImageArgs()="
				+ getMaxReadImageArgs() + ", getMaxWriteImageArgs()=" + getMaxWriteImageArgs()
				+ ", getSingleFpConfig()=" + getSingleFpConfig() + ", getDoubleFpConfig()=" + getDoubleFpConfig()
				+ ", getImage2dMaxWidth()=" + getImage2dMaxWidth() + ", getImage2dMaxHeight()=" + getImage2dMaxHeight()
				+ ", getImage3dMaxWidth()=" + getImage3dMaxWidth() + ", getImage3dMaxHeight()=" + getImage3dMaxHeight()
				+ ", getImage3dMaxDepth()=" + getImage3dMaxDepth() + ", getPreferredVectorWidthChar()="
				+ getPreferredVectorWidthChar() + ", getPreferredVectorWidthShort()=" + getPreferredVectorWidthShort()
				+ ", getPreferredVectorWidthInt()=" + getPreferredVectorWidthInt() + ", getPreferredVectorWidthLong()="
				+ getPreferredVectorWidthLong() + ", getPreferredVectorWidthFloat()=" + getPreferredVectorWidthFloat()
				+ ", getPreferredVectorWidthDouble()=" + getPreferredVectorWidthDouble() + "]";
	}

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

	public OpenCLDeviceReleaser(CLDeviceId resource) {
		super(resource);
	}

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