/**
 * Copyright 2008 - 2015 The Loon Game Engine Authors
 * 
 * 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.
 * 
 * @project loon
 * @author cping
 * @email：javachenpeng@yahoo.com
 * @version 0.5
 */
package loon.robovm;

import loon.Graphics;
import loon.LSystem;
import loon.LTexture;
import loon.canvas.Image;
import loon.canvas.ImageImpl;
import loon.canvas.LColor;
import loon.canvas.Pattern;
import loon.jni.OpenGLES;
import loon.opengl.GL20;
import loon.opengl.TextureSource;
import loon.utils.Scale;

import org.robovm.apple.coregraphics.CGBitmapContext;
import org.robovm.apple.coregraphics.CGBitmapInfo;
import org.robovm.apple.coregraphics.CGColorSpace;
import org.robovm.apple.coregraphics.CGImage;
import org.robovm.apple.coregraphics.CGImageAlphaInfo;
import org.robovm.apple.coregraphics.CGInterpolationQuality;
import org.robovm.apple.coregraphics.CGRect;
import org.robovm.apple.uikit.UIColor;
import org.robovm.apple.uikit.UIImage;
import org.robovm.rt.bro.ptr.IntPtr;

/*
 * 在IOS环境中有个小问题，那就是UIImage提供的API操作像素不太方便，大量地址调用导致效率低下，看情况我可能需要单独构建一个纯像素的图像操作类
 *  (这个问题是共通的，就算不使用robovm，而使用j2obj或mono转换代码也一样)
 */
public class RoboVMImage extends ImageImpl {

	private CGImage image;

	public RoboVMImage(Graphics gfx, Scale scale, CGImage img, String source) {
		super(gfx, scale, (int) img.getWidth(), (int) img.getHeight(), source,
				img);
	}

	public RoboVMImage(RoboVMGame game, boolean async, int preWidth,
			int preHeight, String source) {
		super(game, async, Scale.ONE, preWidth, preHeight, source);
	}

	public CGImage cgImage() {
		return image;
	}

	public UIImage toUIImage() {
		return new UIImage(cgImage());
	}

	@Override
	public Pattern createPattern(boolean repeatX, boolean repeatY) {
		if (image == null) {
			throw new IllegalStateException(
					"Can't create pattern from un-ready image.");
		}
		return new RoboVMPattern(UIColor.fromPatternImage(toUIImage())
				.getCGColor(), repeatX, repeatY);
	}

