/*
 * Copyright (c) 2009-2016 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.opencl;

import com.jme3.math.ColorRGBA;
import java.nio.ByteBuffer;
import java.util.Objects;

/**
 * Wrapper for an OpenCL image.
 * <br>
 * An image object is similar to a {@link Buffer}, but with a specific element
 * format and buffer structure.
 * <br>
 * The image is specified by the {@link ImageDescriptor}, specifying 
 * the extend and dimension of the image, and {@link ImageFormat}, specifying
 * the type of each pixel.
 * <br>
 * An image is created from scratch using 
 * {@link Context#createImage(com.jme3.opencl.MemoryAccess, com.jme3.opencl.Image.ImageFormat, com.jme3.opencl.Image.ImageDescriptor) }
 * or from OpenGL by
 * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) }
 * (and alternative versions).
 * 
 * <p>
 * Most methods take long arrays as input: {@code long[] origin} and {@code long[] region}.
 * Both are arrays of length 3.
 * <br>
 * <b>origin</b> defines the (x, y, z) offset in pixels in the 1D, 2D or 3D
 * image, the (x, y) offset and the image index in the 2D image array or the (x)
 * offset and the image index in the 1D image array. If image is a 2D image
 * object, origin[2] must be 0. If image is a 1D image or 1D image buffer
 * object, origin[1] and origin[2] must be 0. If image is a 1D image array
 * object, origin[2] must be 0. If image is a 1D image array object, origin[1]
 * describes the image index in the 1D image array. If image is a 2D image array
 * object, origin[2] describes the image index in the 2D image array.
 * <br>
 * <b>region</b> defines the (width, height, depth) in pixels of the 1D, 2D or
 * 3D rectangle, the (width, height) in pixels of the 2D rectangle and the
 * number of images of a 2D image array or the (width) in pixels of the 1D
 * rectangle and the number of images of a 1D image array. If image is a 2D
 * image object, region[2] must be 1. If image is a 1D image or 1D image buffer
 * object, region[1] and region[2] must be 1. If image is a 1D image array
 * object, region[2] must be 1. The values in region cannot be 0.
 *
 * @author shaman
 */
public abstract class Image extends AbstractOpenCLObject {
    
    /**
     * {@code ImageChannelType} describes the size of the channel data type.
     */
    public static enum ImageChannelType {
        SNORM_INT8,
        SNORM_INT16,
        UNORM_INT8,
        UNORM_INT16,
        UNORM_SHORT_565,
        UNORM_SHORT_555,
        UNORM_INT_101010,
        SIGNED_INT8,
        SIGNED_INT16,
        SIGNED_INT32,
        UNSIGNED_INT8,
        UNSIGNED_INT16,
        UNSIGNED_INT32,
        HALF_FLOAT,
        FLOAT
    }
    
    /**
     * {@code ImageChannelOrder} specifies the number of channels and the channel layout i.e. the
memory layout in which channels are stored in the image.
     */
    public static enum ImageChannelOrder {
        R, Rx, A,
        INTENSITY,
        LUMINANCE,
        RG, RGx, RA,
        RGB, RGBx,
        RGBA,
        ARGB, BGRA
    }

    /**
     * Describes the image format, consisting of 
     * {@link ImageChannelOrder} and {@link ImageChannelType}.
     */
    public static class ImageFormat { //Struct
        public ImageChannelOrder channelOrder;
        public ImageChannelType channelType;

        public ImageFormat() {
        }

        public ImageFormat(ImageChannelOrder channelOrder, ImageChannelType channelType) {
            this.channelOrder = channelOrder;
            this.channelType = channelType;
        }

        @Override
        public String toString() {
            return "ImageFormat{" + "channelOrder=" + channelOrder + ", channelType=" + channelType + '}';
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 61 * hash + Objects.hashCode(this.channelOrder);
            hash = 61 * hash + Objects.hashCode(this.channelType);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ImageFormat other = (ImageFormat) obj;
            if (this.channelOrder != other.channelOrder) {
                return false;
            }
            if (this.channelType != other.channelType) {
                return false;
            }
            return true;
        }
        
    }

    /**
     * The possible image types / dimensions.
     */
    public static enum ImageType {
        IMAGE_1D,
        IMAGE_1D_BUFFER,
        IMAGE_2D,
        IMAGE_3D,
        IMAGE_1D_ARRAY,
        IMAGE_2D_ARRAY
    }

