/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.graphics;

import java.io.IOException;
import java.nio.ByteBuffer;

import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.Gdx2DPixmap;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;

/** <p>
 * A Pixmap represents an image in memory. It has a width and height expressed in pixels as well as a {@link Format} specifying
 * the number and order of color components per pixel. Coordinates of pixels are specified with respect to the top left corner of
 * the image, with the x-axis pointing to the right and the y-axis pointing downwards.
 * </p>
 * 
 * <p>
 * By default all methods use blending. You can disable blending with {@link Pixmap#setBlending(Blending)}. The
 * {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)} method will scale and stretch the source image to a
 * target image. There either nearest neighbour or bilinear filtering can be used.
 * </p>
 * 
 * <p>
 * A Pixmap stores its data in native heap memory. It is mandatory to call {@link Pixmap#dispose()} when the pixmap is no longer
 * needed, otherwise memory leaks will result
 * </p>
 * 
 * @author badlogicgames@gmail.com */
public class Pixmap implements Disposable {
	/** Different pixel formats.
	 * 
	 * @author mzechner */
	public enum Format {
		Alpha, Intensity, LuminanceAlpha, RGB565, RGBA4444, RGB888, RGBA8888;

		public static int toGdx2DPixmapFormat (Format format) {
			if (format == Alpha) return Gdx2DPixmap.GDX2D_FORMAT_ALPHA;
			if (format == Intensity) return Gdx2DPixmap.GDX2D_FORMAT_ALPHA;
			if (format == LuminanceAlpha) return Gdx2DPixmap.GDX2D_FORMAT_LUMINANCE_ALPHA;
			if (format == RGB565) return Gdx2DPixmap.GDX2D_FORMAT_RGB565;
			if (format == RGBA4444) return Gdx2DPixmap.GDX2D_FORMAT_RGBA4444;
			if (format == RGB888) return Gdx2DPixmap.GDX2D_FORMAT_RGB888;
			if (format == RGBA8888) return Gdx2DPixmap.GDX2D_FORMAT_RGBA8888;
			throw new GdxRuntimeException("Unknown Format: " + format);
		}

		public static Format fromGdx2DPixmapFormat (int format) {
			if (format == Gdx2DPixmap.GDX2D_FORMAT_ALPHA) return Alpha;
			if (format == Gdx2DPixmap.GDX2D_FORMAT_LUMINANCE_ALPHA) return LuminanceAlpha;
			if (format == Gdx2DPixmap.GDX2D_FORMAT_RGB565) return RGB565;
			if (format == Gdx2DPixmap.GDX2D_FORMAT_RGBA4444) return RGBA4444;
			if (format == Gdx2DPixmap.GDX2D_FORMAT_RGB888) return RGB888;
			if (format == Gdx2DPixmap.GDX2D_FORMAT_RGBA8888) return RGBA8888;
			throw new GdxRuntimeException("Unknown Gdx2DPixmap Format: " + format);
		}
	}

	/** Blending functions to be set with {@link Pixmap#setBlending}.
	 * @author mzechner */
	public enum Blending {
		None, SourceOver
	}

	/** Filters to be used with {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)}.
	 * 
	 * @author mzechner */
	public enum Filter {
		NearestNeighbour, BiLinear
	}

	/** global blending state **/
	private static Blending blending = Blending.SourceOver;

	final Gdx2DPixmap pixmap;
	int color = 0;

	private boolean disposed;

	/** Sets the type of {@link Blending} to be used for all operations. Default is {@link Blending#SourceOver}.
	 * @param blending the blending type */
	public static void setBlending (Blending blending) {
		Pixmap.blending = blending;
		Gdx2DPixmap.setBlend(blending == Blending.None ? 0 : 1);
	}

	/** Sets the type of interpolation {@link Filter} to be used in conjunction with
	 * {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)}.
	 * @param filter the filter. */
	public static void setFilter (Filter filter) {
		Gdx2DPixmap.setScale(filter == Filter.NearestNeighbour ? Gdx2DPixmap.GDX2D_SCALE_NEAREST : Gdx2DPixmap.GDX2D_SCALE_LINEAR);
	}

	/** Creates a new Pixmap instance with the given width, height and format.
	 * @param width the width in pixels
	 * @param height the height in pixels
	 * @param format the {@link Format} */
	public Pixmap (int width, int height, Format format) {
		pixmap = new Gdx2DPixmap(width, height, Format.toGdx2DPixmapFormat(format));
		setColor(0, 0, 0, 0);
		fill();
	}

	/** Creates a new Pixmap instance from the given encoded image data. The image can be encoded as JPEG, PNG or BMP.
	 * @param encodedData the encoded image data
	 * @param offset the offset
	 * @param len the length */
	public Pixmap (byte[] encodedData, int offset, int len) {
		try {
			pixmap = new Gdx2DPixmap(encodedData, offset, len, 0);
		} catch (IOException e) {
			throw new GdxRuntimeException("Couldn't load pixmap from image data", e);
		}
	}

