#parse("main/Header.vm")
package com.nativelibs4java.opencl;
import static com.nativelibs4java.opencl.CLException.error;
import static com.nativelibs4java.opencl.JavaCL.CL;
import static com.nativelibs4java.opencl.JavaCL.check;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_FALSE;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_IMAGE_ELEMENT_SIZE;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_IMAGE_FORMAT;
import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_TRUE;
import static com.nativelibs4java.util.NIOUtils.directCopy;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import com.nativelibs4java.opencl.library.cl_image_format;
import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_event;
import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_mem;
import com.nativelibs4java.util.NIOUtils;

import com.nativelibs4java.util.Pair;
import org.bridj.*;
import static org.bridj.Pointer.*;


/**
 * OpenCL Image Memory Object.<br>
 * An image object is used to store a two- or three- dimensional texture, frame-buffer or image<br>
 * An image object is used to represent a buffer that can be used as a texture or a frame-buffer. The elements of an image object are selected from a list of predefined image formats.
 * @author Oliveir Chafik
 */
public abstract class CLImage extends CLMem {

	CLImageFormat format;
	CLImage(CLContext context, long entityPeer, CLImageFormat format) {
        super(context, -1, entityPeer);
		this.format = format;
	}

	protected abstract long[] getDimensions();

	/**
	 * Return image format descriptor specified when image is created with CLContext.create{Input|Output|InputOutput}{2D|3D}.
	 */
	@InfoName("CL_IMAGE_FORMAT")
	public CLImageFormat getFormat() {
		if (format == null) {
			format = new CLImageFormat(new cl_image_format(infos.getMemory(getEntity(), CL_IMAGE_FORMAT)));
		}
		return format;
	}

	/**
	 * Return size of each element of the image memory object given by image. <br>
	 * An element is made up of n channels. The value of n is given in cl_image_format descriptor.
	 */
	@InfoName("CL_IMAGE_ELEMENT_SIZE")
	public long getElementSize() {
		return infos.getIntOrLong(getEntity(), CL_IMAGE_ELEMENT_SIZE);
	}

	/**
#documentCallsFunction("clEnqueueReadImage")
     */
    protected CLEvent read(CLQueue queue, Pointer<SizeT> origin, Pointer<SizeT> region, long rowPitch, long slicePitch, Buffer out, boolean blocking, CLEvent... eventsToWaitFor) {
		return read(queue, origin, region, rowPitch, slicePitch, pointerToBuffer(out), blocking, eventsToWaitFor);
	}
	/**
#documentCallsFunction("clEnqueueReadImage")
     */
    protected CLEvent read(CLQueue queue, Pointer<SizeT> origin, Pointer<SizeT> region, long rowPitch, long slicePitch, Pointer<?> out, boolean blocking, CLEvent... eventsToWaitFor) {
		/*if (!out.isDirect()) {

		}*/
		#declareReusablePtrsAndEventsInOutBlockable()
        error(CL.clEnqueueReadImage(
        	queue.getEntity(), 
        	getEntity(),
			blocking ? CL_TRUE : CL_FALSE,
			getPeer(origin),
			getPeer(region),
			rowPitch,
			slicePitch,
			getPeer(out),
			#eventsInOutArgsRaw()
		));
		#returnEventOut("queue")
	}

	/**
#documentCallsFunction("clEnqueueWriteImage")
     */
    protected CLEvent write(CLQueue queue, Pointer<SizeT> origin, Pointer<SizeT> region, long rowPitch, long slicePitch, Buffer in, boolean blocking, CLEvent... eventsToWaitFor) {
		return write(queue, origin, region, rowPitch, slicePitch, pointerToBuffer(in), blocking, eventsToWaitFor);
	}
	/**
#documentCallsFunction("clEnqueueWriteImage")
     */
    protected CLEvent write(CLQueue queue, Pointer<SizeT> origin, Pointer<SizeT> region, long rowPitch, long slicePitch, Pointer<?> in, boolean blocking, CLEvent... eventsToWaitFor) {
		#declareReusablePtrsAndEventsInOutBlockable()
        error(CL.clEnqueueWriteImage(
        	queue.getEntity(),
        	getEntity(),
			blocking ? CL_TRUE : CL_FALSE,
			getPeer(origin),
			getPeer(region),
			rowPitch,
			slicePitch,
			getPeer(in),
			#eventsInOutArgsRaw()
		));
		CLEvent evt = #eventOutWrapper("queue");

		if (!blocking) {
			final Pointer<?> toHold = in;
			evt.invokeUponCompletion(new Runnable() {
				public void run() {
					// Make sure the GC held a reference to directData until the write was completed !
					toHold.order();
				}
			});
		}

		return evt;
	}

    /**
#documentCallsFunction("clEnqueueFillImage")
     * @param queue
     * @param queue Queue on which to enqueue this fill buffer command.
     * @param color Color components to fill the buffer with.
#documentEventsToWaitForAndReturn()
     */
    public CLEvent fillImage(CLQueue queue, Object color, CLEvent... eventsToWaitFor) {
    	long[] region = getDimensions();
    	long[] origin = new long[region.length];
    	return fillImage(queue, color, origin, region, eventsToWaitFor);
    }