    /**
     * The image descriptor structure describes the type and dimensions of the image or image array.
     * <p>
     * There exists two constructors:<br>
     * {@link #ImageDescriptor(com.jme3.opencl.Image.ImageType, long, long, long, long) }
     * is used when an image with new memory should be created (used most often).<br>
     * {@link #ImageDescriptor(com.jme3.opencl.Image.ImageType, long, long, long, long, long, long, java.nio.ByteBuffer) }
     * creates an image using the provided {@code ByteBuffer} as source.
     */
    public static class ImageDescriptor { //Struct
        public ImageType type;
        public long width;
        public long height;
        public long depth;
        public long arraySize;
        public long rowPitch;
        public long slicePitch;
        public ByteBuffer hostPtr;
        /*
        public int numMipLevels;  //They must always be set to zero
        public int numSamples;
        */

        public ImageDescriptor() {
        }

        /**
         * Used to specify an image with the provided ByteBuffer as soruce
         * @param type the image type
         * @param width the width
         * @param height the height, unused for image types {@code ImageType.IMAGE_1D*}
         * @param depth the depth of the image, only used for image type {@code ImageType.IMAGE_3D}
         * @param arraySize the number of array elements for image type {@code ImageType.IMAGE_1D_ARRAY} and {@code ImageType.IMAGE_2D_ARRAY}
         * @param rowPitch the row pitch of the provided buffer
         * @param slicePitch the slice pitch of the provided buffer
         * @param hostPtr host buffer used as image memory
         */
        public ImageDescriptor(ImageType type, long width, long height, long depth, long arraySize, long rowPitch, long slicePitch, ByteBuffer hostPtr) {
            this.type = type;
            this.width = width;
            this.height = height;
            this.depth = depth;
            this.arraySize = arraySize;
            this.rowPitch = rowPitch;
            this.slicePitch = slicePitch;
            this.hostPtr = hostPtr;
        }
        /**
         * Specifies an image without a host buffer, a new chunk of memory 
         * will be allocated.
         * @param type the image type
         * @param width the width
         * @param height the height, unused for image types {@code ImageType.IMAGE_1D*}
         * @param depth the depth of the image, only used for image type {@code ImageType.IMAGE_3D}
         * @param arraySize the number of array elements for image type {@code ImageType.IMAGE_1D_ARRAY} and {@code ImageType.IMAGE_2D_ARRAY}
         */
        public ImageDescriptor(ImageType type, long width, long height, long depth, long arraySize) {
            this.type = type;
            this.width = width;
            this.height = height;
            this.depth = depth;
            this.arraySize = arraySize;
            this.rowPitch = 0;
            this.slicePitch = 0;
            hostPtr = null;
        }

        @Override
        public String toString() {
            return "ImageDescriptor{" + "type=" + type + ", width=" + width + ", height=" + height + ", depth=" + depth + ", arraySize=" + arraySize + ", rowPitch=" + rowPitch + ", slicePitch=" + slicePitch + '}';
        }
        
    }

    protected Image(ObjectReleaser releaser) {
        super(releaser);
    }
    
    /**
     * @return the width of the image
     */
    public abstract long getWidth();
    /**
     * @return the height of the image
     */
    public abstract long getHeight();
    /**
     * @return the depth of the image
     */
    public abstract long getDepth();
    /**
     * @return the row pitch when the image was created from a host buffer
     * @see ImageDescriptor#ImageDescriptor(com.jme3.opencl.Image.ImageType, long, long, long, long, long, long, java.nio.ByteBuffer) 
     */
    public abstract long getRowPitch();
    /**
     * @return the slice pitch when the image was created from a host buffer
     * @see ImageDescriptor#ImageDescriptor(com.jme3.opencl.Image.ImageType, long, long, long, long, long, long, java.nio.ByteBuffer) 
     */
    public abstract long getSlicePitch();
    /**
     * @return the number of elements in the image array
     * @see ImageType#IMAGE_1D_ARRAY
     * @see ImageType#IMAGE_2D_ARRAY
     */
    public abstract long getArraySize();
    /**
     * @return the image format
     */
    public abstract ImageFormat getImageFormat();
    /**
     * @return the image type
     */
    public abstract ImageType getImageType();
    /**
     * @return the number of bytes per pixel
     */
    public abstract int getElementSize();
    
