/*************************************************************************
 *  Kompilacja:  javac CollisionSystem.java
 *  Wykonanie:    java CollisionSystem N               (dla N losowych cząsteczek)
 *                java CollisionSystem < input.txt     (na podstawie pliku) 
 *  
 *  Tworzy N losowych cząsteczek i symuluje ich ruch zgodnie z prawami
 *  zderzeń sprężystych.
 *
 *************************************************************************/

import java.awt.Color;

public class CollisionSystem {
    private MinPQ<Event> pq;        // Kolejka priorytetowa
    private double t  = 0.0;        // Symulacja upływu czasu
    private double Hz = 0.5;        // Liczba zdarzeń ponownego wyświetlania na jednostkę czasu
    private Particle[] particles;   // Tablica cząsteczek

    // Tworzy nowy system zdarzeń na podstawie danego zbioru cząsteczek
    public CollisionSystem(Particle[] particles) {
        this.particles = particles;
    }

    // Aktualizuje kolejką priorytetową za pomocą wszystkich nowych zdarzeń z udziałem cząsteczki a
    private void predict(Particle a, double limit) {
        if (a == null) return;

        // Zderzenia cząsteczka-cząsteczka
        for(int i = 0; i < particles.length; i++) {
            double dt = a.timeToHit(particles[i]);
            if(t + dt <= limit)
                pq.insert(new Event(t + dt, a, particles[i]));
        }

        // Zderzenia cząsteczka-ściana
        double dtX = a.timeToHitVerticalWall();
        double dtY = a.timeToHitHorizontalWall();
        if (t + dtX <= limit) pq.insert(new Event(t + dtX, a, null));
        if (t + dtY <= limit) pq.insert(new Event(t + dtY, null, a));
    }

    // Ponowne wyświetlanie wszystkich cząsteczek
    private void redraw(double limit) {
        StdDraw.clear();
        for(int i = 0; i < particles.length; i++) {
            particles[i].draw();
        }
        StdDraw.show(20);
        if (t < limit) {
            pq.insert(new Event(t + 1.0 / Hz, null, null));
        }
    }

      
   /********************************************************************************
    *  Symulacja sterowana zdarzeniami dla ograniczonej liczby sekund
    ********************************************************************************/
    public void simulate(double limit) {
        
        // Inicjowanie kolejki priorytetowej za pomocą zdarzeń i zdarzeń ponownego wyświetlania
        pq = new MinPQ<Event>();
        for(int i = 0; i < particles.length; i++) {
            predict(particles[i], limit);
        }
        pq.insert(new Event(0, null, null));        // Zdarzenie ponownego wyświetlania


        // Główna pętla symulacji sterowanej zdarzeniami
        while(!pq.isEmpty()) { 

            // Pobieranie oczekującego zdarzenia i odrzucanie go, jeśli zostało unieważnione
            Event e = pq.delMin();
            if(!e.isValid()) continue;
            Particle a = e.a;
            Particle b = e.b;

            // Fizyczne zderzenie, trzeba więc zaktualizować pozycje, a następnie zegar
            for(int i = 0; i < particles.length; i++)
                particles[i].move(e.time - t);
            t = e.time;

            // Przetwarzanie zdarzenia
            if      (a != null && b != null) a.bounceOff(b);              // Zderzenie cząsteczka-cząsteczka
            else if (a != null && b == null) a.bounceOffVerticalWall();   // Zderzenie cząsteczka ściana
            else if (a == null && b != null) b.bounceOffHorizontalWall(); // Zderzenie cząsteczka ściana
            else if (a == null && b == null) redraw(limit);               // Ponowne wyświetlanie

            // Aktualizowanie kolejki priorytetowej przy użyciu nowych zderzeń z udziałem a lub b
            predict(a, limit);
            predict(b, limit);
        }
    }


   /*************************************************************************
    *  Zdarzenie w symulacji zderzeń cząsteczek. Każde zdarzenie obejmuje czas jego
    *  zajścia (jeśli do tego momentu nie wystąpią zakłócające zdarzenia)
    *  i uwzględniane cząsteczki a i b.
    *
    *    -  a i b to null:               ponowne wyświetlanie
    *    -  a null, b różne od null:     zdarzenie z pionową ścianą
    *    -  a różne od null, b null:     zdarzenie z poziomą ścianą
    *    -  a i b różne od null:         zdarzenie a z b
    *
    *************************************************************************/
    private class Event implements Comparable<Event> {
        private final double time;         // Zaplanowany czas zdarzenia
        private final Particle a, b;       // Cząsteczki uczestniczące w zdarzeni (można użyć wartości null)
        private final int countA, countB;  // Liczba zderzeń w momencie utworzenia zdarzenia
                
        
    // Tworzy nowe zdarzenie z udziałem a i b, które ma zajść w czasie t
    public Event(double t, Particle a, Particle b) {
        this.time = t;
        this.a    = a;
        this.b    = b;
        if (a != null) countA = a.count();
        else           countA = -1;
        if (b != null) countB = b.count();
        else           countB = -1;
    }

    // Porównuje czasy zajścia dwóch zdarzeń
    public int compareTo(Event that) {
        if      (this.time < that.time) return -1;
        else if (this.time > that.time) return +1;
        else                            return  0;
    }
        
    // Czy od momentu utworzenia zdarzenia do obecnej chwili zaszły zdarzenia?
    public boolean isValid() {
        if (a != null && a.count() != countA) return false;
        if (b != null && b.count() != countB) return false;
        return true;
    }
   
}



   /********************************************************************************
    *  Przykładowy klient
    ********************************************************************************/
    public static void main(String[] args) {

        // Usuwanie ramki
        StdDraw.setXscale(1.0/22.0, 21.0/22.0);
        StdDraw.setYscale(1.0/22.0, 21.0/22.0);

        // Włączanie trybu animacji
        StdDraw.show(0);

        // Tablica cząsteczek
        Particle[] particles;

        // Tworzenie N losowych cząsteczek
        if (args.length == 1) {
            int N = Integer.parseInt(args[0]);
            particles = new Particle[N];
            for(int i = 0; i < N; i++) particles[i] = new Particle();
        }

        // lub wczytywanie danych ze standardowego wejścia
        else {
            int N = StdIn.readInt();
            particles = new Particle[N];
            for(int i = 0; i < N; i++) {
                double rx     = StdIn.readDouble();
                double ry     = StdIn.readDouble();
                double vx     = StdIn.readDouble();
                double vy     = StdIn.readDouble();
                double radius = StdIn.readDouble();
                double mass   = StdIn.readDouble();
                int r         = StdIn.readInt();
                int g         = StdIn.readInt();
                int b         = StdIn.readInt();
                Color color   = new Color(r, g, b);
                particles[i] = new Particle(rx, ry, vx, vy, radius, mass, color);
            }
        }

        // Tworzenie systemu zdarzeń i symulowanie jego działania
        CollisionSystem system = new CollisionSystem(particles);
        system.simulate(10000);
    }
      
}
