# Naivna metoda za simuliranje gravitacije n teles.

import math
from multiprocessing import Process, Queue # Vzporedno računanje
import queue
from telo import Telo

# Če je razdalja med telesoma manjša od R_MIN, neelastično trčita
R_MIN = 0.0025

class Simulator(Process):

    def __init__(self, telesa, vrsta, dt, G):
        super().__init__() # Pokličemo konstruktor nadrazreda Process
        self.daemon = True # Če ukinemo uporabniški vmesnik, tudi ta proces umre
        self.dt = dt # časovni korak
        self.G = G # gravitacijska konstanta
        self.iteracija = 0 # zaporedna številka iteracije
        self.telesa = telesa # seznam teles
        self.vrsta = vrsta # vrsta za komuniciranje z uporabniškim vmesnikom
        self._osvezi_zoom() # osveži faktor povečave

    def _osvezi_zoom(self):
        """Osveži self.zoom, da se vsa telesa vidijo."""
        self.zoom = 0.01
        for t in self.telesa:
            self.zoom = max(self.zoom, abs(t.x), abs(t.y))
        self.zoom *= 1.1

    def run(self):
        """Funkcija, ki se zažene, ko sprožimo simulacijo."""
        speedup = 0 # Trenutna pohitritev (koliko iteracij naredimo na eno animacijo)
        max_speedup = 200 # Največja dovoljena pohitritev
        while True:
            self.korak()
            self.iteracija += 1
            try:
                # V vrsto damo seznam naborov (x, y, r), po enega na telo,
                # faktor povečave, številko iteracijo in pohitritev.
                # Če je vrta polna in smo že dosegli navečjo pohitritev, čakamo
                pocakaj = (speedup > max_speedup)
                self.vrsta.put((self.telesa, self.zoom, self.iteracija, speedup),
                               block=pocakaj)
                # Sem pridemo, če smo uspešno vstavili podatke v vrsto
                speedup = 0
            except queue.Full:
                # Sem pridemo, če je vrsta polna, kar pomeni, da animacija še ni
                # osvežila stanja, torej lahko računamo naslednjo iteracijo
                speedup += 1

    def korak(self):
        """Izračunaj en korak simulacije."""
        # Izračunamo trke med telesi
        zbrisi = set() # IDji teles, ki jih bomo zbrisali, ker so se zlila z drugim telesom
        telesa = [] # novo stanje teles
        n = len(self.telesa)
        for i in range(n):
            t = self.telesa[i] # prvo telo
            if id(t) in zbrisi:
                # t je že zbrisan, preskoči ga
                continue
            for j in range(i+1, n):
                u = self.telesa[j] # drugo telo
                if id(u) in zbrisi:
                    # u je že zbrisan, preskoči ga
                    continue
                # izračunamo kvadrat razdalje med t in u
                dx = t.x - u.x
                dy = t.y - u.y
                r2 = dx * dx + dy * dy
                if r2 < R_MIN * R_MIN:
                    # t in u sta preblizu, u trči v t
                    t.trk(u)
                    zbrisi.add(id(u))
            telesa.append(t)
        # Osvežimo stanje teles po trkih
        self.telesa = telesa
        # Premaknemo telesa
        telesa = []
        for t in self.telesa:
            (ax, ay) = (0.0, 0.0) # skupni pospešek na telo t
            for u in self.telesa:
                if t is u:
                    continue
                # Izračunamo pospešek, ki ga u povzroči na t
                dx = t.x - u.x
                dy = t.y - u.y
                r3 = (dx * dx + dy * dy) ** 1.5
                A = - self.G * u.m / r3
                ax += A * dx
                ay += A * dy
            # Nova pozicija t
            x = t.x + t.vx * self.dt + 0.5 * ax * self.dt * self.dt
            y = t.y + t.vy * self.dt + 0.5 * ay * self.dt * self.dt
            # Nova hitrost t
            vx = t.vx + ax * self.dt
            vy = t.vy + ay * self.dt
            # Dodamo t med telesa
            telesa.append(Telo(x, y, vx, vy, t.m))
        # Posodobimo telesa in faktor povečave
        self.telesa = telesa
        self._osvezi_zoom()