    /**
     * Performs a blocking read of the image into the specified byte buffer.
     * @param queue the command queue
     * @param dest the target byte buffer
     * @param origin the image origin location, see class description for the format
     * @param region the copied region, see class description for the format
     * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D.
     * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width}
     * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images.
     * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height}
     */
    public abstract void readImage(CommandQueue queue, ByteBuffer dest, long[] origin, long[] region, long rowPitch, long slicePitch);
    /**
     * Performs an async/non-blocking read of the image into the specified byte buffer.
     * @param queue the command queue
     * @param dest the target byte buffer
     * @param origin the image origin location, see class description for the format
     * @param region the copied region, see class description for the format
     * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D.
     * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width}
     * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images.
     * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height}
     * @return the event object indicating the status of the operation
     */
    public abstract Event readImageAsync(CommandQueue queue, ByteBuffer dest, long[] origin, long[] region, long rowPitch, long slicePitch);
    
    /**
     * Performs a blocking write from the specified byte buffer into the image.
     * @param queue the command queue
     * @param src the source buffer
     * @param origin the image origin location, see class description for the format
     * @param region the copied region, see class description for the format
     * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D.
     * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width}
     * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images.
     * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height}
     */
    public abstract void writeImage(CommandQueue queue, ByteBuffer src, long[] origin, long[] region, long rowPitch, long slicePitch);
    /**
     * Performs an async/non-blocking write from the specified byte buffer into the image.
     * @param queue the command queue
     * @param src the source buffer
     * @param origin the image origin location, see class description for the format
     * @param region the copied region, see class description for the format
     * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D.
     * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width}
     * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images.
     * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height}
     * @return the event object indicating the status of the operation
     */
    public abstract Event writeImageAsync(CommandQueue queue, ByteBuffer src, long[] origin, long[] region, long rowPitch, long slicePitch);
    
    /**
     * Performs a blocking copy operation from one image to another.
     * <b>Important:</b> Both images must have the same format!
     * @param queue the command queue
     * @param dest the target image
     * @param srcOrigin the source image origin, see class description for the format
     * @param destOrigin the target image origin, see class description for the format
     * @param region the copied region, see class description for the format
     */
    public abstract void copyTo(CommandQueue queue, Image dest, long[] srcOrigin, long[] destOrigin, long[] region);
    /**
     * Performs an async/non-blocking copy operation from one image to another.
     * <b>Important:</b> Both images must have the same format!
     * @param queue the command queue
     * @param dest the target image
     * @param srcOrigin the source image origin, see class description for the format
     * @param destOrigin the target image origin, see class description for the format
     * @param region the copied region, see class description for the format
     * @return the event object indicating the status of the operation
     */
    public abstract Event copyToAsync(CommandQueue queue, Image dest, long[] srcOrigin, long[] destOrigin, long[] region);
    
    /**
     * Maps the image into host memory.
     * The returned structure contains the mapped byte buffer and row and slice pitch.
     * The event object is set to {@code null}, it is needed for the asnyc
     * version {@link #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) }.
     * @param queue the command queue
     * @param origin the image origin, see class description for the format
     * @param region the mapped region, see class description for the format
     * @param access the allowed memory access to the mapped memory
     * @return a structure describing the mapped memory
     * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) 
     */
    public abstract ImageMapping map(CommandQueue queue, long[] origin, long[] region, MappingAccess access);
    /**
     * Non-blocking version of {@link #map(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) }.
     * The returned structure contains the mapped byte buffer and row and slice pitch.
     * The event object is used to detect when the mapped memory is available.
     * @param queue the command queue
     * @param origin the image origin, see class description for the format
     * @param region the mapped region, see class description for the format
     * @param access the allowed memory access to the mapped memory
     * @return a structure describing the mapped memory
     * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) 
     */
    public abstract ImageMapping mapAsync(CommandQueue queue, long[] origin, long[] region, MappingAccess access);
    /**
     * Unmaps the mapped memory
     * @param queue the command queue
     * @param mapping the mapped memory
     */
    public abstract void unmap(CommandQueue queue, ImageMapping mapping);
    
    /**
     * Describes a mapped region of the image
     */
    public static class ImageMapping {
        /**
         * The raw byte buffer
         */
        public final ByteBuffer buffer;
        /**
         * The row pitch in bytes.
         * This value is at least {@code bytesPerElement * width}
         */
        public final long rowPitch;
        /**
         * The slice pitch in bytes.
         * This value is at least {@code rowPitch * height}
         */
        public final long slicePitch;
        /**
         * The event object used to detect when the memory is available.
         * @see #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) 
         */
        public final Event event;