    /**
#documentCallsFunction("clEnqueueFillImage")
     * @param queue
     * @param queue Queue on which to enqueue this fill buffer command.
     * @param color Color components to fill the buffer with.
     * @param origin Origin point.
     * @param region Size of the region to fill.
#documentEventsToWaitForAndReturn()
     */
    public CLEvent fillImage(CLQueue queue, Object color, long[] origin, long[] region, CLEvent... eventsToWaitFor) {
    	context.getPlatform().requireMinVersionValue("clEnqueueFillImage", 1.2);
		Pointer<?> pColor;
		if (color instanceof int[]) {
			pColor = pointerToInts((int[]) color);
		} else if (color instanceof float[]) {
			pColor = pointerToFloats((float[]) color);
		} else {
			throw new IllegalArgumentException("Color should be an int[] or a float[] with 4 elements.");
		}
		check(pColor.getValidElements() == 4, "Color should have 4 elements.");

		#declareReusablePtrsAndEventsInOut()
		error(CL.clEnqueueFillImage(
			queue.getEntity(),
			getEntity(),
			getPeer(pColor),
			getPeer(writeOrigin(origin, ptrs.sizeT3_1)),
			getPeer(writeRegion(region, ptrs.sizeT3_2)),
			#eventsInOutArgsRaw()
		));
        #returnEventOut("queue")
    }

	// clEnqueueFillImage (	cl_command_queue command_queue,
 // 	cl_mem image,
 // 	const void *fill_color,
 // 	const size_t *origin,
 // 	const size_t *region,
 // 	cl_uint num_events_in_wait_list,
 // 	const cl_event *event_wait_list,
 // 	cl_event *event)

    protected Pair<ByteBuffer, CLEvent> map(CLQueue queue, MapFlags flags,
            Pointer<SizeT> offset3, Pointer<SizeT> length3,
            Long imageRowPitch,
            Long imageSlicePitch,
            boolean blocking, CLEvent... eventsToWaitFor)
    {
        if (flags == MapFlags.WriteInvalidateRegion) {
            context.getPlatform().requireMinVersionValue("CL_MAP_WRITE_INVALIDATE_REGION", 1.2);
        }
		#declareReusablePtrsAndEventsInOutBlockable()
		#declarePErr()
        long mappedPeer = CL.clEnqueueMapImage(
            queue.getEntity(), 
            getEntity(), 
            blocking ? CL_TRUE : CL_FALSE,
            flags.value(),
			getPeer(offset3),
            getPeer(length3),
            imageRowPitch == null ? 0 : getPeer(ptrs.sizeT3_1.pointerToSizeTs((long)imageRowPitch)),
            imageSlicePitch == null ? 0 : getPeer(ptrs.sizeT3_2.pointerToSizeTs((long)imageSlicePitch)),
			#eventsInOutArgsRaw(),
			getPeer(pErr)
		);
		#checkPErr()
        return new Pair<ByteBuffer, CLEvent>(
			pointerToAddress(mappedPeer).getByteBuffer(getByteCount()),
			#eventOutWrapper("queue")
		);
    }

    /**
#documentCallsFunction("clEnqueueUnmapMemObject")
     * see {@link CLImage2D#map(com.nativelibs4java.opencl.CLQueue, com.nativelibs4java.opencl.CLMem.MapFlags, com.nativelibs4java.opencl.CLEvent[]) }
     * see {@link CLImage3D#map(com.nativelibs4java.opencl.CLQueue, com.nativelibs4java.opencl.CLMem.MapFlags, com.nativelibs4java.opencl.CLEvent[]) }
     * @param queue
     * @param buffer
#documentEventsToWaitForAndReturn()
     */
    public CLEvent unmap(CLQueue queue, ByteBuffer buffer, CLEvent... eventsToWaitFor) {
        #declareReusablePtrsAndEventsInOut()
        Pointer<?> pBuffer = pointerToBuffer(buffer);
        error(CL.clEnqueueUnmapMemObject(queue.getEntity(), getEntity(), getPeer(pBuffer), #eventsInOutArgsRaw()));
		#returnEventOut("queue")
    }

    protected abstract Pointer<SizeT> writeOrigin(long[] origin, ReusablePointer out);
    protected abstract Pointer<SizeT> writeRegion(long[] region, ReusablePointer out);

    /**
#documentCallsFunction("clEnqueueCopyImage")
     * @param queue
#documentEventsToWaitForAndReturn()
     */
    public CLEvent copyTo(CLQueue queue, CLImage destination, CLEvent... eventsToWaitFor) {
        long[] region = getDimensions();
        long[] origin = new long[region.length];
        return copyTo(queue, destination, origin, origin, region, eventsToWaitFor);
    }

    /**
#documentCallsFunction("clEnqueueCopyImage")
     * @param queue
#documentEventsToWaitForAndReturn()
     */
    public CLEvent copyTo(CLQueue queue, CLImage destination, long[] sourceOrigin, long[] destinationOrigin, long[] region, CLEvent... eventsToWaitFor) {
        #declareReusablePtrsAndEventsInOut()
        error(CL.clEnqueueCopyImage(
            queue.getEntity(),
            getEntity(),
            destination.getEntity(),
            getPeer(writeOrigin(sourceOrigin, ptrs.sizeT3_1)),
            getPeer(writeOrigin(destinationOrigin, ptrs.sizeT3_2)),
            getPeer(writeRegion(region, ptrs.sizeT3_3)),
            #eventsInOutArgsRaw()));
		#returnEventOut("queue")
    }
}