	/** Creates a new Pixmap instance from the given file. The file must be a Png, Jpeg or Bitmap. Paletted formats are not
	 * supported.
	 * 
	 * @param file the {@link FileHandle} */
	public Pixmap (FileHandle file) {
		try {
			byte[] bytes = file.readBytes();
			pixmap = new Gdx2DPixmap(bytes, 0, bytes.length, 0);
		} catch (Exception e) {
			throw new GdxRuntimeException("Couldn't load file: " + file, e);
		}
	}

	/** Constructs a new Pixmap from a {@link Gdx2DPixmap}.
	 * @param pixmap */
	public Pixmap (Gdx2DPixmap pixmap) {
		this.pixmap = pixmap;
	}

	/** Sets the color for the following drawing operations
	 * @param color the color, encoded as RGBA8888 */
	public void setColor (int color) {
		this.color = color;
	}

	/** Sets the color for the following drawing operations.
	 * 
	 * @param r The red component.
	 * @param g The green component.
	 * @param b The blue component.
	 * @param a The alpha component. */
	public void setColor (float r, float g, float b, float a) {
		color = Color.rgba8888(r, g, b, a);
	}

	/** Sets the color for the following drawing operations.
	 * @param color The color. */
	public void setColor (Color color) {
		this.color = Color.rgba8888(color.r, color.g, color.b, color.a);
	}

	/** Fills the complete bitmap with the currently set color. */
	public void fill () {
		pixmap.clear(color);
	}

// /**
// * Sets the width in pixels of strokes.
// *
// * @param width The stroke width in pixels.
// */
// public void setStrokeWidth (int width);

	/** Draws a line between the given coordinates using the currently set color.
	 * 
	 * @param x The x-coodinate of the first point
	 * @param y The y-coordinate of the first point
	 * @param x2 The x-coordinate of the first point
	 * @param y2 The y-coordinate of the first point */
	public void drawLine (int x, int y, int x2, int y2) {
		pixmap.drawLine(x, y, x2, y2, color);
	}

	/** Draws a rectangle outline starting at x, y extending by width to the right and by height downwards (y-axis points downwards)
	 * using the current color.
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate
	 * @param width The width in pixels
	 * @param height The height in pixels */
	public void drawRectangle (int x, int y, int width, int height) {
		pixmap.drawRect(x, y, width, height, color);
	}

	/** Draws an area form another Pixmap to this Pixmap.
	 * 
	 * @param pixmap The other Pixmap
	 * @param x The target x-coordinate (top left corner)
	 * @param y The target y-coordinate (top left corner) */
	public void drawPixmap (Pixmap pixmap, int x, int y) {
		drawPixmap(pixmap, x, y, 0, 0, pixmap.getWidth(), pixmap.getHeight());
	}

	/** Draws an area form another Pixmap to this Pixmap.
	 * 
	 * @param pixmap The other Pixmap
	 * @param x The target x-coordinate (top left corner)
	 * @param y The target y-coordinate (top left corner)
	 * @param srcx The source x-coordinate (top left corner)
	 * @param srcy The source y-coordinate (top left corner);
	 * @param srcWidth The width of the area form the other Pixmap in pixels
	 * @param srcHeight The height of the area form the other Pixmap in pixles */
	public void drawPixmap (Pixmap pixmap, int x, int y, int srcx, int srcy, int srcWidth, int srcHeight) {
		this.pixmap.drawPixmap(pixmap.pixmap, srcx, srcy, x, y, srcWidth, srcHeight);
	}

	/** Draws an area form another Pixmap to this Pixmap. This will automatically scale and stretch the source image to the
	 * specified target rectangle. Use {@link Pixmap#setFilter(Filter)} to specify the type of filtering to be used (nearest
	 * neighbour or bilinear).
	 * 
	 * @param pixmap The other Pixmap
	 * @param srcx The source x-coordinate (top left corner)
	 * @param srcy The source y-coordinate (top left corner);
	 * @param srcWidth The width of the area form the other Pixmap in pixels
	 * @param srcHeight The height of the area form the other Pixmap in pixles
	 * @param dstx The target x-coordinate (top left corner)
	 * @param dsty The target y-coordinate (top left corner)
	 * @param dstWidth The target width
	 * @param dstHeight the target height */
	public void drawPixmap (Pixmap pixmap, int srcx, int srcy, int srcWidth, int srcHeight, int dstx, int dsty, int dstWidth,
		int dstHeight) {
		this.pixmap.drawPixmap(pixmap.pixmap, srcx, srcy, srcWidth, srcHeight, dstx, dsty, dstWidth, dstHeight);
	}

