package com.danitheskunk.skunkworks.backends.gl;

import com.danitheskunk.skunkworks.Recti;
import com.danitheskunk.skunkworks.Vec2i;
import com.danitheskunk.skunkworks.gfx.ITexture;
import com.danitheskunk.skunkworks.gfx.Image;
import org.lwjgl.BufferUtils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import static org.lwjgl.opengl.GL11.*;

class TextureAtlas {
	final private int textureID;
	private final List<Texture> textures;
	private Texture atlasTexture; //for debugging
	private Image img;
	private boolean shouldUpdate;

	TextureAtlas() {
		img = new Image(new Vec2i(32, 32));
		textureID = glGenTextures();
		textures = new ArrayList<>();
		shouldUpdate = true;

		bind();
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

		doubleAtlasSize(); //to init

	}

	ITexture addTexture(Image img) {
		//todo: do the actual texture stuff
		//this.img = img;
		var texture = new Texture(new Recti(new Vec2i(0, 0), img.getSize()),
			img
		);
		textures.add(texture);
		shouldUpdate = true;
		return texture;
	}

	public void bind() {
		glBindTexture(GL_TEXTURE_2D, textureID);
	}

	void doubleAtlasSize() {
		img = new Image(Vec2i.mul(img.getSize(), 2));
		System.out.printf("Resized atlas to %dx%d\n",
			img.getSize().getX(),
			img.getSize().getY()
		);
		atlasTexture = new Texture(new Recti(Vec2i.ZERO, img.getSize()), img);
	}

	public Texture getAtlasTexture() {
		return atlasTexture;
	}

	public Vec2i getSize() {
		return img.getSize();
	}

	void repack() {
		int x = 0;
		int y = 0;
		int height;

		textures.sort(new TextureHeightComparator().reversed());
		height = textures.get(0).getImg().getHeight();

		for(var tex : textures) {
			var texImg = tex.getImg();
			//texture larger than atlas? resize atlas and try again
			if(texImg.getHeight() > img.getHeight() ||
				texImg.getWidth() > img.getWidth()) {
				System.out.println("Texture too large");
				doubleAtlasSize();
				repack();
				return;
			}

			//row full? start the next row
			if(img.getWidth() - x < texImg.getWidth()) {
				x = 0;
				y += height;
				height = texImg.getHeight();

				//not enough space for new row? resize atlas and try again
				if(y + height > img.getHeight()) {
					System.out.println("Texture not enough space for new row");
					doubleAtlasSize();
					repack();
					return;
				}
			}

			//take space, advance to right
			tex.setTexArea(new Recti(new Vec2i(x, y), texImg.getSize()));
			x += texImg.getWidth();
		}
	}

	public void update() {
		if(shouldUpdate) {
			repack();
			updateImage();
			uploadToGpu();
			shouldUpdate = false;
		}
	}

	void updateImage() {
		for(var tex : textures) {
			img.drawImage(tex.getImg(), tex.getTexArea().getPos());
		}
	}

	void uploadToGpu() {
		var buf = BufferUtils.createByteBuffer(img.getData().length);
		buf.put(img.getData());
		buf.flip();

		glTexImage2D(GL_TEXTURE_2D,
			0,
			GL_RGBA,
			img.getWidth(),
			img.getHeight(),
			0,
			GL_RGBA,
			GL_UNSIGNED_BYTE,
			buf
		);
	}

	private static class TextureHeightComparator implements Comparator<Texture> {
		@Override
		public int compare(Texture o1, Texture o2) {
			return o1.getImg().getHeight() - o2.getImg().getHeight();
		}
	}
}

