import codedraw.CodeDraw;

/**
 * This class represents celestial bodies like stars, planets, asteroids, etc...
 */
public class Body implements Massive {
    private final double mass;
    private Vector3 massCenter; // position of the mass center.
    private Vector3 currentMovement;

    public Body(double mass, Vector3 massCenter, Vector3 currentMovement) {
        this.mass = mass;
        this.massCenter = massCenter;
        this.currentMovement = currentMovement;
    }

    public Body(Body other) {
        this.mass = other.mass;
        this.massCenter = new Vector3(other.massCenter);
        this.currentMovement = new Vector3(other.currentMovement);
    }

    public Body(double mass) {
        this(mass, new Vector3(), new Vector3());
    }

    public Body() {
        this(0);
    }

    public void setState(Vector3 position, Vector3 velocity) {
        this.massCenter = new Vector3(position);
        this.currentMovement = new Vector3(velocity);
    }

    /**
     * Returns the distance between the mass centers of this body and the specified body 'b'.
     */
    public double distanceTo(Body b) {
        return massCenter.distanceTo(b.massCenter);
    }

    /**
     * Returns a vector representing the gravitational force exerted by 'b' on this body.
     * The gravitational Force F is calculated by F = G*(m1*m2)/(r*r), with m1 and m2 being the
     * masses of the objects interacting, r being the distance between the centers of the masses
     * and G being the gravitational constant.
     * Hint: see simulation loop in Simulation.java to find out how this is done.
     */
    public Vector3 gravitationalForce(Body b) {
        if (b == this) return new Vector3();
        Vector3 direction = b.massCenter.minus(massCenter);
        double distance = direction.length();
        if (distance == 0) return new Vector3();
        direction.normalize();
        double force = Simulation.G * mass * b.mass / (distance * distance);
        return direction.times(force);
    }

    /**
     * Moves this body to a new position, according to the specified force vector 'force' exerted
     * on it, and updates the current movement accordingly.
     * (Movement depends on the mass of this body, its current movement and the exerted force.)
     * Hint: see simulation loop in Simulation.java to find out how this is done.
     */
    public void move(Vector3 force) {
        // F = m*a -> a = F/m
        Vector3 newPosition = massCenter.plus(force.times(1.0 / mass)).plus(currentMovement);

        // new minus old position.
        Vector3 newMovement = newPosition.minus(massCenter);

        // update body state
        massCenter = newPosition;
        currentMovement = newMovement;
    }

    /**
     * Returns the approximate radius of this body.
     * (It is assumed that the radius r is related to the mass m of the body by r = m ^ 0.5,
     * where m and r measured in solar units.)
     */
    public double radius() {
        return SpaceDraw.massToRadius(mass);
    }

    /**
     * Return the mass of the Body.
     */
    public double mass() {
        return mass;
    }

    public Vector3 massCenter() {
        return massCenter;
    }

    public boolean collidesWith(Body body) {
        return this.distanceTo(body) < this.radius() + body.radius();
    }

    /**
     * Returns a new body that is formed by the collision of this body and 'b'. The impulse
     * of the returned body is the sum of the impulses of 'this' and 'b'.
     */
    public Body merge(Body body) {
        double totalMass = this.mass + body.mass;
        return new Body(
                totalMass,
                this.massCenter.times(this.mass).plus(body.massCenter.times(body.mass)).times(1.0 / totalMass),
                this.currentMovement.times(this.mass).plus(body.currentMovement.times(body.mass)).times(1.0 / totalMass)
        );
    }

    /**
     * Draws the body to the specified canvas as a filled circle.
     * The radius of the circle corresponds to the radius of the body
     * (use a conversion of the real scale to the scale of the canvas as
     * in 'Simulation.java').
     * Hint: call the method 'drawAsFilledCircle' implemented in 'Vector3'.
     */
    public void draw(CodeDraw cd) {
        cd.setColor(SpaceDraw.massToColor(mass));
        massCenter.drawAsFilledCircle(cd, SpaceDraw.massToRadius(mass));
    }

    /**
     * Returns a string with the information about this body including
     * mass, position (mass center) and current movement. Example:
     * "5.972E24 kg, position: [1.48E11,0.0,0.0] m, movement: [0.0,29290.0,0.0] m/s."
     */
    @Override
    public String toString() {
        return String.format(
                "%g kg, position: %s m, movement: %s m/s.",
                mass, massCenter.toString(), currentMovement.toString()
        );
    }
}
