package com.draga.spaceTravels3.physic;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Vector2;
import com.draga.spaceTravels3.Constants;
import com.draga.spaceTravels3.component.PhysicsComponent;
import com.draga.spaceTravels3.gameEntity.GameEntity;
import com.draga.spaceTravels3.manager.GameEntityManager;
import com.draga.spaceTravels3.manager.SettingsManager;
import com.draga.utils.JavaUtils;
import com.google.common.base.Stopwatch;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

public class PhysicsEngine
{
    private static final String LOGGING_TAG =
        PhysicsEngine.class.getSimpleName();

    private static Stopwatch updateTimer = Stopwatch.createUnstarted();
    private static float updateTime;

    public static float getUpdateTime()
    {
        return updateTime;
    }

    public static void update(float elapsed)
    {
        updateTimer.start();

        for (int step = 0; step < Constants.Game.PHYSICS_STEPS; step++)
        {
            GameEntityManager.update();
            step(elapsed / Constants.Game.PHYSICS_STEPS);
        }

        updateTime = updateTimer.elapsed(TimeUnit.NANOSECONDS) * Constants.General.NANO;
        updateTimer.reset();
    }

    /**
     * Performs a physic step. Applies gravity, velocity and angular velocity, then checks for
     * collisions.
     */
    private static void step(float deltaTime)
    {
        for (GameEntity gameEntity : GameEntityManager.getGameEntities())
        {
            stepPhysicsComponent(gameEntity.physicsComponent, deltaTime);
        }

        checkCollisions();
    }

    /**
     * Steps the gravity (if enables and is affected by), velocity and angular velocity for a
     * {@link PhysicsComponent}
     */
    private static void stepPhysicsComponent(PhysicsComponent physicsComponent, float deltaTime)
    {
        if (!SettingsManager.getDebugSettings().noGravity
            && physicsComponent.isAffectedByGravity())
        {
            stepGravity(physicsComponent, deltaTime);
        }

        stepVelocity(physicsComponent, deltaTime);
        stepAngularVelocity(physicsComponent, deltaTime);

    }

    /**
     * Check for collisions following this pattern to avoid duplicates:
     * \ X0 \ X1 \ X2
     * Y0 \    \    \
     * Y1 \ X  \    \
     * Y2 \ X  \ X  \
     */
    private static void checkCollisions()
    {
        for (int x = 1; x < GameEntityManager.getGameEntities().size(); x++)
        {
            GameEntity gameEntityA = GameEntityManager.getGameEntities().get(x);
            for (int y = 0; y < x; y++)
            {
                GameEntity gameEntityB = GameEntityManager.getGameEntities().get(y);
                if (areColliding(gameEntityA.physicsComponent, gameEntityB.physicsComponent))
                {
                    Gdx.app.debug(
                        LOGGING_TAG,
                        "Collision between "
                            + gameEntityA.getClass().getSimpleName()
                            + " and "
                            + gameEntityB.getClass().getSimpleName());
                    CollisionResolver.resolve(gameEntityA, gameEntityB);
                }
            }
        }
    }

    /**
     * Accelerate the {@link PhysicsComponent} according to the gravity acting on it.
     */
    private static void stepGravity(PhysicsComponent physicsComponent, float deltaTime)
    {
        Vector2 gravityForce = calculateGravityForce(physicsComponent);

        physicsComponent.getVelocity().add(gravityForce.scl(deltaTime));
    }

    /**
     * Applies the {@link PhysicsComponent}'s velocity to its position.
     */
    private static void stepVelocity(PhysicsComponent physicsComponent, float deltaTime)
    {
        physicsComponent.getPosition()
            .add(physicsComponent.getVelocity().cpy().scl(deltaTime));
    }

    /**
     * Applies the {@link PhysicsComponent}'s angular velocity to its rotation.
     */
    private static void stepAngularVelocity(PhysicsComponent physicsComponent, float deltaTime)
    {
        physicsComponent.setAngle(physicsComponent.getAngle()
            + physicsComponent.getAngularVelocity() * deltaTime);
    }

    /**
     * Check if both {@link PhysicsComponent} can collide with each other and if the two are
     * overlapping.
     */
    private static boolean areColliding(
        PhysicsComponent physicsComponentA,
        PhysicsComponent physicsComponentB)
    {
        return physicsComponentA.getCollideWith().contains(physicsComponentB.getIs())
            && physicsComponentB.getCollideWith().contains(physicsComponentA.getIs())
            && areOverlapping(physicsComponentA, physicsComponentB);
    }

