package mcmas.core;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import mcmas.utils.BuffersUtils;

import org.jocl.CL;
import org.jocl.NativePointerObject;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_mem;
import org.jocl.struct.Buffers;
import org.jocl.struct.SizeofStruct;
import org.jocl.struct.Struct;

public class MCMMemBuilder {
	
	private long size;
	private Pointer data;
	private long datasize;
	private boolean readonly;
	private boolean local;
	
	private MCMContext context;
	
	public MCMMemBuilder(MCMContext context) {
		this.size = -1;
		this.datasize = -1;
		this.data = null;
		this.context = context;
		this.readonly = false;
		this.local = false;
	}
	
	public MCMMemBuilder Constant() {
		this.readonly = true;
		return this;
	}
	
	public MCMMemBuilder Local() {
		this.local = true;
		return this;
	}
	
	public MCMMemBuilder Size(long n) {
		this.size = n;
		checkSizeCoherency();
		return this;
	}
	
	public MCMMemBuilder Size(MCMType type) {
		return Size(type.size);
	}
	
	public MCMMemBuilder Size(long n, MCMType type) {
		return Size(n * type.size);
	}
	
	public MCMMemBuilder Size(long n, Class<? extends Struct> type) {
		return Size(n * SizeofStruct.sizeof(type));
	}
	
	public MCMMemBuilder Size(Buffer data) {
		return Size(BuffersUtils.sizeOf(data));
	}
	
	public MCMMemBuilder Using(Pointer data) {
		if (local) { throw new RuntimeException("Local buffers must remain uninitialized"); }
		this.data = data;
		return this;
	}
	
	public MCMMemBuilder Using(long datasize, Pointer data) {
		if (local) { throw new RuntimeException("Local buffers must remain uninitialized"); }
		this.datasize = datasize;
		this.data = data;
		return this;
	}
	
	public MCMMemBuilder Using(byte... data) {
		return Using(data.length * Sizeof.cl_char, Pointer.to(data));
	}
	
	public MCMMemBuilder Using(char... data) {
		return Using(data.length * Sizeof.cl_char, Pointer.to(data));
	}
	
	public MCMMemBuilder Using(short... data) {
		return Using(data.length * Sizeof.cl_short, Pointer.to(data));
	}
	
	public MCMMemBuilder Using(int... data) {
		return Using(data.length * Sizeof.cl_int, Pointer.to(data));
	}
	
	public MCMMemBuilder Using(long... data) {
		return Using(data.length * Sizeof.cl_long, Pointer.to(data));
	}
	
	public MCMMemBuilder Using(float... data) {
		return Using(data.length * Sizeof.cl_float, Pointer.to(data));
	}
	
	public MCMMemBuilder Using(double... data) {
		return Using(data.length * Sizeof.cl_double, Pointer.to(data));
	}
	
	/*
	public OCLMemBuilder Using(Buffer data) {
		return Using(Pointer.to(data));
	}*/
	
	public MCMMemBuilder Using(Buffer data) {
		return Using(BuffersUtils.sizeOf(data), Pointer.to(data));
	}
	
	/*public OCLMemBuilder Using(ByteBuffer data) {
		return Using(data.capacity(), Pointer.to(data));
	}
	
	public OCLMemBuilder Using(CharBuffer data) {
		return Using(data.capacity() * Sizeof.cl_char, Pointer.to(data));
	}
	
	public OCLMemBuilder Using(ShortBuffer data) {
		return Using(data.capacity() * Sizeof.cl_short, Pointer.to(data));
	}
	
	public OCLMemBuilder Using(IntBuffer data) {
		return Using(data.capacity() * Sizeof.cl_int, Pointer.to(data));
	}
	
	public OCLMemBuilder Using(LongBuffer data) {
		return Using(data.capacity() * Sizeof.cl_long, Pointer.to(data));
	}

	public OCLMemBuilder Using(FloatBuffer data) {
		return Using(data.capacity() * Sizeof.cl_float, Pointer.to(data));
	}
	
	public OCLMemBuilder Using(DoubleBuffer data) {
		return Using(data.capacity() * Sizeof.cl_double, Pointer.to(data));
	}*/
	
	public MCMMemBuilder Using(NativePointerObject data) {
		return Using(Pointer.to(data));
	}
	
	public MCMMemBuilder Using(NativePointerObject... data) {
		return Using(Pointer.to(data));
	}
	
	public MCMMemBuilder Using(Class <? extends Struct> type, Struct... data) {
		long elemSize = SizeofStruct.sizeof(type);
		ByteBuffer temp = Buffers.allocateBuffer(data);
		Buffers.writeToBuffer(temp, data);
		return Using(data.length * elemSize, Pointer.to(temp));
	}
	
	/*public OCLMemBuilder Using(Struct data) {
		long elemSize = SizeofStruct.sizeof(data.getClass());
		ByteBuffer temp = Buffers.allocateBuffer(data);
		Buffers.writeToBuffer(temp, data);
		return Using( elemSize, Pointer.to(temp));
	}*/
	
	public MCMMemBuilder Using(Struct... data) {
		long elemSize = SizeofStruct.sizeof(data[0].getClass());
		ByteBuffer temp = Buffers.allocateBuffer(data);
		Buffers.writeToBuffer(temp, data);
		return Using(data.length * elemSize, Pointer.to(temp));
	}
	
	public MCMMem b() {
		long flags = 0;
		cl_mem mem = null;
		long asize = (size > 0 ? size : datasize);
		if (readonly) { flags |= CL.CL_MEM_READ_ONLY; }
		if (asize < 0) { throw new RuntimeException("No size specified"); }
		if (data != null) { flags |= CL.CL_MEM_COPY_HOST_PTR; }
		
		// Actually allocate memory if this is not a local buffer
		// (in which case mem MUST be NULL)
		if (! local) {
			mem = MCMHelpers.createMem(context.getContext(), flags, asize, data);
		}
		
		MCMMem buffer = new MCMMem(mem);
		buffer.setLocal(local);
		buffer.setReadOnly(readonly);
		context.register(buffer);
		
		return buffer;
	}
	
	private void checkSizeCoherency() {
		if (datasize > 0 && size > 0 && datasize > size) {
			throw new RuntimeException("Size is superior to the size of the data used for initialization");
		}
	}
	
}
