import codedraw.CodeDraw;

import java.awt.*;
import java.util.Random;

// 1. Datenkapselung: Zusammenfassen von Methoden und Variablen zu Einheit.
//    Die `static` Methoden von `Simulation` nach `Body` oder `Vector3` verschieben.

// 2. Data Hiding: Verstecken vor Zugriffen von außen.
//    Attribute von `Body` und `Vector3` auf `private` setzen.

// 3. Beim Methodenaufruf steht links vom `.` entweder das Objekt, oder die Klasse. In Java beginnen Variablennamen
//    üblicherweise mit Kleinbuchstaben und Klassennamen üblicherweise mit einem Großbuchstaben.

/**
 * Simulates the formation of a massive solar system.
 */
public class Simulation {

    // gravitational constant
    public static final double G = 6.6743e-11;

    // one astronomical unit (AU) is the average distance of earth to the sun.
    public static final double AU = 150e9; // meters

    // one light year
    public static final double LY = 9.461e15; // meters

    // some further constants needed in the simulation
    public static final double SUN_MASS = 1.989e30; // kilograms
    public static final double SUN_RADIUS = 696340e3; // meters
    public static final double EARTH_MASS = 5.972e24; // kilograms
    public static final double EARTH_RADIUS = 6371e3; // meters

    // set some system parameters
    public static final double SECTION_SIZE = 2 * AU; // the size of the square region in space
    public static final int NUMBER_OF_BODIES = 22;
    public static final double OVERALL_SYSTEM_MASS = 20 * SUN_MASS; // kilograms

    // all quantities are based on units of kilogram respectively second and meter.

    /**
     * The main simulation method using instances of other classes.
     */
    public static void main(String[] args) {
        // simulation
        CodeDraw cd = new CodeDraw();
        BodyQueue bodies = new BodyQueue();
        BodyForceMap forceOnBody = new BodyForceMap();

        Random random = new Random(2022);

        for (int i = 0; i < NUMBER_OF_BODIES; i++) {
            bodies.add(new Body(
                    Math.abs(random.nextGaussian()) * OVERALL_SYSTEM_MASS / NUMBER_OF_BODIES, // kg
                    new Vector3(
                            0.2 * random.nextGaussian() * AU,
                            0.2 * random.nextGaussian() * AU,
                            0.2 * random.nextGaussian() * AU
                    ),
                    new Vector3(
                            0 + random.nextGaussian() * 5e3,
                            0 + random.nextGaussian() * 5e3,
                            0 + random.nextGaussian() * 5e3
                    )
            ));
        }

        double seconds = 0;

        // simulation loop
        while (true) {
            BodyQueue newBodies = new BodyQueue(bodies);
            Body[] tmp = new Body[bodies.size()];
            for (int i = 0; bodies.size() > 0; i++) {
                tmp[i] = bodies.poll();
            }
            seconds++; // each iteration computes the movement of the celestial bodies within one second.

            /*
            // merge bodies that have collided
            for (int i = 0; i < bodies.length; i++) {
                for (int j = i + 1; j < bodies.length; j++) {
                    if (bodies[j].collidesWith(bodies[i])) {
                        bodies[i] = bodies[i].merge(bodies[j]);
                        Body[] bodiesOneRemoved = new Body[bodies.length - 1];
                        for (int k = 0; k < bodiesOneRemoved.length; k++) {
                            bodiesOneRemoved[k] = bodies[k < j ? k : k + 1];
                        }
                        bodies = bodiesOneRemoved;

                        // since the body index i changed size there might be new collisions
                        // at all positions of bodies, so start all over again
                        i = -1;
                        j = bodies.length;
                    }
                }
            }
           */

            // for each body (with index i): compute the total force exerted on it.
            for (Body b1 : tmp) {
                Vector3 force = new Vector3(); // begin with zero
                for (Body b2 : tmp) {
                    if (b1 != b2) {
                        force = force.plus(b1.gravitationalForce(b2));
                    }
                }
                forceOnBody.put(b1, force);
            }
            // now forceOnBody[i] holds the force vector exerted on body with index i.

            // for each body (with index i): move it according to the total force exerted on it.
            for (Body body : tmp) {
                body.move(forceOnBody.get(body));
            }

            // show all movements in the canvas only every hour (to speed up the simulation)
            if (seconds % (3600) == 0) {
                // clear old positions (exclude the following line if you want to draw orbits).
                cd.clear(Color.BLACK);

                // draw new positions
                for (Body body : tmp) {
                    body.draw(cd);
                }

                // show new positions
                cd.show();
            }

            bodies = newBodies;
        }
    }
}
