package de.frajul.endlessroll.entities.collision;

import de.frajul.endlessroll.data.Vector;
import de.frajul.endlessroll.entities.Entity;
import de.frajul.endlessroll.entities.Player;
import de.frajul.endlessroll.entities.collision.collisionData.EntityCollisionData;
import de.frajul.endlessroll.entities.collision.geometry.Circle;
import de.frajul.endlessroll.entities.collision.geometry.Quad;
import de.frajul.endlessroll.entities.collision.geometry.Triangle;
import de.frajul.endlessroll.main.GameLog;

/**
 * Created by Julian on 01.12.2015.
 */
public class CollisionDetector {

    private CircleTriangleCollisionDetector triangleDetector;

    public CollisionDetector() {
        triangleDetector = new CircleTriangleCollisionDetector();
    }

    public boolean isCircleCircleCollision(Circle c1, Circle c2) {
        float distance = Math.abs(c1.getCenter().vectorTo(c2.getCenter()).length());
        float radiusSum = c1.getRadius() + c2.getRadius();
        return distance < radiusSum;
    }

    public boolean isCircleQuadCollision(Circle circle, Quad quad) {
        Vector distance = circle.getCenter().vectorTo(quad.getPosition());
        distance.x = Math.abs(distance.x);
        distance.y = Math.abs(distance.y);

        if (distance.x > circle.getRadius() + quad.getWidth() / 2)
            return false;
        if (distance.y > circle.getRadius() + quad.getHeight() / 2)
            return false;

        if (distance.x <= quad.getWidth() / 2)
            return true;
        if (distance.y <= quad.getHeight() / 2)
            return true;

        float cornerDistance_sq = (distance.x - quad.getWidth() / 2) * (distance.x - quad
                .getWidth() / 2) + (distance.y - quad.getHeight() / 2) * (distance.y - quad
                .getHeight() / 2);
        return cornerDistance_sq <= circle.getRadius() * circle.getRadius();
    }

    public boolean isCircleTriangleCollision(Circle circle, Triangle triangle) {
        return triangleDetector.circleTriangleCollision(circle, triangle);
    }

    public boolean isQuadQuadCollision(Quad q1, Quad q2) {
        float xDistance = Math.abs(q2.getPosition().x - q1.getPosition().x);
        float yDistance = Math.abs(q2.getPosition().y - q1.getPosition().y);
        if (xDistance >= q1.getWidth() / 2 + q2.getWidth() / 2)
            return false;
        if (yDistance >= q1.getHeight() / 2 + q2.getHeight() / 2)
            return false;
        return true;
    }

    public boolean isQuadTriangleCollision(Quad quad, Triangle triangle) {
        boolean quadVertex1InTriangle = isVertexInTriangle(
                new Vector(quad.getLeftEdge(), quad.getTopEdge()), triangle);
        if (quadVertex1InTriangle)
            return true;
        boolean quadVertex2InTriangle = isVertexInTriangle(
                new Vector(quad.getRightEdge(), quad.getTopEdge()), triangle);
        if (quadVertex2InTriangle)
            return true;
        boolean quadVertex3InTriangle = isVertexInTriangle(
                new Vector(quad.getRightEdge(), quad.getBottomEdge()), triangle);
        if (quadVertex3InTriangle)
            return true;
        boolean quadVertex4InTriangle = isVertexInTriangle(
                new Vector(quad.getLeftEdge(), quad.getBottomEdge()), triangle);
        if (quadVertex4InTriangle)
            return true;
        boolean triangleVertex1InQuad = isVertexInQuad(triangle.getVertex1(), quad);
        if (triangleVertex1InQuad)
            return true;
        boolean triangleVertex2InQuad = isVertexInQuad(triangle.getVertex2(), quad);
        if (triangleVertex2InQuad)
            return true;
        boolean triangleVertex3InQuad = isVertexInQuad(triangle.getVertex3(), quad);
        if (triangleVertex3InQuad)
            return true;
        return false;
    }

    public boolean isTriangleTriangleCollision(Triangle triangle1, Triangle triangle2) {
        boolean triangle1Vertex1InTriangle2 = isVertexInTriangle(triangle1.getVertex1(), triangle2);
        if (triangle1Vertex1InTriangle2)
            return true;
        boolean triangle1Vertex2InTriangle2 = isVertexInTriangle(triangle1.getVertex2(), triangle2);
        if (triangle1Vertex2InTriangle2)
            return true;
        boolean triangle1Vertex3InTriangle2 = isVertexInTriangle(triangle1.getVertex3(), triangle2);
        if (triangle1Vertex3InTriangle2)
            return true;
        boolean triangle2Vertex1InTriangle1 = isVertexInTriangle(triangle2.getVertex1(), triangle1);
        if (triangle2Vertex1InTriangle1)
            return true;
        boolean triangle2Vertex2InTriangle1 = isVertexInTriangle(triangle2.getVertex2(), triangle1);
        if (triangle2Vertex2InTriangle1)
            return true;
        boolean triangle2Vertex3InTriangle1 = isVertexInTriangle(triangle2.getVertex3(), triangle1);
        if (triangle2Vertex3InTriangle1)
            return true;
        return false;
    }


