package de.frajul.endlessroll.main.physics;

import android.support.annotation.Nullable;

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

import de.frajul.endlessroll.entities.Entity;
import de.frajul.endlessroll.entities.Obstacle;
import de.frajul.endlessroll.entities.collectables.Collectables;
import de.frajul.endlessroll.entities.collectables.Energy;
import de.frajul.endlessroll.entities.collectables.Star;
import de.frajul.endlessroll.entities.collision.CollisionDetector;
import de.frajul.endlessroll.entities.collision.collisionData.EntityCollisionData;
import de.frajul.endlessroll.entities.collision.collisionData.ObstacleCollisionData;
import de.frajul.endlessroll.entities.collision.collisionData.PlayerCollisionData;
import de.frajul.endlessroll.entities.collision.collisionData.ToolCollisionData;
import de.frajul.endlessroll.entities.collision.geometry.Circle;
import de.frajul.endlessroll.entities.tileLists.Ceiling;
import de.frajul.endlessroll.entities.tileLists.Terrain;
import de.frajul.endlessroll.entities.tileLists.Tile;
import de.frajul.endlessroll.entities.tools.Bomb;
import de.frajul.endlessroll.entities.tools.Tool;
import de.frajul.endlessroll.main.game.GameScene;
import de.frajul.endlessroll.main.game.Scene;
import de.frajul.endlessroll.main.game.Timer;

/**
 * Created by Julian on 27.11.2015.
 */
public class Physics {

    public final float GRAVITY_FORCE = .0000025f;
    private final float TOOL_ON_OBSTACLE_LEFT_EDGE_TOLERANCE = 0.05f;
    private CollisionDetector detector;

    public Physics() {
        detector = new CollisionDetector();
    }

    public void applyGravity(GameScene scene, Timer timer) {
        float gravity = GRAVITY_FORCE * timer.getFrameTimeSeconds();

        scene.getPlayer().setGravityForce(-gravity);

        synchronized (scene.getTools()) {
            for (Tool tool : scene.getTools()) {
                if (tool.isFloating())
                    continue;
                tool.getMovement().y -= gravity * 2;
            }
        }
    }

    public synchronized void checkToolCollision(Scene scene) {
        synchronized (scene.getTools()) {
            for (Tool tool : scene.getTools()) {
                checkSingleToolCollision(tool, scene);
            }
        }
    }

    public synchronized void checkSingleToolCollision(Tool tool, Scene scene) {
        if(tool.isFloating())
            return;
        float terrainEdge = getTerrainEdge(tool, scene.getTerrain());
        Obstacle toolIsCollidingWith = getHighestObstacleToolIsCollidingWith(tool, scene);

        if (toolIsCollidingWith != null) {
            float distObstTool = tool.getRightEdge() - toolIsCollidingWith.getLeftEdge();
            if (distObstTool < TOOL_ON_OBSTACLE_LEFT_EDGE_TOLERANCE) {
                tool.getPosition().x -= TOOL_ON_OBSTACLE_LEFT_EDGE_TOLERANCE;
                terrainEdge = getTerrainEdge(tool, scene.getTerrain());
                toolIsCollidingWith = getHighestObstacleToolIsCollidingWith(tool, scene);
            }
        }

        float orientingHeight = terrainEdge;
        if (toolIsCollidingWith != null)
            orientingHeight = Math.max(toolIsCollidingWith.getTopEdge(), terrainEdge);

        if (tool.getBottomEdge() <= orientingHeight) {
            tool.getMovement().y = 0;
            if (tool instanceof Bomb)
                tool.setFloating(true);
            else
                tool.setToTerrain(orientingHeight);
        }
    }

    @Nullable
    private Obstacle getHighestObstacleToolIsCollidingWith(Tool tool, Scene scene) {
        List<Obstacle> collisionObstacles = new ArrayList<>();
        synchronized (scene.getObstacles()) {
            for (Obstacle obstacle : scene.getObstacles()) {
                if (tool.getWorldCollisionBounds().isCollisionWithQuad(obstacle, detector)) {
                    collisionObstacles.add(obstacle);
                }
            }
        }
        Obstacle highest = null;
        for (Obstacle obstacle : collisionObstacles)
            if (highest == null || highest.getTopEdge() < obstacle.getTopEdge())
                highest = obstacle;
        return highest;
    }

