package speiger.src.coreengine.rendering.texturesOld.normal;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.lwjgl.opengl.GL11;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;

import speiger.src.collections.ints.collections.IntIterator;
import speiger.src.collections.ints.maps.impl.hash.Int2ObjectLinkedOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap.Entry;
import speiger.src.collections.ints.sets.IntLinkedOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.ints.utils.maps.Int2ObjectMaps;
import speiger.src.collections.utils.ITrimmable;
import speiger.src.coreengine.math.bits.BitUtil;
import speiger.src.coreengine.rendering.texturesOld.base.AbstractTexture;
import speiger.src.coreengine.rendering.texturesOld.base.TextureManager;
import speiger.src.coreengine.rendering.texturesOld.custom.IDynamicTexture;
import speiger.src.coreengine.rendering.utils.AllocationTracker;

public class DynamicTexture extends AbstractTexture implements IDynamicTexture
{
	public static final int R = 0xFF << 16;
	public static final int G = 0xFF << 8;
	public static final int B = 0xFF;
	public static final int A = 0xFF << 24;
	Int2ObjectMap<IntSet> dirtyChunks = new Int2ObjectLinkedOpenHashMap<IntSet>();
	Lock lock = new ReentrantLock();
	int[] data;
	int width;
	int height;
	
	public DynamicTexture(int width, int height)
	{
		this.width = width;
		this.height = height;
		setTextureID(GL11.glGenTextures());
		data = new int[width * height];
	}
	
	public DynamicTexture(int width, int height, int defaultValue)
	{
		this(width, height);
		Arrays.fill(data, defaultValue);
		processChanges(true);
	}
	
	public int[] getTextureData()
	{
		return data;
	}
	
	@Override
	public void markDirty(int x, int z)
	{
		lock.lock();
		dirtyChunks.supplyIfAbsent(BitUtil.toInt(x >> 4, z >> 4), IntLinkedOpenHashSet::new).add((x & 15) << 4 | (z & 15));
		lock.unlock();
	}
	
	@Override
	public void setData(int index, int value)
	{
		data[index] = value;
		markDirty(index);
	}

	@Override
	public void setRed(int index, int red)
	{
		if(getRed(index) == red) return;
		data[index] = (data[index] & ~R) | (red & 0xFF) << 16;
		markDirty(index);
	}
	
	@Override
	public void setGreen(int index, int green)
	{
		if(getGreen(index) == green) return;
		data[index] = (data[index] & ~G) | (green & 0xFF) << 8;
		markDirty(index);
	}

	@Override
	public void setBlue(int index, int blue)
	{
		if(getBlue(index) == blue) return;
		data[index] = (data[index] & ~B) | (blue & 0xFF);
		markDirty(index);
	}

	@Override
	public void setAlpha(int index, int alpha)
	{
		if(getAlpha(index) == alpha) return;
		data[index] = (data[index] & ~A) | (alpha & 0xFF) << 24;
		markDirty(index);
	}
	
	@Override
	public int getRGB(int index)
	{
		return data[index];
	}
	
	@Override
	public int getRed(int index)
	{
		return (data[index] >> 16) & 0xFF;
	}

	@Override
	public int getGreen(int index)
	{
		return (data[index] >> 8) & 0xFF;
	}

	@Override
	public int getBlue(int index)
	{
		return data[index] & 0xFF;
	}

	@Override
	public int getAlpha(int index)
	{
		return (data[index] >> 24) & 0xFF;
	}
	
	@Override
	public boolean isDirty()
	{
		return dirtyChunks.size() > 0;
	}
	
	@Override
	public void processChanges(boolean full)
	{
		lock.lock();
		if(full)
		{
			ByteBuffer buffer = MemoryUtil.memAlloc(data.length * 4);
			for(int i = 0;i<data.length;i++)
			{
				int pixel = data[i];
				buffer.put((byte)((pixel >> 16) & 0xFF));
				buffer.put((byte)((pixel >> 8) & 0xFF));
				buffer.put((byte)(pixel & 0xFF));
				buffer.put((byte)((pixel >> 24) & 0xFF));
			}
			buffer.flip();
			bindTexture();
			GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
			GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
			AllocationTracker.INSTANCE.addGPUBytes(buffer.remaining());
			GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
			MemoryUtil.memFree(buffer);
			dirtyChunks.clear();
		}
		else
		{
			bindTexture();
			Thread thread = Thread.currentThread();
			for(Iterator<Entry<IntSet>> iter = Int2ObjectMaps.fastIterator(dirtyChunks);iter.hasNext() && !thread.isInterrupted();iter.remove())
			{
				Entry<IntSet> values = iter.next();
				int key = values.getIntKey();
				int chunkX = BitUtil.shortKey(key) * 16;
				int chunkZ = BitUtil.shortValue(key) * 16;
				int width = 0;
				int height = 0;
				for(IntIterator subIt = values.getValue().iterator();subIt.hasNext();)
				{
					int index = subIt.nextInt();
					width = Math.max(width, ((index >> 4) & 0xF) + 1);
					height = Math.max(height, (index & 0xF) + 1);
				}
				try(MemoryStack stack = MemoryStack.stackPush())
				{
					ByteBuffer buffer = stack.malloc(width * height * 4);
					for(int y = 0;y<height;y++)
					{
						for(int x = 0;x<width;x++)
						{
							int pixel = data[((chunkZ + y) * this.width) + chunkX + x];
							buffer.put((byte)((pixel >> 16) & 0xFF));
							buffer.put((byte)((pixel >> 8) & 0xFF));
							buffer.put((byte)(pixel & 0xFF));
							buffer.put((byte)((pixel >> 24) & 0xFF));
						}
					}
					buffer.flip();
					AllocationTracker.INSTANCE.addGPUBytes(buffer.remaining());
					GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, chunkX, chunkZ, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
				}
			}
		}
		if(dirtyChunks.isEmpty())
		{
			((ITrimmable)dirtyChunks).trim();
		}
		lock.unlock();
	}
	
	@Override
	public void reload()
	{
		int old = getTextureId();
		setTextureID(GL11.glGenTextures());
		TextureManager.INSTANCE.removeTexture(old);
		processChanges(true);
	}
	
	@Override
	public int getWidth()
	{
		return width;
	}

	@Override
	public int getHeight()
	{
		return height;
	}
}
