package kha;

import haxe.io.Bytes;
import js.Browser;
import js.lib.Uint8Array;
import js.html.VideoElement;
import js.html.webgl.GL;
import kha.graphics4.TextureFormat;
import kha.js.CanvasGraphics;

class CanvasImage extends Image {
	public var image: Dynamic;
	public var video: VideoElement;

	static var context: Dynamic;

	var data: Dynamic;

	var myWidth: Int;
	var myHeight: Int;
	var myFormat: TextureFormat;
	var renderTarget: Bool;

	public var frameBuffer: Dynamic;

	var graphics1: kha.graphics1.Graphics;
	var g2canvas: CanvasGraphics = null;

	public static function init() {
		var canvas: Dynamic = Browser.document.createElement("canvas");
		if (canvas != null) {
			context = canvas.getContext("2d");
			canvas.width = 2048;
			canvas.height = 2048;
			context.globalCompositeOperation = "copy";
		}
	}

	public function new(width: Int, height: Int, format: TextureFormat, renderTarget: Bool) {
		myWidth = width;
		myHeight = height;
		myFormat = format;
		this.renderTarget = renderTarget;
		image = null;
		video = null;
		if (renderTarget)
			createTexture();
	}

	override function get_g1(): kha.graphics1.Graphics {
		if (graphics1 == null) {
			graphics1 = new kha.graphics2.Graphics1(this);
		}
		return graphics1;
	}

	override function get_g2(): kha.graphics2.Graphics {
		if (g2canvas == null) {
			var canvas: Dynamic = Browser.document.createElement("canvas");
			image = canvas;
			var context = canvas.getContext("2d");
			canvas.width = width;
			canvas.height = height;
			g2canvas = new CanvasGraphics(context);
		}
		return g2canvas;
	}

	override function get_g4(): kha.graphics4.Graphics {
		return null;
	}

	override function get_width(): Int {
		return myWidth;
	}

	override function get_height(): Int {
		return myHeight;
	}

	override function get_format(): TextureFormat {
		return myFormat;
	}

	override function get_realWidth(): Int {
		return myWidth;
	}

	override function get_realHeight(): Int {
		return myHeight;
	}

	override function get_stride(): Int {
		return myFormat == TextureFormat.RGBA32 ? 4 * width : width;
	}

	override public function isOpaque(x: Int, y: Int): Bool {
		if (data == null) {
			if (context == null)
				return true;
			else
				createImageData();
		}
		return (data.data[y * Std.int(image.width) * 4 + x * 4 + 3] != 0);
	}

	override public function at(x: Int, y: Int): Color {
		if (data == null) {
			if (context == null)
				return Color.Black;
			else
				createImageData();
		}

		var r = data.data[y * Std.int(image.width) * 4 + x * 4];
		var g = data.data[y * Std.int(image.width) * 4 + x * 4 + 1];
		var b = data.data[y * Std.int(image.width) * 4 + x * 4 + 2];
		var a = data.data[y * Std.int(image.width) * 4 + x * 4 + 3];

		return Color.fromValue((a << 24) | (r << 16) | (g << 8) | b);
	}

	function createImageData() {
		context.strokeStyle = "rgba(0,0,0,0)";
		context.fillStyle = "rgba(0,0,0,0)";
		context.fillRect(0, 0, image.width, image.height);
		context.drawImage(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height);
		data = context.getImageData(0, 0, image.width, image.height);
	}

	var texture: Dynamic;

	static function upperPowerOfTwo(v: Int): Int {
		v--;
		v |= v >>> 1;
		v |= v >>> 2;
		v |= v >>> 4;
		v |= v >>> 8;
		v |= v >>> 16;
		v++;
		return v;
	}

	public function createTexture() {
		if (SystemImpl.gl == null)
			return;
		texture = SystemImpl.gl.createTexture();
		// texture.image = image;
		SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture);
		// Sys.gl.pixelStorei(Sys.gl.UNPACK_FLIP_Y_WEBGL, true);

		SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
		SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR);
		SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
		SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
		if (renderTarget) {
			frameBuffer = SystemImpl.gl.createFramebuffer();
			SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer);
			SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, realWidth, realHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null);
			SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, texture, 0);
			SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null);
		}
		else if (video != null)
			SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, video);
		else
			SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, image);
		// Sys.gl.generateMipmap(Sys.gl.TEXTURE_2D);
		SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null);
	}

	public function set(stage: Int): Void {
		SystemImpl.gl.activeTexture(GL.TEXTURE0 + stage);
		SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture);
		if (video != null)
			SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, video);
	}

	public var bytes: Bytes;

	override public function lock(level: Int = 0): Bytes {
		bytes = Bytes.alloc(myFormat == TextureFormat.RGBA32 ? 4 * width * height : width * height);
		return bytes;
	}

	override public function unlock(): Void {
		data = null;

		if (SystemImpl.gl != null) {
			texture = SystemImpl.gl.createTexture();
			// texture.image = image;
			SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture);
			// Sys.gl.pixelStorei(Sys.gl.UNPACK_FLIP_Y_WEBGL, true);

			SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
			SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR);
			SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
			SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
			SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.LUMINANCE, width, height, 0, GL.LUMINANCE, GL.UNSIGNED_BYTE, new Uint8Array(bytes.getData()));

			if (SystemImpl.ie && SystemImpl.gl.getError() == 1282) { // no LUMINANCE support in IE11
				var rgbaBytes = Bytes.alloc(width * height * 4);
				for (y in 0...height)
					for (x in 0...width) {
						var value = bytes.get(y * width + x);
						rgbaBytes.set(y * width * 4 + x * 4 + 0, value);
						rgbaBytes.set(y * width * 4 + x * 4 + 1, value);
						rgbaBytes.set(y * width * 4 + x * 4 + 2, value);
						rgbaBytes.set(y * width * 4 + x * 4 + 3, 255);
					}
				SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, width, height, 0, GL.RGBA, GL.UNSIGNED_BYTE, new Uint8Array(rgbaBytes.getData()));
			}

			// Sys.gl.generateMipmap(Sys.gl.TEXTURE_2D);
			SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null);
			bytes = null;
		}
	}

	override public function getPixels(): Bytes {
		@:privateAccess var context: js.html.CanvasRenderingContext2D = g2canvas.canvas;
		var imageData: js.html.ImageData = context.getImageData(0, 0, width, height);
		var bytes = Bytes.alloc(imageData.data.length);
		for (i in 0...imageData.data.length) {
			bytes.set(i, imageData.data[i]);
		}
		return bytes;
	}

	override public function unload(): Void {
		image = null;
		video = null;
		data = null;
	}
}