    private boolean isVertexInTriangle(Vector vertex, Triangle triangle) {
        Vector vertex1To2 = triangle.getVertex1().vectorTo(triangle.getVertex2());
        Vector vertex2To3 = triangle.getVertex2().vectorTo(triangle.getVertex3());
        Vector vertex3To1 = triangle.getVertex3().vectorTo(triangle.getVertex1());
        Vector vertex1ToCenter = triangle.getVertex1().vectorTo(vertex);
        Vector vertex2ToCenter = triangle.getVertex2().vectorTo(vertex);
        Vector vertex3ToCenter = triangle.getVertex3().vectorTo(vertex);

        boolean centerInsideV1V2 = vertex1To2.y * vertex1ToCenter.x - vertex1To2.x * vertex1ToCenter.y < 0;
        boolean centerInsideV2V3 = vertex2To3.y * vertex2ToCenter.x - vertex2To3.x * vertex2ToCenter.y < 0;
        boolean centerInsideV3V1 = vertex3To1.y * vertex3ToCenter.x - vertex3To1.x * vertex3ToCenter.y < 0;

        return centerInsideV1V2 && centerInsideV2V3 && centerInsideV3V1;
    }

    private boolean isVertexInQuad(Vector vertex, Quad quad) {
        return vertex.getX() >= quad.getLeftEdge() && vertex.getX() <= quad.getRightEdge() && vertex
                .getY() >= quad.getBottomEdge() && vertex.getY() <= quad.getTopEdge();
    }


    public EntityCollisionData playerEntityCollision(Player player, Entity entity) {
        Circle circle = new Circle(player);
        Quad quad = entity;
        if (isCircleQuadCollision(circle, quad)) {
            Edge edge = circleQuadCollisionEdge(circle, player.getMovement(), quad,
                    entity.getMovement());
            //Obstacles with MoveComponent do not have entity.getMovement() --> if player not moving, relativeMovement.x = 0
            return new EntityCollisionData(entity, edge);
        }
        return new EntityCollisionData(null, null);
    }

    private Edge circleQuadCollisionEdge(Circle circle, Vector circleMovement, Quad quad, Vector quadMovement) {
        Vector relativeMovement = circleMovement.translate(quadMovement.negate());

        //LEFT || TOP || BOTTOM
        if (relativeMovement.x > 0) {
            //LEFT || BOTTOM
            if (relativeMovement.y > 0) {
                float toLeftDistance = quad.getLeftEdge() - circle.getCenter().x - circle.getRadius();
                float actualY = toLeftDistance * (relativeMovement.y / relativeMovement.x) + circle
                        .getCenter().y + circle.getRadius();
                if (actualY <= quad.getBottomEdge())
                    return Edge.BOTTOM;
                else
                    return Edge.LEFT;
            }
            //LEFT || TOP
            else if (relativeMovement.y < 0) {
                float toLeftDistance = quad.getLeftEdge() - circle.getCenter().x - circle.getRadius();
                float actualY = toLeftDistance * (relativeMovement.y / relativeMovement.x) + circle
                        .getCenter().y - circle.getRadius();
                if (actualY > quad.getTopEdge())
                    return Edge.TOP;
                else
                    return Edge.LEFT;
            } else {
                return Edge.LEFT;
            }
        }
        //RIGHT || TOP || BOTTOM
        else if (relativeMovement.x < 0) {
            //RIGHT || BOTTOM
            if (relativeMovement.y > 0) {
                float toRightDistance = quad.getRightEdge() - circle.getCenter().x + circle.getRadius();
                float actualY = toRightDistance * (relativeMovement.y / relativeMovement.x) + circle
                        .getCenter().y + circle.getRadius();
                if (actualY <= quad.getBottomEdge())
                    return Edge.BOTTOM;
                else
                    return Edge.RIGHT;
            }
            //RIGHT || TOP
            else if (relativeMovement.y < 0) {
                float toRightDistance = quad.getRightEdge() - circle.getCenter().x + circle.getRadius();
                float actualY = toRightDistance * (relativeMovement.y / relativeMovement.x) + circle
                        .getCenter().y - circle.getRadius();
                if (actualY > quad.getTopEdge())
                    return Edge.TOP;
                else
                    return Edge.RIGHT;
            } else {
                return Edge.RIGHT;
            }
        } else {
            if (relativeMovement.y > 0)
                return Edge.BOTTOM;
            else if (relativeMovement.y < 0)
                return Edge.TOP;
        }
        return Edge.NONE;
    }
}