        public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch, Event event) {
            this.buffer = buffer;
            this.rowPitch = rowPitch;
            this.slicePitch = slicePitch;
            this.event = event;
        }
        public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) {
            this.buffer = buffer;
            this.rowPitch = rowPitch;
            this.slicePitch = slicePitch;
            this.event = null;
        }
        
    }
    
    /**
     * Fills the image with the specified color.
     * Does <b>only</b> work if the image channel is {@link ImageChannelType#FLOAT}
     * or {@link ImageChannelType#HALF_FLOAT}.
     * @param queue the command queue
     * @param origin the image origin, see class description for the format
     * @param region the size of the region, see class description for the format
     * @param color the color to fill
     * @return an event object to detect for the completion
     */
    public abstract Event fillAsync(CommandQueue queue, long[] origin, long[] region, ColorRGBA color);
    /**
     * Fills the image with the specified color given as four integer variables.
     * Does <b>not</b> work if the image channel is {@link ImageChannelType#FLOAT}
     * or {@link ImageChannelType#HALF_FLOAT}.
     * @param queue the command queue
     * @param origin the image origin, see class description for the format
     * @param region the size of the region, see class description for the format
     * @param color the color to fill, must be an array of length 4
     * @return an event object to detect for the completion
     */
    public abstract Event fillAsync(CommandQueue queue, long[] origin, long[] region, int[] color);
    
    /**
     * Copies this image into the specified buffer, no format conversion is done.
     * This is the dual function to 
     * {@link Buffer#copyToImageAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image, long, long[], long[]) }.
     * @param queue the command queue
     * @param dest the target buffer
     * @param srcOrigin the image origin, see class description for the format
     * @param srcRegion the copied region, see class description for the format
     * @param destOffset an offset into the target buffer
     * @return the event object to detect the completion of the operation
     */
    public abstract Event copyToBufferAsync(CommandQueue queue, Buffer dest, long[] srcOrigin, long[] srcRegion, long destOffset);
    
    /**
     * Aquires this image object for using. Only call this method if this image
     * represents a shared object from OpenGL, created with e.g.
     * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) }
     * or variations.
     * This method must be called before the image is used. After the work is
     * done, the image must be released by calling
     * {@link #releaseImageForSharingAsync(com.jme3.opencl.CommandQueue)  }
     * so that OpenGL can use the image/texture/renderbuffer again.
     * @param queue the command queue
     * @return the event object
     */
    public abstract Event acquireImageForSharingAsync(CommandQueue queue);
    
    /**
     * Aquires this image object for using. Only call this method if this image
     * represents a shared object from OpenGL, created with e.g.
     * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) }
     * or variations.
     * This method must be called before the image is used. After the work is
     * done, the image must be released by calling
     * {@link #releaseImageForSharingAsync(com.jme3.opencl.CommandQueue)  }
     * so that OpenGL can use the image/texture/renderbuffer again.
     * 
     * The generated event object is directly released.
     * This brings a performance improvement when the resource is e.g. directly
     * used by a kernel afterwards on the same queue (this implicitly waits for
     * this action). If you need the event, use 
     * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }.
     * 
     * @param queue the command queue
     */
    public void acquireImageForSharingNoEvent(CommandQueue queue) {
        //Default implementation, overwrite for performance
        acquireImageForSharingAsync(queue).release();
    }
    
    /**
     * Releases a shared image object.
     * Call this method after the image object was acquired by
     * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }
     * to hand the control back to OpenGL.
     * @param queue the command queue
     * @return the event object
     */
    public abstract Event releaseImageForSharingAsync(CommandQueue queue);
    
    /**
     * Releases a shared image object.
     * Call this method after the image object was acquired by
     * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }
     * to hand the control back to OpenGL.
     * The generated event object is directly released, resulting in 
     * performance improvements.
     * @param queue the command queue
     */
    public void releaseImageForSharingNoEvent(CommandQueue queue) {
        //default implementation, overwrite it for performance improvements
        releaseImageForSharingAsync(queue).release();
    }
    
    //TODO: add variants of the above two methods that don't create the event object, but release the event immediately
}
