package com.mbien.opencl;

import com.sun.gluegen.runtime.BufferFactory;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;

import static com.mbien.opencl.CLException.*;
import static com.mbien.opencl.CL.*;

/**
 *
 * @author Michael Bien
 */
public class CLBuffer<B extends Buffer> implements CLResource {

    public final B buffer;
    public final long ID;
    
    private final CLContext context;
    private final CL cl;

    CLBuffer(CLContext context, B directBuffer, int flags) {
        this(context, directBuffer, 0, flags);
    }

    CLBuffer(CLContext context, B directBuffer, int glBuffer, int flags) {

        if(!directBuffer.isDirect())
            throw new IllegalArgumentException("buffer is not a direct buffer");

        this.buffer = directBuffer;
        this.context = context;
        this.cl = context.cl;

        int[] intArray = new int[1];

        if(glBuffer == 0) {
            this.ID = cl.clCreateBuffer(context.ID, flags,
                    sizeOfBufferElem(directBuffer)*directBuffer.capacity(), null, intArray, 0);
        }else{
            CLGLI clgli = (CLGLI)cl;
            this.ID = clgli.clCreateFromGLBuffer(context.ID, flags, glBuffer, intArray, 0);
        }
        checkForError(intArray[0], "can not create cl buffer");

    }

    public void release() {
        int ret = cl.clReleaseMemObject(ID);
        context.onBufferReleased(this);
        checkForError(ret, "can not release mem object");
    }

    //stolen from JOGL project... think about merging
    private final int sizeOfBufferElem(Buffer buffer) {

        if (buffer instanceof ByteBuffer) {
            return BufferFactory.SIZEOF_BYTE;
        } else if (buffer instanceof IntBuffer) {
            return BufferFactory.SIZEOF_INT;
        } else if (buffer instanceof ShortBuffer) {
            return BufferFactory.SIZEOF_SHORT;
        } else if (buffer instanceof FloatBuffer) {
            return BufferFactory.SIZEOF_FLOAT;
        } else if (buffer instanceof DoubleBuffer) {
            return BufferFactory.SIZEOF_DOUBLE;
        }
        throw new RuntimeException("Unexpected buffer type " + buffer.getClass().getName());
    }

    int getSizeInBytes() {
        return sizeOfBufferElem(buffer)*buffer.capacity();
    }


    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final CLBuffer<?> other = (CLBuffer<?>) obj;
        if (this.buffer != other.buffer && (this.buffer == null || !this.buffer.equals(other.buffer))) {
            return false;
        }
        if (this.context.ID != other.context.ID) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 29 * hash + (this.buffer != null ? this.buffer.hashCode() : 0);
        hash = 29 * hash + (int) (this.context.ID ^ (this.context.ID >>> 32));
        return hash;
    }

    /**
     * Memory settings for configuring CLBuffers.
     */
    public enum Mem {

        /**
         * This flag specifies that the memory object will be read and
         * written by a kernel.
         */
        READ_WRITE(CL_MEM_READ_WRITE),

        /**
         * This flags specifies that the memory object will be written
         * but not read by a kernel.
         * Reading from a buffer or image object created with WRITE_ONLY
         * inside a kernel is undefined.
         */
        WRITE_ONLY(CL_MEM_WRITE_ONLY),

        /**
         * This flag specifies that the memory object is a read-only memory
         * object when used inside a kernel. Writing to a buffer or image object
         * created withREAD_ONLY inside a kernel is undefined.
         */
        READ_ONLY(CL_MEM_READ_ONLY);

        /**
         * If specified, it indicates that the application wants the OpenCL
         * implementation to use memory referenced by host_ptr as the storage
         * bits for the memory object. OpenCL implementations are allowed
         * to cache the buffer contents pointed to by host_ptr in device memory.
         * This cached copy can be used when kernels are executed on a device.
         */
//        USE_HOST_PTR(CL_MEM_USE_HOST_PTR),

//        ALLOC_HOST_PTR(CL_MEM_ALLOC_HOST_PTR), // this is the default in java world anyway

        /**
         * If CL_MEM_COPY_HOST_PTR specified, it indicates that the application
         * wants the OpenCL implementation to allocate memory for the memory object
         * and copy the data from memory referenced by host_ptr.<br/>
         * COPY_HOST_PTR and USE_HOST_PTR are mutually exclusive.
         */
//        COPY_HOST_PTR(CL_MEM_COPY_HOST_PTR);

        /**
         * Value of wrapped OpenCL flag.
         */
        public final int CONFIG;

        private Mem(int config) {
            this.CONFIG = config;
        }

        public static Mem valueOf(int bufferFlag) {
            switch(bufferFlag) {
                case(CL_MEM_READ_WRITE):
                    return READ_WRITE;
                case(CL_MEM_READ_ONLY):
                    return READ_ONLY;
//                case(CL_MEM_USE_HOST_PTR):
//                    return USE_HOST_PTR;
//                case(CL_MEM_ALLOC_HOST_PTR):
//                    return ALLOC_HOST_PTR;
//                case(CL_MEM_COPY_HOST_PTR):
//                    return COPY_HOST_PTR;
            }
            return null;
        }

        static int flagsToInt(Mem[] flags) {
            int clFlags = 0;
            if(flags != null) {
                for (int i = 0; i < flags.length; i++) {
                    clFlags |= flags[i].CONFIG;
                }
            }
            if(clFlags == 0)
                clFlags = CL_MEM_READ_WRITE;
            return clFlags;
        }

    }

}