package com.gnarly.game;

import com.gnarly.engine.display.Camera;
import com.gnarly.engine.model.ColRect;
import com.gnarly.engine.model.Vao;
import com.gnarly.engine.shaders.Shader;
import com.gnarly.engine.shaders.Shader2t;
import com.gnarly.engine.texture.TextureSet;
import com.gnarly.game.enemies.Enemy;
import org.joml.Vector2f;

import java.util.ArrayList;

public class BulletList {

	private static final double SPF = 1.0 / 20.0;

	private static TextureSet[] sprites = null;
	private static Vao vao;
	private Shader2t shader = Shader.SHADER2T;

	public static class Bullet {
		public double lifetime;
		public int sprite;
		public Vector2f position;
		public Vector2f side0;
		public Vector2f side1;
		public Vector2f velocity;

		public float width;
		public float height;
		public float rotation;

		public Vector2f boundingPosition;
		public Vector2f bounds;

		public int damage;
	}

	public static final Vector2f[] DIMENSIONS = new Vector2f[] {
		new Vector2f(4, -8),
		new Vector2f(4, -8)
	};

	public static final int
		TYPE_PLAYER = 0x00,
		TYPE_SMOL   = 0x01;

	private Camera camera;

	private ArrayList<Bullet> bullets;

	public BulletList(Camera camera) {
		this.camera = camera;
		bullets = new ArrayList<>();

		if (sprites == null) {
			sprites = new TextureSet[2];
			sprites[0] = new TextureSet(new String[] {
				"res/img/bullets/bullet-0/bullet-0.png",
				"res/img/bullets/bullet-0/bullet-1.png",
				"res/img/bullets/bullet-0/bullet-2.png",
				"res/img/bullets/bullet-0/bullet-3.png"
			});
			sprites[1] = new TextureSet(new String[] {
				"res/img/bullets/bullet-1/bullet-0.png",
				"res/img/bullets/bullet-1/bullet-1.png",
				"res/img/bullets/bullet-1/bullet-2.png",
				"res/img/bullets/bullet-1/bullet-3.png"
			});
			float vertices[] = {
				1, 1, 0, // Top left
				1, 0, 0, // Bottom left
				0, 0, 0, // Bottom right
				0, 1, 0  // Top right
			};
			int indices[] = {
				0, 1, 3,
				1, 2, 3
			};
			float[] texCoords = {
				1, 0,
				1, 1,
				0, 1,
				0, 0
			};
			vao = new Vao(vertices, indices);
			vao.addAttrib(texCoords, 2);
		}
	}

	public void update(ArrayList<Enemy> enemies) {
		for (int i = 0; i < bullets.size(); ++i) {
			Bullet bullet = bullets.get(i);
			Vector2f deltaV = bullet.velocity.mul((float) Main.dtime, new Vector2f());
			for (int j = 0; j < enemies.size(); ++j) {
				if (Collision.collide(enemies.get(j).getPosition(), enemies.get(j).getDimensions(), enemies.get(j).getVelocity(), bullet)) {
					enemies.get(j).damage(bullet.damage);
					if (enemies.get(j).isDead()) {
						enemies.remove(j);
						--j;
					}
					bullets.remove(i);
					--i;
				}
			}
			bullet.position.add(deltaV);
			bullet.boundingPosition.add(deltaV);
			bullet.lifetime += Main.dtime;

			if (bullet.boundingPosition.x > camera.getWidth()   || bullet.boundingPosition.y > camera.getHeight()
			 || bullet.boundingPosition.x + bullet.bounds.x < 0 || bullet.boundingPosition.y + bullet.bounds.y < 0) {
				bullets.remove(i);
				--i;
			}
		}
	}

