package algs61; // section 6.1
import stdlib.*;
import java.awt.Color;
import algs24.MinPQ;
/* ***********************************************************************
 *  Compilation:  javac CollisionSystem.java
 *  Execution:    java CollisionSystem N               (N random particles)
 *                java CollisionSystem < input.txt     (from a file)
 *
 *  Creates N random particles and simulates their motion according
 *  to the laws of elastic collisions.
 *
 *************************************************************************/

public class CollisionSystem {
	private MinPQ<Event> pq;        // the priority queue
	private double t  = 0.0;        // simulation clock time
	private final double hz = 0.5;        // number of redraw events per clock tick
	private final Particle[] particles;   // the array of particles

	// create a new collision system with the given set of particles
	public CollisionSystem(Particle[] particles) {
		this.particles = particles;
	}

	// updates priority queue with all new events for particle a
	private void predict(Particle a, double limit) {
		if (a == null) return;

		// particle-particle collisions
		for (Particle particle : particles) {
			double dt = a.timeToHit(particle);
			if (t + dt <= limit)
				pq.insert(new Event(t + dt, a, particle));
		}

		// particle-wall collisions
		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));
	}

	// redraw all particles
	private void redraw(double limit) {
		StdDraw.clear();
		for (Particle particle : particles) {
			particle.draw();
		}
		StdDraw.show(20);
		if (t < limit) {
			pq.insert(new Event(t + 1.0 / hz, null, null));
		}
	}


	/* ******************************************************************************
	 *  Event based simulation for limit seconds
	 ********************************************************************************/
	public void simulate(double limit) {

		// initialize PQ with collision events and redraw event
		pq = new MinPQ<>(100000);
		for (Particle particle : particles) {
			predict(particle, limit);
		}
		pq.insert(new Event(0, null, null));        // redraw event


		// the main event-driven simulation loop
		while (!pq.isEmpty()) {

			// get impending event, discard if invalidated
			Event e = pq.delMin();
			if (!e.isValid()) continue;
			Particle a = e.a;
			Particle b = e.b;

			// physical collision, so update positions, and then simulation clock
			for (Particle particle : particles)
				particle.move(e.time - t);
			t = e.time;

			// process event
			if      (a != null && b != null) a.bounceOff(b);              // particle-particle collision
			else if (a != null && b == null) a.bounceOffVerticalWall();   // particle-wall collision
			else if (a == null && b != null) b.bounceOffHorizontalWall(); // particle-wall collision
			else if (a == null && b == null) redraw(limit);               // redraw event

			// update the priority queue with new collisions involving a or b
			predict(a, limit);
			predict(b, limit);
		}
	}


	/* ***********************************************************************
	 *  An event during a particle collision simulation. Each event contains
	 *  the time at which it will occur (assuming no supervening actions)
	 *  and the particles a and b involved.
	 *
	 *    -  a and b both null:      redraw event
	 *    -  a null, b not null:     collision with vertical wall
	 *    -  a not null, b null:     collision with horizontal wall
	 *    -  a and b both not null:  binary collision between a and b
	 *
	 *************************************************************************/
	private static class Event implements Comparable<Event> {
		public final double time;         // time that event is scheduled to occur
		public final Particle a, b;       // particles involved in event, possibly null
		public final int countA, countB;  // collision counts at event creation


		// create a new event to occur at time t involving a and b
		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;
		}

		// compare times when two events will occur
		public int compareTo(Event that) {
			if      (this.time < that.time) return -1;
			else if (this.time > that.time) return +1;
			else                            return  0;
		}

		// has any collision occurred between when event was created and now?
		public boolean isValid() {
			if (a != null && a.count() != countA) return false;
			if (b != null && b.count() != countB) return false;
			return true;
		}

	}



	/* ******************************************************************************
	 *  Sample client
	 ********************************************************************************/
	public static void main(String[] args) {
		//args = new String[]{ "20" };
		//StdIn.fromFile ("data/brownian.txt");
		StdIn.fromFile ("data/diffusion.txt");

		// remove the border
		StdDraw.setXscale(1.0/22.0, 21.0/22.0);
		StdDraw.setYscale(1.0/22.0, 21.0/22.0);

		// turn on animation mode
		StdDraw.show(0);

		// the array of particles
		Particle[] particles;

		// create N random particles
		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();
		}

		// or read from standard input
		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);
			}
		}

		// create collision system and simulate
		CollisionSystem system = new CollisionSystem(particles);
		system.simulate(10000);
	}

}
