package com.dream.box2d.water;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.Shape;

/**
 * Utilities to calculate the intersection between polygons
 */
public class IntersectionUtils {

	/**
	 * Gets the point where two lines intersect
	 * @param cp1 Polygon side point 1
	 * @param cp2 Polygon side point 2
	 * @param s Line start point
	 * @param e Line end point
	 * @return The point where the two lines intersect or null if the don't cross
	 */
	public static Vector2 intersection(Vector2 cp1, Vector2 cp2, Vector2 s, Vector2 e) {
		Vector2 dc = new Vector2(cp1.x - cp2.x, cp1.y - cp2.y);
		Vector2 dp = new Vector2(s.x - e.x, s.y - e.y);
		float n1 = cp1.x * cp2.y - cp1.y * cp2.x;
		float n2 = s.x * e.y - s.y * e.x;
		float n3 = (dc.x * dp.y - dc.y * dp.x);
		if(n3 != 0){
			n3 = 1.0f / n3;
			return new Vector2((n1 * dp.x - n2 * dc.x) * n3, (n1 * dp.y - n2 * dc.y) * n3);
		}
		
		return null;
	}
	
	/**
	 * Checks if one point is inside of a side of a polygon
	 * @param cp1 Polygon point 1
	 * @param cp2 Polygon point 2
	 * @param p Point to check
	 * @return True if the point is inside of the polygon
	 */
	public static boolean inside(Vector2 cp1, Vector2 cp2, Vector2 p) {
		return (cp2.x - cp1.x) * (p.y - cp1.y) > (cp2.y - cp1.y) * (p.x - cp1.x);
	}

	
	/**
	 * Finds the points where two fixtures intersects
	 * @param fA Fixture A (water)
	 * @param fB Fixture B (dynamic body)
	 * @param outputVertices It will be set with the points that form the result intersection polygon
	 * @return True if the two fixtures intersect
	 */
	public static boolean findIntersectionOfFixtures(Fixture fA, Fixture fB, List<Vector2> outputVertices) {
		// currently this only handles polygon or circles
		if (fA.getShape().getType() != Shape.Type.Polygon && fA.getShape().getType() != Shape.Type.Circle || 
				fB.getShape().getType() != Shape.Type.Polygon && fB.getShape().getType() != Shape.Type.Circle)
			return false;
		
		PolygonShape polyA = new PolygonShape();
		PolygonShape polyB = new PolygonShape();
		
		// if there is a circle, convert to octagon
		if(fA.getShape().getType() == Shape.Type.Circle) 
			polyA = circleToSquare(fA);
		else
			polyA = (PolygonShape) fA.getShape();
		
		if(fB.getShape().getType() == Shape.Type.Circle)
			polyB = circleToSquare(fB);
		else
			polyB = (PolygonShape) fB.getShape();

		// fill subject polygon from fixtureA polygon
		for (int i = 0; i < polyA.getVertexCount(); i++) {
			Vector2 vertex = new Vector2();
			polyA.getVertex(i, vertex);
			vertex = fA.getBody().getWorldPoint(vertex);
			outputVertices.add(new Vector2(vertex));
		}

		// fill clip polygon from fixtureB polygon
		List<Vector2> clipPolygon = new ArrayList<Vector2>();
		for (int i = 0; i < polyB.getVertexCount(); i++) {
			Vector2 vertex = new Vector2();
			polyB.getVertex(i, vertex);
			vertex = fB.getBody().getWorldPoint(vertex);
			clipPolygon.add(new Vector2(vertex));
		}

		Vector2 cp1 = clipPolygon.get(clipPolygon.size() - 1);
		for (int j = 0; j < clipPolygon.size(); j++) {
			Vector2 cp2 = clipPolygon.get(j);
			if (outputVertices.isEmpty())
				return false;
			List<Vector2> inputList = new ArrayList<Vector2>(outputVertices);
			outputVertices.clear();
			Vector2 s = inputList.get(inputList.size() - 1); // last on the input list
			for (int i = 0; i < inputList.size(); i++) {
				Vector2 e = inputList.get(i);
				if (inside(cp1, cp2, e)) {
					if (!inside(cp1, cp2, s)) {
						outputVertices.add(intersection(cp1, cp2, s, e));
					}
					outputVertices.add(e);
				} else if (inside(cp1, cp2, s)) {
					outputVertices.add(intersection(cp1, cp2, s, e));
				}
				s = e;
			}
			cp1 = cp2;
		}

		return !outputVertices.isEmpty();
	}

	/**
	 * Creates a SimplePolygon2d object
	 * @param vertices Vertices of the polygon
	 * @return Polygon result
	 */
	public static Polygon getIntersectionPolygon(List<Vector2> vertices) {
		
		float[] points = new float[vertices.size()*2];
		for(int i=0, j=0; i< vertices.size(); i++, j+=2){
			points[j] = vertices.get(i).x;
			points[j+1] = vertices.get(i).y;
		}
		
		return new Polygon(points);
	}
	
	/**
	 * Because the algorithm is based on vertices, and the circles do not have vertices, we create a square around it.
	 * @param fixture Circle fixture
	 * @return A square instead of the circle
	 */
	private static PolygonShape circleToSquare(Fixture fixture) {
		Vector2 position = fixture.getBody().getLocalCenter();
		float x = position.x;
		float y = position .y;
		float radius = fixture.getShape().getRadius();
		
		PolygonShape octagon = new PolygonShape();
		Vector2[] vertices = new Vector2[4];
		vertices[0] = new Vector2(x-radius, y+radius);
		vertices[1]= new Vector2(x+radius, y+radius);
		vertices[2]= new Vector2(x-radius, y-radius);
		vertices[3]= new Vector2(x+radius, y-radius);
		octagon.set((Vector2[]) vertices);

		return octagon;
	}
	
	/**
	 * Obtains a random vector
	 * @param maxLength Max length
	 * @return A randon vector
	 */
	public static Vector2 getRandomVector(float maxLength) {
		return fromPolar(getRandomFloat(-Math.PI, Math.PI), getRandomFloat(0, maxLength));
	}

	/**
	 * Obtains a random float between min and max value
	 * @param min Min value
	 * @param max Max value
	 * @return Random float
	 */
	private static float getRandomFloat(double min, double max) {
		return (float) (new Random().nextDouble() * (max - min) + min);
	}

	private static Vector2 fromPolar(float angle, float magnitude) {
		Vector2 res = new Vector2((float) Math.cos(angle), (float) Math.sin(angle));
		res.x = res.x * magnitude;
		res.y = res.y * magnitude;

		return res;
	}

}