/*******************************************************************************
 * Copyright 2015 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/


package com.mygdx.game.debugdrawers;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.ai.pfa.Connection;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Bits;
import com.mygdx.game.objects.GameCharacter;
import com.mygdx.game.pathfinding.*;
import com.mygdx.game.steerers.FollowPathSteerer;
import com.mygdx.game.utilities.MyShapeRenderer;

/**
 * @author jsjolund
 */
public class NavMeshDebugDrawer {

	private final Vector3 tmp1 = new Vector3();
	private final Vector3 tmp2 = new Vector3();
	private final Vector3 tmp3 = new Vector3();
	private final Vector3 tmp4 = new Vector3();
	private final Matrix4 tmpMatrix = new Matrix4();
	private final Quaternion tmpQuat = new Quaternion();
	private Bits visibleLayers;
	private MyShapeRenderer shapeRenderer;
	private NavMesh navMesh;

	private boolean triangleIsVisible(Triangle t) {
		return (visibleLayers.nextSetBit(t.meshPartIndex) != -1);
	}

	private void drawVertex(Vector3 pos, float size, Color color) {
		shapeRenderer.setColor(color);
		float offset = size / 2;
		shapeRenderer.box(pos.x - offset, pos.y - offset, pos.z + offset, size, size, size);
	}

	private void drawNavMeshTriangles() {
		shapeRenderer.set(MyShapeRenderer.ShapeType.Line);
		for (int i = 0; i < navMesh.graph.getNodeCount(); i++) {
			Triangle t = navMesh.graph.getTriangleFromGraphIndex(i);
			if (triangleIsVisible(t)) {
				drawTriangle(t, Color.LIGHT_GRAY, 1);
			}
		}
	}

	private void drawNavMeshIndices(SpriteBatch spriteBatch, Camera camera, BitmapFont font) {
		// TODO: Get rid of all the transform matrix setting
		if (spriteBatch.isDrawing()) {
			spriteBatch.end();
		}
		spriteBatch.begin();
		spriteBatch.setProjectionMatrix(camera.combined);
		for (int i = 0; i < navMesh.graph.getNodeCount(); i++) {
			Triangle t = navMesh.graph.getTriangleFromGraphIndex(i);
			if (triangleIsVisible(t)) {
				tmpMatrix.set(camera.view).inv().getRotation(tmpQuat);
				tmpMatrix.setToTranslation(t.centroid).rotate(tmpQuat);
				spriteBatch.setTransformMatrix(tmpMatrix);
				font.draw(spriteBatch, Integer.toString(t.triIndex), 0, 0);
			}
		}
		spriteBatch.end();
	}

	private void drawTriangle(Triangle tri, Color color, float alpha) {
		shapeRenderer.setColor(color.r, color.g, color.b, alpha);
		shapeRenderer.triangle(tri.a, tri.b, tri.c);
	}

	private void drawPathTriangles(NavMeshGraphPath navMeshGraphPath, Triangle currentTriangle) {
		shapeRenderer.set(MyShapeRenderer.ShapeType.Filled);

		if (navMeshGraphPath != null && navMeshGraphPath.getCount() > 0) {

			for (int i = 0; i < navMeshGraphPath.getCount(); i++) {
				Edge e = (Edge) navMeshGraphPath.get(i);
				if (triangleIsVisible(e.fromNode)) {
					if (currentTriangle.getIndex() == e.fromNode.getIndex()) {
						drawTriangle(e.fromNode, Color.RED, 0.2f);
					} else {
						drawTriangle(e.fromNode, Color.YELLOW, 0.2f);
					}
				}
				if (i == navMeshGraphPath.getCount() - 1) {
					if (triangleIsVisible(e.toNode)) {
						if (currentTriangle.getIndex() == e.toNode.getIndex()) {
							drawTriangle(e.toNode, Color.RED, 0.2f);
						} else {
							drawTriangle(e.toNode, Color.YELLOW, 0.2f);
						}
					}
				}
			}
			// Shared triangle edges
			shapeRenderer.set(MyShapeRenderer.ShapeType.Line);
			for (Connection<Triangle> connection : navMeshGraphPath) {
				Edge e = (Edge) connection;
				if (triangleIsVisible(e.fromNode) || triangleIsVisible(e.toNode)) {
					shapeRenderer.line(e.rightVertex, e.leftVertex, Color.GREEN, Color.RED);
				}
			}
		} else if (currentTriangle != null) {
			drawTriangle(currentTriangle, Color.RED, 0.2f);
		}

	}

	private void drawPathPoints(NavMeshPointPath navMeshPointPath) {
		shapeRenderer.set(MyShapeRenderer.ShapeType.Line);
		Vector3 q;
		Vector3 p = navMeshPointPath.getVector(navMeshPointPath.getSize() - 1);
		shapeRenderer.setColor(Color.WHITE);
		for (int i = navMeshPointPath.getSize() - 1; i >= 0; i--) {
			q = navMeshPointPath.getVector(i);
			shapeRenderer.setColor(Color.CYAN);
			shapeRenderer.line(p, q);
			p = q;
			drawVertex(p, 0.02f, Color.WHITE);
		}
	}

	public void drawClosestPointDebug(GameCharacter character) {
		Vector3 position = tmp1;
		Vector3 direction = tmp2;
		Vector3 closestPoint = tmp3;
		Vector3 aimPoint = tmp4;
		float radius = 2;
		character.getDirection(direction);
		character.getGroundPosition(position);
		aimPoint.set(direction).scl(radius).add(position);
		Triangle closest = navMesh.getClosestValidPointAt(position, direction, radius,
				closestPoint, character.visibleOnLayers);

		shapeRenderer.set(MyShapeRenderer.ShapeType.Filled);
		shapeRenderer.setColor(0, 0, 1, 0.2f);
		shapeRenderer.triangle(closest.a, closest.b, closest.c);
		drawVertex(aimPoint, 0.05f, Color.GREEN);
		drawVertex(closestPoint, 0.05f, Color.WHITE);

	}

	public void drawNavMesh(MyShapeRenderer shapeRenderer,
							SpriteBatch spriteBatch,
							NavMesh navMesh,
							GameCharacter character,
							Bits visibleLayers,
							Camera camera,
							BitmapFont font) {

		this.visibleLayers = visibleLayers;
		this.shapeRenderer = shapeRenderer;
		this.navMesh = navMesh;

		if (shapeRenderer.isDrawing()) {
			shapeRenderer.end();
		}

		Gdx.gl.glEnable(GL20.GL_BLEND);
		Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
		shapeRenderer.begin(MyShapeRenderer.ShapeType.Line);
		drawNavMeshTriangles();
		if (character != null && character.steerer instanceof FollowPathSteerer) {
			FollowPathSteerer fpSteerer = (FollowPathSteerer)character.steerer;
			drawPathTriangles(fpSteerer.navMeshGraphPath, character.getCurrentTriangle());
			if (fpSteerer.navMeshPointPath.getSize() > 0) {
				drawPathPoints(fpSteerer.navMeshPointPath);
			}
			drawClosestPointDebug(character);
		}
		shapeRenderer.end();
		Gdx.gl.glDisable(GL20.GL_BLEND);

		drawNavMeshIndices(spriteBatch, camera, font);
	}


}