    public static Vector2 calculateGravityForce(PhysicsComponent physicsComponent)
    {
        Vector2 totalForce = new Vector2();
        for (GameEntity gameEntity : GameEntityManager.getGameEntities())
        {
            if (!physicsComponent.equals(gameEntity.physicsComponent))
            {
                Vector2 force =
                    calculateGravityForce(physicsComponent, gameEntity.physicsComponent);
                totalForce.add(force);
            }
        }

        return totalForce;
    }

    private static boolean areOverlapping(
        PhysicsComponent physicsComponentA,
        PhysicsComponent physicsComponentB)
    {
        float overlappingDistance =
            physicsComponentA.getShape().radius + physicsComponentB.getShape().radius;
        float distance = physicsComponentA.getPosition().dst(physicsComponentB.getPosition());

        boolean areOverlapping = distance <= overlappingDistance;

        return areOverlapping;
    }

    private static Vector2 calculateGravityForce(
        PhysicsComponent physicsComponentA,
        PhysicsComponent physicsComponentB)
    {
        Vector2 distance = physicsComponentB.getPosition()
            .cpy()
            .sub(physicsComponentA.getPosition());
        float distanceLen2 = distance.len2();

        Vector2 direction = distance.nor();

        float force = physicsComponentA.getMass()
            * physicsComponentB.getMass()
            / distanceLen2;

        Vector2 gravityForce = direction.scl(force);

        return gravityForce;
    }

    private static Vector2 calculateGravityForceActingOn(PhysicsComponent physicsComponent)
    {
        Vector2 totalForce = new Vector2();

        for (GameEntity otherGameEntity : GameEntityManager.getGameEntities())
        {
            PhysicsComponent otherPhysicsComponent = otherGameEntity.physicsComponent;

            if (!physicsComponent.equals(otherPhysicsComponent))
            {
                Vector2 force = calculateGravityForce(physicsComponent, otherPhysicsComponent);
                totalForce.add(force);
            }
        }

        return totalForce;
    }

    public static void create()
    {
        updateTimer = Stopwatch.createUnstarted();
    }
    
    public static void dispose()
    {

    }

    public static ArrayList<Vector2> projectGravity(
        PhysicsComponent originalPhysicsComponent,
        int steps,
        float stepTime)
    {
        // Swap out physics component so to revert after projection.
        PhysicsComponent physicsComponent = new PhysicsComponent(originalPhysicsComponent);

        ArrayList<Vector2> projections = new ArrayList<>();

        buildProjections:
        for (int i = 0; i < steps; i++)
        {
            // This cannot use calculateGravityForce() because it wouldn't consider the original
            // physic component as the same and would calculate the force between the two.
            Vector2 gravity = new Vector2();
            for (GameEntity gameEntity : GameEntityManager.getGameEntities())
            {
                if (!originalPhysicsComponent.equals(gameEntity.physicsComponent))
                {
                    Vector2 force =
                        calculateGravityForce(physicsComponent, gameEntity.physicsComponent);
                    gravity.add(force);
                }
            }
            physicsComponent.getVelocity().add(gravity.scl(stepTime));

            // Move.
            stepVelocity(physicsComponent, stepTime);

            projections.add(physicsComponent.getPosition().cpy());

            // Stop on collision.
            for (GameEntity otherGameEntity : GameEntityManager.getGameEntities())
            {
                PhysicsComponent otherPhysicsComponent = otherGameEntity.physicsComponent;
                if (!originalPhysicsComponent.equals(otherPhysicsComponent)
                    && areColliding(physicsComponent, otherPhysicsComponent))
                {
                    break buildProjections;
                }
            }
        }

        return projections;
    }
}

@@ -374,42 +374,8 @@
 er;%0A
-import com.draga.utils.JavaUtils;%0A
 impo
@@ -6239,601 +6239,8 @@
  %7D%0A%0A
-    private static Vector2 calculateGravityForceActingOn(PhysicsComponent physicsComponent)%0A    %7B%0A        Vector2 totalForce = new Vector2();%0A%0A        for (GameEntity otherGameEntity : GameEntityManager.getGameEntities())%0A        %7B%0A            PhysicsComponent otherPhysicsComponent = otherGameEntity.physicsComponent;%0A%0A            if (!physicsComponent.equals(otherPhysicsComponent))%0A            %7B%0A                Vector2 force = calculateGravityForce(physicsComponent, otherPhysicsComponent);%0A                totalForce.add(force);%0A            %7D%0A        %7D%0A%0A        return totalForce;%0A    %7D%0A%0A
     

remove duplicated method