	/** Fills a rectangle starting at x, y extending by width to the right and by height downwards (y-axis points downwards) using
	 * the current color.
	 * 
	 * @param x The x coordinate
	 * @param y The y coordinate
	 * @param width The width in pixels
	 * @param height The height in pixels */
	public void fillRectangle (int x, int y, int width, int height) {
		pixmap.fillRect(x, y, width, height, color);
	}

	/** Draws a circle outline with the center at x,y and a radius using the current color and stroke width.
	 * 
	 * @param x The x-coordinate of the center
	 * @param y The y-coordinate of the center
	 * @param radius The radius in pixels */
	public void drawCircle (int x, int y, int radius) {
		pixmap.drawCircle(x, y, radius, color);
	}

	/** Fills a circle with the center at x,y and a radius using the current color.
	 * 
	 * @param x The x-coordinate of the center
	 * @param y The y-coordinate of the center
	 * @param radius The radius in pixels */
	public void fillCircle (int x, int y, int radius) {
		pixmap.fillCircle(x, y, radius, color);
	}

	/** Fills a triangle with vertices at x1,y1 and x2,y2 and x3,y3 using the current color.
	 * 
	 * @param x1 The x-coordinate of vertex 1
	 * @param y1 The y-coordinate of vertex 1
	 * @param x2 The x-coordinate of vertex 2
	 * @param y2 The y-coordinate of vertex 2
	 * @param x3 The x-coordinate of vertex 3
	 * @param y3 The y-coordinate of vertex 3 */
	public void fillTriangle (int x1, int y1, int x2, int y2, int x3, int y3) {
		pixmap.fillTriangle(x1, y1, x2, y2, x3, y3, color);
	}

	/** Returns the 32-bit RGBA8888 value of the pixel at x, y. For Alpha formats the RGB components will be one.
	 * 
	 * @param x The x-coordinate
	 * @param y The y-coordinate
	 * @return The pixel color in RGBA8888 format. */
	public int getPixel (int x, int y) {
		return pixmap.getPixel(x, y);
	}

	/** @return The width of the Pixmap in pixels. */
	public int getWidth () {
		return pixmap.getWidth();
	}

	/** @return The height of the Pixmap in pixels. */
	public int getHeight () {
		return pixmap.getHeight();
	}

	/** Releases all resources associated with this Pixmap. */
	public void dispose () {
		if (disposed) throw new GdxRuntimeException("Pixmap already disposed!");
		pixmap.dispose();
		disposed = true;
	}

	/** Draws a pixel at the given location with the current color.
	 * 
	 * @param x the x-coordinate
	 * @param y the y-coordinate */
	public void drawPixel (int x, int y) {
		pixmap.setPixel(x, y, color);
	}

	/** Draws a pixel at the given location with the given color.
	 * 
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 * @param color the color in RGBA8888 format. */
	public void drawPixel (int x, int y, int color) {
		pixmap.setPixel(x, y, color);
	}

	/** Returns the OpenGL ES format of this Pixmap. Used as the seventh parameter to
	 * {@link GLCommon#glTexImage2D(int, int, int, int, int, int, int, int, java.nio.Buffer)}.
	 * @return one of GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA. */
	public int getGLFormat () {
		return pixmap.getGLFormat();
	}

	/** Returns the OpenGL ES format of this Pixmap. Used as the third parameter to
	 * {@link GLCommon#glTexImage2D(int, int, int, int, int, int, int, int, java.nio.Buffer)}.
	 * @return one of GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA. */
	public int getGLInternalFormat () {
		return pixmap.getGLInternalFormat();
	}

	/** Returns the OpenGL ES type of this Pixmap. Used as the eighth parameter to
	 * {@link GLCommon#glTexImage2D(int, int, int, int, int, int, int, int, java.nio.Buffer)}.
	 * @return one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4 */
	public int getGLType () {
		return pixmap.getGLType();
	}

	/** Returns the direct ByteBuffer holding the pixel data. For the format Alpha each value is encoded as a byte. For the format
	 * LuminanceAlpha the luminance is the first byte and the alpha is the second byte of the pixel. For the formats RGB888 and
	 * RGBA8888 the color components are stored in a single byte each in the order red, green, blue (alpha). For the formats RGB565
	 * and RGBA4444 the pixel colors are stored in shorts in machine dependent order.
	 * @return the direct {@link ByteBuffer} holding the pixel data. */
	public ByteBuffer getPixels () {
		if (disposed) throw new GdxRuntimeException("Pixmap already disposed");
		return pixmap.getPixels();
	}

	/** @return the {@link Format} of this Pixmap. */
	public Format getFormat () {
		return Format.fromGdx2DPixmapFormat(pixmap.getFormat());
	}

	/** @return the currently set {@link Blending} */
	public static Blending getBlending () {
		return blending;
	}
}