	@Override
	public void getRGB(int startX, int startY, int width, int height,
			int[] rgbArray, int offset, int scanSize) {
		if (width <= 0 || height <= 0){
			return;
		}
		final int bytesPerPixel = 4;
		final int bytesPerRow = bytesPerPixel * width;
		CGBitmapContext context = CGBitmapContext.create(width, height, 8,
				bytesPerRow, CGColorSpace.createDeviceRGB(), new CGBitmapInfo(
						CGImageAlphaInfo.PremultipliedFirst.value()));
		context.setInterpolationQuality(CGInterpolationQuality.None);
		draw(context, 0, 0, width, height, startX, startY, width, height);

		int[] pixelData = new int[height * bytesPerRow];
		
		context.getData().get(pixelData);

		int i = 0;
		int dst = offset;
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int r = pixelData[i++];
				int g = pixelData[i++];
				int b = pixelData[i++];
				int a = pixelData[i++];
				rgbArray[dst + x] = a << 24 | r << 16 | g << 8 | b;
			}
			dst += scanSize;
		}
		context.dispose();
	}

	@Override
	public void setRGB(int startX, int startY, int width, int height,
			int[] rgbArray, int offset, int scanSize) {
		if (width <= 0 || height <= 0){
			return;
		}
		final int bytesPerPixel = 4;
		final int bytesPerRow = bytesPerPixel * width;
		CGBitmapContext context = CGBitmapContext.create(width, height, 8,
				bytesPerRow, CGColorSpace.createDeviceRGB(), new CGBitmapInfo(
						CGImageAlphaInfo.PremultipliedFirst.value()));
		context.setInterpolationQuality(CGInterpolationQuality.None);
		draw(context, 0, 0, width, height, startX, startY, width, height);
		int[] pixelData = new int[height * bytesPerRow];
		IntPtr handle = context.getData();
		handle.get(pixelData);
		int i = 0;
		int dst = offset;
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int argb = rgbArray[dst + x];
				pixelData[i++] = (argb >> 16) & 255;
				pixelData[i++] = (argb >> 8) & 255;
				pixelData[i++] = (argb) & 255;
				pixelData[i++] = (argb >> 24) & 255;
			}
			dst += scanSize;
		}
		handle.set(pixelData);
		
		setBitmap(context.toImage());
		
		context.dispose();
	}

	@Override
	public Image transform(BitmapTransformer xform) {
		UIImage ximage = new UIImage(
				((RoboVMTransformer) xform).transform(cgImage()));
		return new RoboVMImage(gfx, scale, ximage.getCGImage(), source);
	}

	@Override
	public void draw(Object ctx, float x, float y, float width, float height) {
		CGBitmapContext bctx = (CGBitmapContext) ctx;
		y += height;
		bctx.saveGState();
		bctx.translateCTM(x, y);
		bctx.scaleCTM(1, -1);
		bctx.drawImage(new CGRect(0, 0, width, height), cgImage());
		bctx.restoreGState();
	}

	@Override
	public void draw(Object ctx, float dx, float dy, float dw, float dh,
			float sx, float sy, float sw, float sh) {
		sx *= scale.factor;
		sy *= scale.factor;
		sw *= scale.factor;
		sh *= scale.factor;

		CGImage image = cgImage();
		CGBitmapContext bctx = (CGBitmapContext) ctx;
		float iw = image.getWidth(), ih = image.getHeight();
		float scaleX = dw / (sw - sx), scaleY = dh / (sh - sy);

		bctx.saveGState();
		bctx.translateCTM(dx, dy + dh);
		bctx.scaleCTM(1, -1);
		bctx.clipToRect(new CGRect(0, 0, dw, dh));
		bctx.translateCTM(-sx * scaleX, -(ih - (sy + sh)) * scaleY);
		bctx.drawImage(new CGRect(0, 0, iw * scaleX, ih * scaleY), image);
		bctx.restoreGState();
	}

	@Override
	public String toString() {
		return "Image[src=" + source + ", cgimg=" + image + "]";
	}

	public void dispose() {
		if (image != null) {
			image.dispose();
			image = null;
		}
	}

	protected RoboVMImage(Graphics gfx, Scale scale, int pixelWidth,
			int pixelHeight, String source) {
		super(gfx, scale, pixelWidth, pixelHeight, null, source);
	}

	@Override
	public void upload(Graphics gfx, LTexture tex) {
		int width = pixelWidth, height = pixelHeight;
		if (width == 0 || height == 0) {
			((RoboVMGraphics) gfx).game.log().info(
					"Ignoring texture update for empty image (" + width + "x"
							+ height + ").");
			return;
		}

		CGBitmapContext bctx = RoboVMGraphics.createCGBitmap(width, height);
		CGRect rect = new CGRect(0, 0, width, height);
		bctx.clearRect(rect);
		bctx.drawImage(rect, image);
		upload(gfx, tex.getID(), width, height, bctx.getData());
		bctx.dispose();
	}

	protected void upload(Graphics gfx, int tex, int width, int height,
			IntPtr data) {
		gfx.gl.glBindTexture(GL20.GL_TEXTURE_2D, tex);
		gfx.gl.glPixelStorei(GL20.GL_UNPACK_ALIGNMENT, 1);
		OpenGLES.glTexImage2Dp(GL20.GL_TEXTURE_2D, 0, GL20.GL_RGBA, width,
				height, 0, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, data);
	}

	@Override
	protected void setBitmap(Object bitmap) {
		image = (CGImage) bitmap;
	}
	
	protected void setBitmap(CGBitmapContext context) {
		setBitmap(context.toImage());
	}

	@Override
	protected Object createErrorBitmap(int pixelWidth, int pixelHeight) {
		return RoboVMGraphics.createCGBitmap(pixelWidth, pixelHeight).toImage();
	}

	public void getLight(Image buffer, int v) {
		int width = (int) buffer.width();
		int height = (int) buffer.height();
		for (int x = 0; x < width; ++x) {
			for (int y = 0; y < height; ++y) {
				int rgbValue = buffer.getRGB(x, y);
				if (rgbValue != 0) {
					int color = getLight(rgbValue, v);
					buffer.setRGB(color, x, y);
				}
			}
		}
	}

	public int getLight(int color, int v) {
		int red = LColor.getRed(color);
		int green = LColor.getGreen(color);
		int blue = LColor.getBlue(color);
		red += v;
		green += v;
		blue += v;
		blue = blue > 255 ? 255 : blue;
		red = red > 255 ? 255 : red;
		green = green > 255 ? 255 : green;
		red = red < 0 ? 0 : red;
		green = green < 0 ? 0 : green;
		blue = blue < 0 ? 0 : blue;
		return LColor.getRGB(red, green, blue);
	}

	@Override
	public int[] getPixels() {
		int width = (int) image.getWidth();
		int height = (int) image.getHeight();
		int[] rgbArray = new int[width * height];
		return getPixels(rgbArray);
	}

	@Override
	public int[] getPixels(int[] pixels) {
		final int width = (int) image.getWidth();
		final int height = (int) image.getHeight();
		final int length = width * height;
		final int[] rgbArray = new int[length];
		getRGB(0, 0, width, height, rgbArray, length, width);
		return rgbArray;
	}

	@Override
	public int[] getPixels(int x, int y, int w, int h) {
		return getPixels(0, w * h, x, y, w, h);
	}

	@Override
	public int[] getPixels(int offset, int stride, int x, int y, int width,
			int height) {
		final int[] rgbArray = new int[width * height];
		getRGB(x, y, width, height, rgbArray, offset, width);
		return rgbArray;
	}

	@Override
	public int[] getPixels(int[] pixels, int offset, int stride, int x, int y,
			int width, int height) {
		getRGB(x, y, width, height, pixels, offset, stride);
		return pixels;
	}

	@Override
	public void setPixels(int[] pixels, int width, int height) {
		setPixels(pixels, 0, width, 0, 0, width, height);
	}

	@Override
	public void setPixels(int[] pixels, int offset, int stride, int x, int y,
			int width, int height) {
		setRGB(x, y, width, height, pixels, offset, stride);
	}

	@Override
	public int[] setPixels(int[] pixels, int x, int y, int w, int h) {
		setRGB(x, y, w, h, pixels, 0, w);
		return pixels;
	}

	@Override
	public void setPixel(LColor c, int x, int y) {
		setPixel(c.getARGB(), x, y);
	}

	@Override
	public void setPixel(int rgb, int x, int y) {
		setRGB(rgb, x, y);
	}

	@Override
	public int getPixel(int x, int y) {
		int width = (int) image.getWidth();
		int height = (int) image.getHeight();

		int bytesPerPixel = 4;
		int bytesPerRow = bytesPerPixel * width;
		CGBitmapContext context = CGBitmapContext.create(width, height, 8,
				bytesPerRow, CGColorSpace.createDeviceRGB(), new CGBitmapInfo(
						CGImageAlphaInfo.PremultipliedFirst.value()));
		context.setInterpolationQuality(CGInterpolationQuality.None);
		draw(context, 0, 0, width, height);
		int[] pixelData = new int[height * bytesPerRow];
		context.getData().get(pixelData);

		int rowOffset = y * bytesPerRow;
		int colOffset = x * bytesPerPixel;
		int pixelDataLoc = rowOffset + colOffset;

		int r = pixelData[pixelDataLoc + 0];
		int g = pixelData[pixelDataLoc + 1];
		int b = pixelData[pixelDataLoc + 2];
		int a = pixelData[pixelDataLoc + 3];
		context.dispose();
		return LColor.getARGB(r, g, b, a);
	}

	@Override
	public int getRGB(int x, int y) {
		return getPixel(x, y);
	}

	@Override
	public void setRGB(int rgb, int x, int y) {
		int width = (int) image.getWidth();
		int height = (int) image.getHeight();

		int bytesPerPixel = 4;
		int bytesPerRow = bytesPerPixel * width;
		CGBitmapContext context = CGBitmapContext.create(width, height, 8,
				bytesPerRow, CGColorSpace.createDeviceRGB(), new CGBitmapInfo(
						CGImageAlphaInfo.PremultipliedFirst.value()));
		context.setInterpolationQuality(CGInterpolationQuality.None);
		
		y += height;
		context.saveGState();
		context.translateCTM(x, y);
		context.scaleCTM(1, -1);
		context.drawImage(new CGRect(0, 0, width, height), cgImage());

		LColor color = new LColor(rgb);
		
		float r = color.r;
		float g = color.g;
		float b = color.b;
		float a = color.a;
		
		context.setRGBStrokeColor(r, g, b, a);
		context.setRGBFillColor(r, g, b, a);
		context.setAlpha(a);

		context.fillRect(new CGRect(x, y, 1f, 1f));
		context.restoreGState();
		
		setBitmap(context.toImage());
		
		context.dispose();
		
		/**
		 * 
		int width = (int) image.getWidth();
		int height = (int) image.getHeight();

		int bytesPerPixel = 4;
		int bytesPerRow = bytesPerPixel * width;
		CGBitmapContext context = CGBitmapContext.create(width, height, 8,
				bytesPerRow, CGColorSpace.createDeviceRGB(), new CGBitmapInfo(
						CGImageAlphaInfo.PremultipliedFirst.value()));
		context.setInterpolationQuality(CGInterpolationQuality.None);
		draw(context, 0, 0, width, height);
		int[] pixelData = new int[height * bytesPerRow];
		IntPtr handle = context.getData();
		handle.get(pixelData);

		int rowOffset = y * bytesPerRow;
		int colOffset = x * bytesPerPixel;
		int pixelDataLoc = rowOffset + colOffset;

		pixelData[pixelDataLoc + 0] = (rgb >> 16) & 255;
		pixelData[pixelDataLoc + 1] = (rgb >> 8) & 255;
		pixelData[pixelDataLoc + 2] = (rgb) & 255;
		pixelData[pixelDataLoc + 3] = (rgb >> 24) & 255;

		handle.set(pixelData);
		
		setBitmap(context.toImage());
		
		context.dispose();
	
		 */
	}

	@Override
	public boolean hasAlpha() {
		return image.getAlphaInfo().equals(CGImageAlphaInfo.Last)
				|| image.getAlphaInfo().equals(CGImageAlphaInfo.First);
	}

	@Override
	public Image getSubImage(int x, int y, int width, int height) {
		return new RoboVMImage(LSystem.base().graphics(), LSystem.base()
				.graphics().scale(), CGImage.createWithImageInRect(image,
				new CGRect(x, y, width, height)), TextureSource.RenderCanvas);
	}

	@Override
	protected void finalize() {
		dispose();
	}

	@Override
	protected void closeImpl() {
		this.dispose();
	}

}
