/*
 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javafx.scene.image;

import com.sun.javafx.tk.ImageLoader;
import com.sun.javafx.tk.PlatformImage;
import com.sun.javafx.tk.Toolkit;
import javafx.beans.NamedArg;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.paint.Color;

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

/**
 * The {@code WritableImage} class represents a custom graphical image
 * that is constructed from pixels supplied by the application, and possibly
 * from {@code PixelReader} objects from any number of sources, including
 * images read from a file or URL.
 * @since JavaFX 2.2
 */
public class WritableImage extends Image {

    static {
        Toolkit.setWritableImageAccessor(new Toolkit.WritableImageAccessor() {
            @Override public void loadTkImage(WritableImage wimg, Object loader) {
                wimg.loadTkImage(loader);
            }

            @Override public Object getTkImageLoader(WritableImage wimg) {
                return wimg.getTkImageLoader();
            }
        });
    }

    private ImageLoader tkImageLoader;

    /**
     * Construct an empty image of the specified dimensions.
     * The image will initially be filled with transparent pixels.
     * Images constructed this way will always be readable and writable
     * so the corresponding getPixelReader() and getPixelWriter() will
     * always return valid objects.
     * The dimensions must both be positive numbers <code>(&gt;&nbsp;0)</code>.
     *
     * @param width the desired width of the writable image
     * @param height the desired height of the desired image
     * @throws IllegalArgumentException if either dimension is negative or zero.
     */
    public WritableImage(@NamedArg("width") int width, @NamedArg("height") int height) {
        super(width, height);
    }

    /**
     * Construct an image of the specified dimensions, initialized from
     * the indicated {@link PixelReader}.
     * The image will initially be filled with data returned from the
     * {@code PixelReader}.
     * If the {@code PixelReader} accesses a surface that does not contain
     * the necessary number of pixel rows and columns then an
     * {@link ArrayIndexOutOfBoundsException} will be thrown.
     * Images constructed this way will always be readable and writable
     * so the corresponding getPixelReader() and getPixelWriter() will
     * always return valid objects.
     * The dimensions must both be positive numbers <code>(&gt;&nbsp;0)</code>.
     *
     * @param width the desired width of the writable image and the
     *        width of the region to be read from the {@code reader}
     * @param height the desired height of the desired image and the
     *        width of the region to be read from the {@code reader}
     * @throws ArrayIndexOutOfBoundsException if the {@code reader} does
     *         not access a surface of at least the requested dimensions
     * @throws IllegalArgumentException if either dimension is negative or zero.
     */
    public WritableImage(@NamedArg("reader") PixelReader reader, @NamedArg("width") int width, @NamedArg("height") int height) {
        super(width, height);
        getPixelWriter().setPixels(0, 0, width, height, reader, 0, 0);
    }

    /**
     * Construct an image of the specified dimensions, initialized from
     * the indicated region of the {@link PixelReader}.
     * The image will initially be filled with data returned from the
     * {@code PixelReader} for the specified region.
     * If the {@code PixelReader} accesses a surface that does not contain
     * the necessary number of pixel rows and columns then an
     * {@link ArrayIndexOutOfBoundsException} will be thrown.
     * Images constructed this way will always be readable and writable
     * so the corresponding getPixelReader() and getPixelWriter() will
     * always return valid objects.
     * The dimensions must both be positive numbers <code>(&gt;&nbsp;0)</code>.
     *
     * @param x the X coordinate of the upper left corner of the region to
     *        read from the {@code reader}
     * @param y the Y coordinate of the upper left corner of the region to
     *        read from the {@code reader}
     * @param width the desired width of the writable image and the
     *        width of the region to be read from the {@code reader}
     * @param height the desired height of the desired image and the
     *        width of the region to be read from the {@code reader}
     * @throws ArrayIndexOutOfBoundsException if the {@code reader} does
     *         not access a surface containing at least the indicated region
     * @throws IllegalArgumentException if either dimension is negative or zero.
     */
    public WritableImage(@NamedArg("reader") PixelReader reader,
                         @NamedArg("x") int x, @NamedArg("y") int y, @NamedArg("width") int width, @NamedArg("height") int height)
    {
        super(width, height);
        getPixelWriter().setPixels(0, 0, width, height, reader, x, y);
    }

