/** SolarSystem.class by Daniel Piron (Queens College Nucleus)
 ** -------------------------------------------------------------------------
 ** This class is basically a linked list which has been modified to serve
 ** the specific purpose of simulating a solar system, and rendering the
 ** celestial bodies there-in graphically, and real-time.
 **/

 import java.awt.Graphics;
 import java.awt.Color;

 public class SolarSystem {

    // I could not come up with cute little names for these without
    // compromising clarity. These are your basic head and tail pointers
    // of a linked list.

    CelestialBody head, tail;

    private int   count;
    float xView, yView;          // position of center of viewspace.
    int   viewHeight, viewWidth; // dimensions of the screen (we need this for
                                 // scaling.

    CelestialBody focusPoint;    // a body we may be following.

    float zoomScale; // factor by which to zoom, so that we may fit the solar
                     // system within frame (NOTE: We will need a function
                     // that will literally zoom to a distance that allow us
                     // to view the system in it's entirity.)

    public SolarSystem() {

       head = tail = null;
       zoomScale = 1.0f;
       CelestialBody.GRAVCONST = .0008f;
       count = 0;

       setView(0, 0);
       setFocus(null);

       } // ends no argument constructor

    public SolarSystem(float grav) {

       head = tail = null;
       zoomScale = 1.0f;
       CelestialBody.GRAVCONST = grav;
       count = 0;

       } // ends no argument constructor

    public void setView(float x, float y) {

       xView = x;
       yView = y;

       } // ends setView

    public void setFocus(CelestialBody b) { focusPoint = b; }

    public int getCount() { return count; }

    public void setViewSpace(int vx, int vy) {

       viewWidth = vx;
       viewHeight = vy;

       } // ends setViewSpace

    public void insertHead(CelestialBody body) {

       body.setNext(head);  // place ourselves behind the head.
       // if there is a head, set this body as it's previous.
       if(head == null) tail = body;
       else head.setPrev(body);

       body.setPrev(null); // not entirly necessary, but just in case.
       head = body;
       count++;

       } // ends method insertHead.

    public void insertTail(CelestialBody body) {

       if(head == null) head = body;

       body.setPrev(tail);
       if(tail != null) tail.setNext(body);

       tail = body;
       tail.setNext(null); 

       count++;

       } // ends method insertHead.


    public CelestialBody removeHead() {

      CelestialBody body;

      body = head;
      if(head != null) {
         head = head.getNext();
         head.setPrev(null);
         } // ends if

      count--;

      return body;

      } // ends method removeHead

   public void remove(CelestialBody n) {

      if(head == null) return;
      
      if(n.getPrev() != null) n.getPrev().setNext(n.getNext());
      else head = n.getNext();
      
      if(n.getNext() != null) n.getNext().setPrev(n.getPrev());
      else tail = n.getPrev();
	 	
      n.setNext(null);
      n.setPrev(null);

      count--;

      } // ends remove


    public void shatterBody(CelestialBody body, float mx, float my) {

       CelestialBody newbie;
       float energy, mass,  rad, dx, dy, vx, vy, nx, ny, theta;
       mass = body.getMass();
       rad = body.getRadius();
       int numPieces, i;

       energy = (float)Math.sqrt((body.getXVel() * body.getXVel()) +
                          (body.getYVel() * body.getYVel()));

       numPieces = (int)(Math.random() * 8);

       mass /= (float)numPieces;       
       rad /= (float)numPieces;
       energy /= (float)numPieces;

       energy /= mass;

       // remove(body);

       vx = body.getXVel();
       vy = body.getYVel();
       nx = body.getXPos();
       ny = body.getYPos();

       for(i = 0; i < numPieces; i++) {

           theta = (float)(Math.random() * (Math.PI * 2.0f));
           dx = (energy * (float)Math.cos(theta)) + vx;
           dy = (energy * (float)Math.sin(theta)) + vy;

           if(i == 0) {
                      body.setPosition(nx + dx, ny + dy);
                      body.setVelocity(dx, dy);
                      body.setMass(mass);
                      body.setRadius(rad);
                      } // ends if
           else { 
              newbie = new CelestialBody(nx + dx, ny + dy,
                                        dx, dy, mass, rad);

           // newbie.setColor(Color.red);
              insertTail(newbie);
              } // ends else

           }

       } // ends method shatterBody

    public void workCollision(CelestialBody body) {
       CelestialBody prev;
       float m1X, m1Y, m2X, m2Y, totalMass, nx, ny;

       prev = body.getPrev();
       while(prev != null) {
          if(body.weCollide(prev)) {
             
             if(body.getMass() > prev.getMass() * 15) {
                body.setMass(body.getMass() + prev.getMass());
                remove(prev);
                }
             else if(prev.getMass() > body.getMass() * 15) {
                prev.setMass(body.getMass() + prev.getMass());
                remove(body);
                break;
                }
             else {

               m1X = body.getXVel() * body.getMass();
               m1Y = body.getYVel() * body.getMass();
               m2X = prev.getXVel() * prev.getMass();
               m2Y = prev.getYVel() * prev.getMass();

               if(body.getMass() > 1.0f) shatterBody(body, m2X, m2Y);
               if(prev.getMass() > 1.0f) shatterBody(prev, m1X, m1Y);
               }
             } 
             /*
             m1X = body.getXVel() * body.getMass();
             m1Y = body.getYVel() * body.getMass();
             m2X = prev.getXVel() * prev.getMass();
             m2Y = prev.getYVel() * prev.getMass();
   
             totalMass = body.getMass() + prev.getMass();
             // calculate new velocity after inelastic collision.
             nx = (m1X + m2X) / totalMass;
             ny = (m1X + m2Y) / totalMass;
  
             body.setVelocity(nx, ny);
             prev.setVelocity(nx, ny);
             
             } // ends if */

          prev = prev.getPrev();

          } // ends while loop

       } // ends method workCollision.
  

    /**
     ** Update's each of the celestial bodies in the solar system
     **/
    public void update() {

       CelestialBody cur;

       cur = head;

       while(cur != null) {

          workCollision(cur);
          cur.update();

          cur = cur.getNext();

          } // ends while loop.

       } // ends update method

    public void createBody(float xp, float yp, float xv, float yv, float m, float r, boolean flags) {

       CelestialBody body;
       float invZoom = 1.0f / zoomScale;

       xp -= (viewWidth /2);
       yp -= (viewHeight /2);

       xp = (xp * invZoom) + xView;
       yp = (yp * invZoom) + yView;
       xv *= invZoom;
       yv *= invZoom;

       // if there is a focal point, set our velocity relative to it's.
       if(focusPoint != null) {

          xv += focusPoint.getXVel();
          yv += focusPoint.getYVel();

          } // ends if statement.

       body = new CelestialBody(xp, yp, xv, yv, m, r);

       insertHead(body);

       }

    public CelestialBody catchBody(float x1, float y1, float x2, float y2) {

       CelestialBody cur;
       float invZoom = 1.0f / zoomScale;
       float x, y, temp;

       // make sure x1 is less than x2.
       if(x1 > x2) {
         temp = x1;
         x1 = x2;
         x2 = temp;
         }

       if(y1 > y2) {
         temp = y1;
         y1 = y2;
         y2 = temp;
         }

       x1 = x1 - (viewWidth / 2) - xView;
       x1 *= zoomScale;
       
       y1 = y1 - (viewHeight / 2) - yView;
       y1 *= zoomScale;

       x2 = x2 - (viewWidth / 2) - xView;
       x1 *= zoomScale;
       
       y2 = y2 - (viewHeight / 2) - yView;
       y1 *= zoomScale;

       cur = head;
       while(cur != null) {

          x = cur.getXPos();
          y = cur.getYPos();

          if((x1 <= x) && (x <= x2) && (y1 <= y) && (y <= y2)) return cur;

          cur = cur.getNext();

          } // ends while loop.

       return null;

       }

    /*
     ** Draws a single celestial body given the current zoomScale of this
     ** Solar System's viewspace, taking coordinates (0, 0) as the center
     ** of the graphic's viewspace.
     */
    public void drawBody(Graphics g, CelestialBody body) {

       int x1, y1, d;
       boolean realTiny = false;

       d = (int)(body.getRadius() * zoomScale * 2); // we need the diameter for the fillOval

       if(d < 1) realTiny = true;       

       x1 = (int)((body.getXPos() - xView - body.getRadius()) * zoomScale);
       y1 = (int)((body.getYPos() - yView - body.getRadius()) * zoomScale);

       // Adding half of the width of the screen to the x component, and
       // half of the height of the screen to the y component, we compensate
       // for the fact that 0,0 is the upper-lefthand corner of the screen.
       // The origin is now at the center of the screen.
       x1 += (viewWidth / 2);
       y1 += (viewHeight / 2);

       g.setColor(body.getColor());
       // if the radius is less than 1 then just draw a single pixel using
       // a line.  This is probably less expensive then using fillOval, and
       // since most of the particals will be REALLY small, I've included
       // this optimization for time purposes.
       if(realTiny) g.drawLine(x1, y1, x1, y1);
       else g.fillOval(x1, y1, d, d);

       } // ends method drawBody

    public void draw(Graphics g) {

       CelestialBody body;

       
       if(focusPoint != null) setView(focusPoint.getXPos(), focusPoint.getYPos());

       body = head;
       while(body != null) {

          drawBody(g, body);

          body = body.getNext();

          } // ends while loop

       } // ends method draw

    } // ends class SolarSystem
