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 MCMBufferBuilder {
	
	private long size;
	private Pointer data;
	private long datasize;
	private boolean readonly;
	private boolean local;
	
	private MCMContext context;
	
	public MCMBufferBuilder(MCMContext context) {
		this.size = -1;
		this.datasize = -1;
		this.data = null;
		this.context = context;
		this.readonly = false;
		this.local = false;
	}
	
	public MCMBufferBuilder constant() {
		this.readonly = true;
		return this;
	}
	
	public MCMBufferBuilder local() {
		this.local = true;
		return this;
	}
	
	public MCMBufferBuilder size(long n) {
		this.size = n;
		return this;
	}
	
	public MCMBufferBuilder size(MCMType type) {
		return size(type.size);
	}
	
	public MCMBufferBuilder size(long n, MCMType type) {
		return size(n * type.size);
	}
	
	public MCMBufferBuilder size(long n, Class<? extends Struct> type) {
		return size(n * SizeofStruct.sizeof(type));
	}
	
	public MCMBufferBuilder size(Buffer data) {
		return size(BuffersUtils.sizeOf(data));
	}
	
	public MCMBufferBuilder data(Pointer data) {
		if (local) {
			throw new RuntimeException("Local buffers must remain uninitialized");
		}
		
		if (this.data != null) {
			throw new RuntimeException("Data are already associated to this buffer");
		}
		
		this.data = data;
		return this;
	}
	
	public MCMBufferBuilder data(long datasize, Pointer data) {
		if (local) { throw new RuntimeException("Local buffers must remain uninitialized"); }
		this.datasize = datasize;
		this.data = data;
		return this;
	}
	
	public MCMBufferBuilder data(byte... data) {
		return data(data.length * Sizeof.cl_char, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(char... data) {
		return data(data.length * Sizeof.cl_char, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(short... data) {
		return data(data.length * Sizeof.cl_short, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(int... data) {
		return data(data.length * Sizeof.cl_int, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(long... data) {
		return data(data.length * Sizeof.cl_long, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(float... data) {
		return data(data.length * Sizeof.cl_float, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(double... data) {
		return data(data.length * Sizeof.cl_double, Pointer.to(data));
	}
	
	public MCMBufferBuilder data(Buffer data) {
		return data(BuffersUtils.sizeOf(data), Pointer.to(data));
	}
	
	public MCMBufferBuilder data(NativePointerObject data) {
		return data(Pointer.to(data));
	}
	
	public MCMBufferBuilder data(NativePointerObject... data) {
		return data(Pointer.to(data));
	}
	
	public MCMBufferBuilder data(Class <? extends Struct> type, Struct... data) {
		long elemSize = SizeofStruct.sizeof(type);
		ByteBuffer temp = Buffers.allocateBuffer(data);
		Buffers.writeToBuffer(temp, data);
		return data(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 MCMBufferBuilder Using(Struct... data) {
		long elemSize = SizeofStruct.sizeof(data[0].getClass());
		ByteBuffer temp = Buffers.allocateBuffer(data);
		Buffers.writeToBuffer(temp, data);
		return data(data.length * elemSize, Pointer.to(temp));
	}
	
	public MCMMem get() {
		checkCoherency();
		
		long flags = 0;
		cl_mem mem = null;
		long memsize = (size > 0 ? size : datasize);
		
		if (readonly) {
			flags |= CL.CL_MEM_READ_ONLY;
		} else {
			flags |= CL.CL_MEM_READ_WRITE;
		}
		
		if (data != null) {
			flags |= CL.CL_MEM_COPY_HOST_PTR;
		}
		
		if (!local) {
			mem = MCMHelpers.createMem(context.getContext(), flags, memsize, data);
		}
		
		MCMMem buffer = new MCMMem(mem);
		buffer.setLocal(local);
		buffer.setReadOnly(readonly);
		
		return buffer;
	}
	
	private void checkCoherency() {
		if (size < 0) {
			throw new RuntimeException("A buffer cannot have a negative size: " + size);
		}
		
		if (local && data != null) {
			throw new RuntimeException("Local buffers cannot be initialized using CPU data");
		}
		
		if (datasize > 0 && size > datasize) {
			throw new RuntimeException("The buffer size is greater than the input data size");
		}
	}
	
}
