package speiger.src.coreengine.rendering.textures.custom;

import org.lwjgl.system.MemoryUtil;

import speiger.src.collections.ints.collections.IntIterator;
import speiger.src.collections.ints.sets.IntLinkedOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.math.bits.BitUtil;
import speiger.src.coreengine.math.color.ColorSpaces;
import speiger.src.coreengine.rendering.textures.base.BaseTexture;
import speiger.src.coreengine.rendering.textures.base.TextureMetadata;
import speiger.src.coreengine.rendering.utils.GLFunctions;
import speiger.src.coreengine.rendering.utils.GLStateTracker;
import speiger.src.coreengine.rendering.utils.values.GLDataType;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureFormat;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureParameter;
import speiger.src.coreengine.rendering.utils.values.textures.GLTextureValue;

public class DynamicTexture extends BaseTexture implements IDynamicTexture {
	public static final TextureMetadata DEFAULT_PARAMETERS = TextureMetadata.builder().internalFormat(GLTextureFormat.RGBAI).externalFormat(GLTextureFormat.RGBA).dataFormat(GLDataType.UNSIGNED_BYTE)
						.arguement(GLTextureParameter.MIN_FILTER, GLTextureValue.NEAREST).arguement(GLTextureParameter.MAG_FILTER, GLTextureValue.NEAREST)
						.arguement(GLTextureParameter.WRAP_S, GLTextureValue.CLAMP_TO_EDGE).arguement(GLTextureParameter.WRAP_T, GLTextureValue.CLAMP_TO_EDGE).build();
	IntSet dirtySections = new IntLinkedOpenHashSet();
	TextureMetadata metadata;
	int width;
	int height;
	long data;
	
	public DynamicTexture(int width, int height, TextureMetadata metadata) {
		this.metadata = metadata;
		this.width = width;
		this.height = height;
		if(width % 16 != 0 || height % 16 != 0) throw new IllegalArgumentException("Texture must be a power of 16");
		data = MemoryUtil.nmemAllocChecked(4L * width * height);
		track();
	}
	
	@Override
	public void load(IAssetProvider provider) {
		createTexture();
		metadata.applyArguments(id());
		GLFunctions.prepare2DImage(id(), 1, metadata, width, height);
		dirtySections.clear();
	}
	
	@Override
	public void delete(boolean untrack) {
		super.delete(untrack);
		if(untrack && data != 0L) {
			MemoryUtil.nmemFree(data);
			data = 0L;
		}
	}
	
	@Override
	public int width() { return width; }
	@Override
	public int height() { return height; }
	@Override
	public ColorSpaces colorspace() { return ColorSpaces.ABGR; }
	@Override
	public boolean isDirty() { return !dirtySections.isEmpty(); }
	
	@Override
	public void process(boolean full) {
		if(full) {
			GLFunctions.upload2DSubImage(id(), 0, 0, 0, width, height, GLTextureFormat.RGBA, GLDataType.UNSIGNED_BYTE, data);
			dirtySections.clear();
			return;
		}
		GLStateTracker tracker = GLStateTracker.instance();
		tracker.unpack_row_length.set(width());
		Thread thread = Thread.currentThread();
		for(IntIterator iter = dirtySections.iterator();iter.hasNext() && !thread.isInterrupted();iter.remove()) {
			int key = iter.nextInt();
			uploadPixels(tracker, BitUtil.shortKey(key) * 16, BitUtil.shortValue(key) * 16);
		}
		tracker.unpack_row_length.setDefault();
		tracker.unpack_skip_pixel.setDefault();
		tracker.unpack_skip_rows.setDefault();
	}
	
	protected void uploadPixels(GLStateTracker tracker, int x, int y) {
		tracker.unpack_skip_pixel.set(x);
		tracker.unpack_skip_rows.set(y);
		GLFunctions.upload2DSubImage(id(), 0, x, y, 16, 16, GLTextureFormat.RGBA, GLDataType.UNSIGNED_BYTE, data);
	}
	
	protected long offset(int x, int y) {
		return ((y * width()) + x) << 2L;
	}
	
	protected void ensureValid(int index) {
		ensureValid(index % width, index / width);
	}
	
	protected void ensureValid(int x, int y) {
		if(x < 0 || y < 0) throw new ArrayIndexOutOfBoundsException("Index out of bounds: X=["+x+"], Y=["+y+"]");
		if(x > width || y > height) throw new ArrayIndexOutOfBoundsException("Index out of bounds: X=["+x+"], Y=["+y+"], width=["+width+"], height=["+height+"]");
		if(data == 0L) throw new IllegalStateException("Texture Data isn't bound");
	}
	
	@Override
	public void fill(int x, int y, int width, int height, int data) {
		ensureValid(x, y);
		ensureValid(x+width, y+height);
		for(int xOff = 0;xOff<width;xOff++) {
			for(int yOff = 0;yOff<height;yOff++) {
				MemoryUtil.memPutInt(this.data + offset(x+xOff, y+yOff), data);
			}
		}
		if(id() == 0) return;
		for(int i = x>>4,m=width>>4;i<m;i++) {
			for(int j = y>>4,n=height>>4;j<n;j++) {
				dirtySections.add(BitUtil.toInt(i, j));
			}
		}
	}
	
	@Override
	public void dirty(int x, int y) {
		if(id() == 0) return;
		ensureValid(x, y);
		dirtySections.add(BitUtil.toInt(x >> 4, y >> 4));
	}
	
	@Override
	public void set(int index, int data) {
		ensureValid(index);
		MemoryUtil.memPutInt(this.data + index * 4L, data);
		dirty(index);
	}
	
	@Override
	public void setR(int index, int red) {
		ensureValid(index);
		MemoryUtil.memPutByte(data + index * 4L, (byte)(red & 0xFF));
		dirty(index);
	}
	
	@Override
	public void setG(int index, int green) {
		ensureValid(index);
		MemoryUtil.memPutByte(data + index * 4L + 1L, (byte)(green & 0xFF));
		dirty(index);
	}
	
	@Override
	public void setB(int index, int blue) {
		ensureValid(index);
		MemoryUtil.memPutByte(data + index * 4L + 2L, (byte)(blue & 0xFF));
		dirty(index);
	}
	
	@Override
	public void setA(int index, int alpha) {
		ensureValid(index);
		MemoryUtil.memPutByte(data + index * 4L + 3L, (byte)(alpha & 0xFF));
		dirty(index);
	}
	
	@Override
	public int get(int index) {
		ensureValid(index);
		return MemoryUtil.memGetInt(data + index * 4L);
	}
	
	@Override
	public int getR(int index) {
		ensureValid(index);
		return MemoryUtil.memGetByte(data + index * 4L);
	}
	
	@Override
	public int getG(int index) {
		ensureValid(index);
		return MemoryUtil.memGetByte(data + index * 4L + 1L);
	}
	
	@Override
	public int getB(int index) {
		ensureValid(index);
		return MemoryUtil.memGetByte(data + index * 4L + 2L);
	}
	
	@Override
	public int getA(int index) {
		ensureValid(index);
		return MemoryUtil.memGetByte(data + index * 4L + 3L);
	}
}