package main.entities;

import ea.Ticker;
import ea.Vektor;
import main.Main;
import main.Tile;
import main.maps.Map;

/*
 * Alles was sich bewegen kann oder nicht an das grid der map gebunden ist, ist ein Entity.
 * Also Der Spieler, Gegner, Items, Coins, ...
 */
public abstract class Entity implements Ticker {

	protected float posX, posY; // Position des entities in gameunits.
	protected float velX, velY; // Geschwindigkeit des eitities in gameunits pro frame.
	protected float accelleration = 0.012f; // Beschleunigung des entities in gameunits pro frame.
	protected float friction = 0.2f; // Entschleunigung des entities in gameunits pro frame.
	protected float width = 1f, height = 1f; // Breite und Höhe der Hitbox.

	// Das ist die Ticker-Methode von ea; wird jeden frame ausgeführt (frameloop)
	@Override
	public void tick() {
		// Geschwindigkeit wird zur position addiert (es gibt natürlich auch negative
		// Ges.)
		posX += velX;
		posY += velY;
		// Reibung wird mit einberechnet, damit nicht unentlich beschleunigt wird
		velX *= (1f - friction);
		velY *= (1f - friction);
		// Wenn die Geschwindigkeit (ungefähr) nicht mehr sichtbar ist wird sie einfach
		// auf 0 gecapt.
		if (velX < 0.001f && velX > -0.001f) {
			velX = 0f;
		}
		if (velY < 0.001f && velY > -0.001f) {
			velY = 0f;
		}
		update();
	}

	/**
	 * Die Update-Methode wird jeden frame ausgeführt. Hier kann man die logik für
	 * sein Entity reinschreiben wenn man sie Überschreibt.
	 */
	protected abstract void update();

	/**
	 * Checkt collision mit tiles von der map. Fertig, clean, nie wieder ändern.
	 */
	public void checkTileCollisions(Map map) {
		for (int x = (int) (posX - 2); x < posX + 2; x++) {
			for (int y = (int) (posY - 2); y < posY + 2; y++) {
				if (x >= 0 && x < map.getWidth() && y >= 0 && y < map.getHeight()) {
					if (map.getTile(x, y).isCollidable()) {
						if (posY + height / 2 > y && posY - height / 2 < y + 1 && posX + width / 2 > x && posX - width / 2 < x + 1) {
							float distN = Math.abs((y + 1 + height / 2) - posY);
							float distE = Math.abs((x + 1 + width / 2) - posX);
							float distS = Math.abs((y - height / 2) - posY);
							float distW = Math.abs((x - width / 2) - posX);
							if (distN <= distE && distN < distS && distN < distW) {
								onBlockCollision(new boolean[] { true, false, false, false }, x, y);
							} else if (distE <= distS && distE < distN && distE < distW) {
								onBlockCollision(new boolean[] { false, true, false, false }, x, y);
							} else if (distS <= distW && distS < distN && distS < distE) {
								onBlockCollision(new boolean[] { false, false, true, false }, x, y);
							} else if (distW <= distS && distW < distN && distW < distE) {
								onBlockCollision(new boolean[] { false, false, false, true }, x, y);
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Gehört mit zur collision detection. Hier werden kollisionen aufgelöst.
	 */
	private void onBlockCollision(boolean[] sides, int blockX, int blockY) {
		if (sides[0]) {
			if (velY < 0) {
				velY = 0;
			}
			posY = blockY + 1 + height / 2;
		}
		if (sides[1]) {
			if (velX < 0) {
				velX = 0;
			}
			posX = blockX + 1 + width / 2;
		}
		if (sides[2]) {
			if (velY > 0) {
				velY = 0;
			}
			posY = blockY - height / 2;
		}
		if (sides[3]) {
			if (velX > 0) {
				velX = 0;
			}
			posX = blockX - width / 2;
		}
	}
	
	/**
	 * @return die entifernung zu diesem Entity
	 */
	public float dist(Entity e) {
		return (float) Math.sqrt((e.posX - posX) * (e.posX - posX) + (e.posY - posY) * (e.posY - posY));
	}
	
	/**
	 * Generiert einen vektor von diesm Entity zu einem anderen
	 */
	public Vektor vectorToEntity(Entity e) {
		return new Vektor(e.posX - posX, e.posY - posY);
	}
	
	protected boolean lineOfSightClear(Entity e) {
		Vektor direct = vectorToEntity(e).normiert();
		direct = direct.teilen(4f);
		Vektor loc = new Vektor(posX, posY);
		float lastDist;
		float dist = Float.MAX_VALUE;
		boolean clear = true;
		do {
			loc = loc.summe(direct);
			lastDist = dist;
			dist = Math.abs(e.posX - loc.x) + Math.abs(e.posY - loc.y);
			Tile t = Main.instance.getWorld().getCurrentMap().getTile((int) loc.x, (int) loc.y);
			if (t != null && t.isCollidable()) {
				clear = false;
				break;
			}
		} while (dist < lastDist);
		return clear;
	}

}