    @Override
    boolean isAnimation() {
        return true;
    }

    @Override
    boolean pixelsReadable() {
        return true;
    }

    private PixelWriter writer;
    /**
     * This method returns a {@code PixelWriter} that provides access to
     * write the pixels of the image.
     *
     * @return the {@code PixelWriter} for writing pixels to the image
     */
    public final PixelWriter getPixelWriter() {
        if (getProgress() < 1.0 || isError()) {
            return null;
        }
        if (writer == null) {
            writer = new PixelWriter() {
                ReadOnlyObjectProperty<PlatformImage> pimgprop =
                    acc_platformImageProperty();

                @Override
                public PixelFormat getPixelFormat() {
                    PlatformImage pimg = getWritablePlatformImage();
                    return pimg.getPlatformPixelFormat();
                }

                @Override
                public void setArgb(int x, int y, int argb) {
                    getWritablePlatformImage().setArgb(x, y, argb);
                    pixelsDirty();
                }

                @Override
                public void setColor(int x, int y, Color c) {
                    if (c == null) throw new NullPointerException("Color cannot be null");
                    int a = (int) Math.round(c.getOpacity() * 255);
                    int r = (int) Math.round(c.getRed()     * 255);
                    int g = (int) Math.round(c.getGreen()   * 255);
                    int b = (int) Math.round(c.getBlue()    * 255);
                    setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b);
                }

                @Override
                public <T extends Buffer>
                    void setPixels(int x, int y, int w, int h,
                                   PixelFormat<T> pixelformat,
                                   T buffer, int scanlineStride)
                {
                    if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
                    if (buffer == null) throw new NullPointerException("Buffer cannot be null");
                    PlatformImage pimg = getWritablePlatformImage();
                    pimg.setPixels(x, y, w, h, pixelformat,
                                   buffer, scanlineStride);
                    pixelsDirty();
                }

                @Override
                public void setPixels(int x, int y, int w, int h,
                                      PixelFormat<ByteBuffer> pixelformat,
                                      byte buffer[], int offset, int scanlineStride)
                {
                    if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
                    if (buffer == null) throw new NullPointerException("Buffer cannot be null");
                    PlatformImage pimg = getWritablePlatformImage();
                    pimg.setPixels(x, y, w, h, pixelformat,
                                   buffer, offset, scanlineStride);
                    pixelsDirty();
                }

                @Override
                public void setPixels(int x, int y, int w, int h,
                                      PixelFormat<IntBuffer> pixelformat,
                                      int buffer[], int offset, int scanlineStride)
                {
                    if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
                    if (buffer == null) throw new NullPointerException("Buffer cannot be null");
                    PlatformImage pimg = getWritablePlatformImage();
                    pimg.setPixels(x, y, w, h, pixelformat,
                                   buffer, offset, scanlineStride);
                    pixelsDirty();
                }

                @Override
                public void setPixels(int writex, int writey, int w, int h,
                                      PixelReader reader, int readx, int ready)
                {
                    if (reader == null) throw new NullPointerException("Reader cannot be null");
                    PlatformImage pimg = getWritablePlatformImage();
                    pimg.setPixels(writex, writey, w, h, reader, readx, ready);
                    pixelsDirty();
                }
            };
        }
        return writer;
    }

    private void loadTkImage(Object loader) {
        if (!(loader instanceof ImageLoader)) {
            throw new IllegalArgumentException("Unrecognized image loader: "
                    + loader);
        }
        ImageLoader tkLoader = (ImageLoader)loader;
        if (tkLoader.getWidth() != (int)this.getWidth()
                || tkLoader.getHeight() != (int)this.getHeight())
        {
            throw new IllegalArgumentException("Size of loader does not match size of image");
        }

        super.setPlatformImage(tkLoader.getFrame(0));
        this.tkImageLoader = tkLoader;
    }

    private Object getTkImageLoader() {
        return tkImageLoader;
    }
}