	public void update(Player player) {
		for (int i = 0; i < bullets.size(); ++i) {
			Bullet bullet = bullets.get(i);
			Vector2f deltaV = bullet.velocity.mul((float) Main.dtime, new Vector2f());
			Collision.Bounds bounds = player.getBounds();
			Vector2f[] segments = player.getSegments();
			if (Collision.collide(bounds, segments, player.getVelocity(), bullet)) {
				player.damage(bullet.damage);
				bullets.remove(i);
				--i;
			}

			bullet.position.add(deltaV);
			bullet.boundingPosition.add(deltaV);
			bullet.lifetime += Main.dtime;

			if (bullet.boundingPosition.x > camera.getWidth()   || bullet.boundingPosition.y > camera.getHeight()
			 || bullet.boundingPosition.x + bullet.bounds.x < 0 || bullet.boundingPosition.y + bullet.bounds.y < 0) {
				bullets.remove(i);
				--i;
			}
		}
	}

	public void render() {
		for (int i = 0; i < bullets.size(); ++i) {
			Bullet bullet = bullets.get(i);
			sprites[bullet.sprite].get((int) (bullet.lifetime / SPF) % sprites[bullet.sprite].length()).bind();
			shader.enable();
			shader.setMVP(camera.getMatrix().translate(bullet.position.x, bullet.position.y, -0.25f).rotateZ(bullet.rotation).scale(bullet.width, bullet.height, 1));
			vao.render();
		}
	}

	public void spawnBullet(Vector2f position, float rotation, int type, int speed, int damage) {
		spawnBullet(position, DIMENSIONS[type], rotation, speed, type, damage);
	}

	private void spawnBullet(Vector2f position, Vector2f dimensions, float rotation, float velocity, int sprite, int damage) {
		float sin = (float) -Math.sin(rotation);
		float cos = (float)  Math.cos(rotation);

		float initialPosition = -dimensions.x / 2;

		/**************************************
		 *                                    *
		 *   2D Rotations                     *
		 *                                    *
		 **************************************
		 *                                    *
		 *   x + yi * cos + sini              *
	     *   (xcos - ysin) + (xsin + ycos)i   *
		 *                                    *
		 **************************************/
		Bullet bullet = new Bullet();
		bullet.position = new Vector2f(initialPosition * cos, initialPosition * sin);
		bullet.position.add(position);
		bullet.side0 = new Vector2f( dimensions.x * cos, dimensions.x * sin);
		bullet.side1 = new Vector2f(-dimensions.y * sin, dimensions.y * cos);
		bullet.velocity = new Vector2f(velocity * sin, -velocity * cos);

		bullet.width  = dimensions.x;
		bullet.height = dimensions.y;
		bullet.rotation = -rotation;

		bullet.lifetime = 0;
		bullet.sprite = sprite;
		bullet.damage = damage;

		Vector2f p0 = new Vector2f(bullet.position);
		Vector2f p1 = new Vector2f(bullet.position).add(bullet.side0);
		Vector2f p2 = new Vector2f(bullet.position).add(bullet.side1);
		Vector2f p3 = new Vector2f(bullet.position).add(bullet.side0).add(bullet.side1);

		bullet.boundingPosition = new Vector2f(
			min(new float[]{ p0.x, p1.x, p2.x, p3.x }),
			min(new float[]{ p0.y, p1.y, p2.y, p3.y })
		);
		bullet.bounds = new Vector2f(
			max(new float[]{ p0.x, p1.x, p2.x, p3.x }),
			max(new float[]{ p0.y, p1.y, p2.y, p3.y })
		).sub(bullet.boundingPosition);

		bullets.add(bullet);
	}

	public static float getWidth(int type) {
		return DIMENSIONS[type].x;
	}

	public static float getHeight(int type) {
		return DIMENSIONS[type].y;
	}

	private float min(float[] nums) {
		float min = nums[0];
		for (int i = 1; i < nums.length; ++i)
			if (nums[i] < min)
				min = nums[i];
		return min;
	}

	private float max(float[] nums) {
		float max = nums[0];
		for (int i = 1; i < nums.length; ++i)
			if (nums[i] > max)
				max = nums[i];
		return max;
	}
}