    private float getTerrainEdge(Entity tool, Terrain terrain) {
        for (Tile instance : terrain) {
            if ((tool.getLeftEdge() >= instance.getLeftEdge() && tool.getLeftEdge() <= instance
                    .getRightEdge()) || (tool.getRightEdge() <= instance.getRightEdge() && tool
                    .getRightEdge() >= instance.getLeftEdge()) || (instance.getLeftEdge() >= tool
                    .getLeftEdge() && instance.getLeftEdge() <= tool.getRightEdge()) || (instance
                    .getRightEdge() <= tool.getRightEdge() && instance.getRightEdge() >= tool
                    .getLeftEdge()))
                return terrain.getEdge();
        }
        return -10;
    }

    public PlayerCollisionData getPlayerCollisionData(GameScene scene) {
        EntityCollisionData terrainData = playerCollidesWithTerrain(scene);
        EntityCollisionData ceilingData = playerCollidesWithCeiling(scene);
        ObstacleCollisionData obstacleData = playerCollidesWithObstacle(scene);
        ToolCollisionData toolData = playerCollidesWithTool(scene);
        EntityCollisionData starData = playerCollidesWithStar(scene);
        EntityCollisionData energyData = playerCollidesWithEnergy(scene);
        return new PlayerCollisionData(terrainData, ceilingData, obstacleData, toolData, starData,
                energyData);
    }

    private EntityCollisionData playerCollidesWithTerrain(GameScene scene) {
        Terrain terrain = scene.getTerrain();
        for (Tile terrainTile : terrain) {
            EntityCollisionData data = detector
                    .playerEntityCollision(scene.getPlayer(), terrainTile);
            if (data.isCollision())
                return data;
        }
        return new EntityCollisionData(null, null);
    }

    private EntityCollisionData playerCollidesWithCeiling(GameScene scene) {
        Ceiling ceiling = scene.getCeiling();
        for (Tile ceilingTile : ceiling) {
            EntityCollisionData data = detector
                    .playerEntityCollision(scene.getPlayer(), ceilingTile);
            if (data.isCollision())
                return data;
        }
        return new EntityCollisionData(null, null);
    }

    private ObstacleCollisionData playerCollidesWithObstacle(GameScene scene) {
        List<EntityCollisionData> collisions = new ArrayList<>();
        synchronized (scene.getObstacles()) {
            for (Obstacle obstacle : scene.getObstacles()) {
                EntityCollisionData data = detector
                        .playerEntityCollision(scene.getPlayer(), obstacle);
                if (data.isCollision())
                    collisions.add(data);
            }
        }
        return new ObstacleCollisionData(collisions);
    }

    private ToolCollisionData playerCollidesWithTool(GameScene scene) {
        List<Tool> tools = new ArrayList<>();
        Circle circle = new Circle(scene.getPlayer());
        synchronized (scene.getTools()) {
            for (Tool tool : scene.getTools()) {
                if (tool.getPlayerCollisionBounds().isCollisionWithCircle(circle, detector))
                    tools.add(tool);
            }
        }
        return new ToolCollisionData(tools);
    }

    private EntityCollisionData playerCollidesWithStar(GameScene scene) {
        Collectables collectables = scene.getCollectables();
        synchronized (collectables.getStars()) {
            for (Star star : collectables.getStars()) {
                EntityCollisionData data = detector.playerEntityCollision(scene.getPlayer(), star);
                if (data.isCollision())
                    return data;
            }
        }
        return new EntityCollisionData(null, null);
    }

    private EntityCollisionData playerCollidesWithEnergy(GameScene scene) {
        Energy energy = scene.getCollectables().getEnergy();
        if (energy != null) {
            EntityCollisionData data = detector.playerEntityCollision(scene.getPlayer(), energy);
            return data;
        } else
            return new EntityCollisionData(null, null);
    }
}
