package mcmas.core;

import java.nio.Buffer;

import mcmas.utils.Objects7;

import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.cl_context;
import org.jocl.cl_device_id;
import org.jocl.cl_image_format;
import org.jocl.cl_platform_id;


public class MCMContext extends MCMObject {
	
	private final cl_context context;
	//private final static Logger logger = LoggerFactory.getLogger(OCLContext.class);
	
	/**
	 * Create a new context using the first available hardware among
	 * default devices types.
	 */
	public MCMContext() {
		this(getDefaultContextTypes());
	}
	
	/**
	 * Create a new context using the first available hardware among
	 * indicated device types.
	 * 
	 * @param deviceTypes device types to search for
	 */
	public MCMContext(long... deviceTypes) {
		MCMPlatform[] platforms = MCMPlatform.getPlatforms();
		
		if (platforms.length <= 0) {
			throw new RuntimeException("No OpenCL platform found");
		}
		
		this.context = MCMHelpers.createContextFromType(platforms[0].getId(), deviceTypes);
		
		if (context == null) {
			throw new RuntimeException("No OpenCL device found");
		}
	}
	
	/**
	 * Create a new context using the indicated platform.
	 * 
	 * @param platform platform to consider
	 */
	public MCMContext(MCMPlatform platform) {
		this(platform, getDefaultContextTypes());
	}
	
	/**
	 * Create a new context using the indicated platform and device types.
	 * 
	 * @param platform platform to consider
	 * @param deviceTypes device types to search for
	 */
	public MCMContext(MCMPlatform platform, long... deviceTypes) {
		platform = Objects7.requireNonNull(platform, "Can't create an OpenCL context with a null platform");
		
		System.out.println("platform vendor : " + platform.getInfoString(CL.CL_PLATFORM_VENDOR));
		System.out.println("platform name : " + platform.getInfoString(CL.CL_PLATFORM_NAME));
		
		this.context = MCMHelpers.createContextFromType(platform.getId(), deviceTypes);
		
		if (context == null) {
			throw new RuntimeException("No OpenCL device found");
		}
	}
	
	/**
	 * Return a list of default device types to test when nothing
	 * is indicated at the MCMContext creation.
	 * 
	 * The default list indicates to use these kind of hardware in this order:
	 * GPU, Accelerators (such as Xeon Phi) and CPU.
	 * 
	 * It can be overriden using the MCM_CONTEXT_TYPE environment variable,
	 * which admits the following values: "CPU", "GPU", "ACCELERATOR", "ALL".
	 * 
	 * "ALL" considers all the devices available on the system, but the
	 * order is not garanteed (even for the same type of device) and depends
	 * of the installed runtimes.
	 * 
	 * @return a list of context type to try.
	 */
	public static long[] getDefaultContextTypes() {
		String context_type = System.getenv("MCM_CONTEXT_TYPE");
		
		if ("CPU".equals(context_type)) {
			return new long[] { CL.CL_DEVICE_TYPE_CPU };
		} else if ("GPU".equals(context_type)) {
			return new long[] { CL.CL_DEVICE_TYPE_GPU };
		} else if ("ACCELERATOR".equals(context_type)) {
			return new long[] { CL.CL_DEVICE_TYPE_ACCELERATOR };
		} else if ("ALL".equals(context_type)) {
			return new long[] { CL.CL_DEVICE_TYPE_ALL };
		} else {
			return new long[] {
					CL.CL_DEVICE_TYPE_GPU,
					CL.CL_DEVICE_TYPE_ACCELERATOR,
					CL.CL_DEVICE_TYPE_CPU
			};
		}
	}
	
	public cl_context getContext() {
		return context;
	}
	
	public MCMPlatform[] getPlatforms() {
		cl_platform_id[] platform_ids = MCMHelpers.getPlatforms();
		MCMPlatform[] platforms = new MCMPlatform[platform_ids.length];
		
		for (int i = 0; i < platform_ids.length; i++) {
			platforms[i] = new MCMPlatform(platform_ids[i]);
		}
		
		return platforms;
	}
	
	public MCMDevice[] getDevices() {
		cl_device_id[] device_ids = MCMHelpers.getDevices(context);
		MCMDevice[] devices = new MCMDevice[device_ids.length];
		for (int i = 0; i < device_ids.length; i++) {
			devices[i] = new MCMDevice(device_ids[i]);
		}
		return devices;
	}
	
	public MCMCommandQueue createCommandQueue(MCMCommandQueueProperty... properties) {
		MCMCommandQueue q = new MCMCommandQueue(this, properties);
		this.register(q);
		return q;
	}
	
	public MCMCommandQueue createCommandQueue(MCMDevice device, MCMCommandQueueProperty... properties) {
		MCMCommandQueue q = new MCMCommandQueue(this, device, properties);
		this.register(q);
		return q;
	}
	
	public MCMProgram createProgram(String source, String... options) {
		MCMProgram p = new MCMProgram(this, source, options);
		this.register(p);
		return p;
	}
	
	public MCMProgram createProgram(String source, MCMProgramOptions options) {
		MCMProgram p = new MCMProgram(this, source, options.toString());
		this.register(p);
		return p;
	}
	
	public MCMProgram createProgramFromFile(String file, String... options) {
		MCMProgram p = new MCMProgram(this, MCMUtils.readFileAsString(file), options);
		this.register(p);
		return p;
	}
	
	public MCMProgram createProgramFromFile(String file, MCMProgramOptions options) {
		MCMProgram p = new MCMProgram(this, MCMUtils.readFileAsString(file), options.toString());
		this.register(p);
		return p;
	}
	
	public MCMMemBuilder newBuffer() {
		return new MCMMemBuilder(this);
	}
	
	public MCMBufferBuilder newBuffer2() {
		return new MCMBufferBuilder(this);
	}
	
	@Deprecated
	public MCMMem createImage2d(long flags, int channelType, int width, int height) {
		cl_image_format[] format = new cl_image_format[1];
		format[0].image_channel_order = CL.CL_RGBA;
		format[0].image_channel_data_type = channelType;
		
		return new MCMMem(CL.clCreateImage2D(context, flags, format, width, height, 0, null, null));
	}
	
	@Deprecated
	public MCMMem createImage2d(long flags, int channelType, int width, int height, Buffer data) {
		cl_image_format[] format = new cl_image_format[1];
		format[0].image_channel_order = CL.CL_RGBA;
		format[0].image_channel_data_type = channelType;
		
		return new MCMMem(CL.clCreateImage2D(context, flags, format, width, height, 0, Pointer.to(data), null));
	}
	
	@Deprecated
	public MCMMem createImage2d(long flags, int channelType, int width, int height, Pointer data) {
		cl_image_format[] format = new cl_image_format[1];
		format[0].image_channel_order = CL.CL_RGBA;
		format[0].image_channel_data_type = channelType;
		
		return new MCMMem(CL.clCreateImage2D(context, flags, format, width, height, 0, data, null));
	}
	
	@Override
	protected void releaseImpl() {
		CL.clReleaseContext(context);
	}
	
}
