/*
 ** CelestialBody.class - by Daniel Piron (Queens College Nucleus)
 ** -------------------------------------------------------------------------
 ** Objects instanciated to this class represent individual bodies in a solar
 ** system.  I have decided to incorporate the functionality of a Linked List
 ** Node into this class in order to simplify the code a bit.  The data
 ** stored in this class include the X and Y cartesian coordinates of a
 ** celestial body (The system is represented in 2D), it's velocity, mass,
 ** and it's spatial displacement radius (how big it looks on the screen, and
 ** effects how the program decides when two bodies are in contact with each
 ** other.)
 */

import java.awt.Color;      // each body has it's own color.

public class CelestialBody {

   public static float GRAVCONST;
   private Color color;

   // For our linked list functionality.
   private CelestialBody prev, next; 

   // The position assumes that the origin is at the center of the screen,
   // this requires a bit adjustment since the screen's origin is at the
   // upper left-hand corner.
   private float xPos, yPos;  // x and y components of position.
   private float xVel, yVel;  // x and y components of velocity.

   private float xForce, yForce;

   private float mass;        // relative mass of this body.
   private float radius;      // should probably be directly related to the.
                              // mass, but then again, look at pulsars which.
                              // are "tiny," but extremely massive.

   public CelestialBody() {

      setPosition(0, 0);
      setVelocity(0, 0);

      setMass(100);
      setRadius(.5f);

      setColor(Color.white);

      xForce = 0;
      yForce = 0;

      // link to no one.
      prev = null;
      next = null;

      } // ends constructor

   public CelestialBody(float xp, float yp, float xv, float yv, float m, float r) {

      setPosition(xp, yp);
      setVelocity(xv, yv);

      setMass(m);
      setRadius(r);

      setColor(Color.white);

      xForce = 0;
      yForce = 0;

      // link to no one.
      prev = null;
      next = null;

      } // ends constructor

   // Misc Accessor Methods
   public void setColor(Color c) { color = c; }
   public Color getColor() { return color; }

   // VIET
   // Node Accessor Methods
   public void setNext(CelestialBody b) { next = b; }
   public void setPrev(CelestialBody b) { prev = b; }

   public CelestialBody getNext() { return next; }
   public CelestialBody getPrev() { return prev; }

   // MA
   // Data Accessor Methods
   public void setPosition(float x, float y) {

      xPos = x;
      yPos = y;

      } // ends method setPosition.

   public void setVelocity(float x, float y) {

      xVel = x;
      yVel = y;

      } // ends method setPosition.

   public float getXPos() { return xPos; }
   public float getYPos() { return yPos; }

   public float getXVel() { return xVel; }
   public float getYVel() { return yVel; }

   public void setMass(float m) { mass = m; }
   public float getMass() { return mass; }

   public void setRadius(float r) { radius = r; }
   public float getRadius() { return radius; }

   // applies a given force in components to this body.
   public void doForce(float fx, float fy) {

      // the following is applying an acceleration on this body, by adding
      // the force divided by the bodies mass to the velocity of the said
      // body. F = ma, so a = F / m.
      // xVel += fx / mass;
      // yVel += fy / mass;
      xForce += fx;
      yForce += fy;

      } // ends doForce
   /*
   // This funciton calculates a force of gravity between this celestial
   // body and another.  A reference to the other body is required as well
   // as the gravitational constant. (NOTE: this constant won't necessarly
   // by Plank's constant.)
   public void doGravity(CelestialBody b2) {

      float combMass, fx, fy, dx, dy;
      float dist;

      // here we are doing distance calculations by component. We take
      // these distances square them, and divide them by G * m1m2, to get
      // the forces along the respective x and y coordinates.
      dx = (xPos - b2.getXPos());
      dy = (yPos - b2.getYPos());
     
      combMass = mass * b2.getMass() * GRAVCONST; // m1 * m2 * g

      // gravitational constant * mass1 * mass2 divided by distance squared.

      fx = combMass / (dx * dx);
      fy = combMass / (dy * dy);

      if(dx < 0) fx = -fx;
      if(dy < 0) fy = -fy;
     
      // once we have the force components we can apply these forces in
      // opposite direction to the two parties involved...
      doForce(-fx, -fy);    // we want the two bodies to be pulled towards
      b2.doForce(fx, fy);   // each other.

      } // ends method doGravity.
   */

   // This funciton calculates a force of gravity between this celestial
   // body and another.  A reference to the other body is required as well
   // as the gravitational constant. (NOTE: this constant won't necessarly
   // by Plank's constant.)
   public void doGravity(CelestialBody b2) {

      float combMass, fx, fy, dx, dy, f;
      float dist;

      // here we are doing distance calculations by component. We take
      // these distances square them, and divide them by G * m1m2, to get
      // the forces along the respective x and y coordinates.
      dx = (xPos - b2.getXPos());
      dy = (yPos - b2.getYPos());

      dist = (float)Math.sqrt((dx * dx) + (dy * dy));
     
      combMass = mass * b2.getMass() * GRAVCONST; // m1 * m2 * g

      // gravitational constant * mass1 * mass2 divided by distance squared.

      f = combMass / (dist * dist);

      fx = (dx / dist) * f;
      fy = (dy / dist) * f;
     
      // once we have the force components we can apply these forces in
      // opposite direction to the two parties involved...
      doForce(-fx, -fy);    // we want the two bodies to be pulled towards
      b2.doForce(fx, fy);   // each other.

      } // ends method doGravity.

   /**
    ** This method returns true if this and the specified CelestialBody, have
    ** collided with each other.  I would like to have this knowledge for
    ** having a little fun that will remind you all of asteroids.  When bodies
    ** collide I will have them smash each other appart. MUHUHAHAH! ;)
    **/
   public boolean weCollide(CelestialBody b2) {

      float dx, dy, xV2, yV2, xP2, yP2, nVx, nVy;
      float dist, realDist, correct;

      xP2 = b2.getXPos();
      yP2 = b2.getYPos();

      xV2 = b2.getXVel();
      yV2 = b2.getYVel();

      dx = xP2 - xPos;
      dy = yP2 - yPos;
      
      // calculate the distance between the two bodies by using a little bit
      // of pythagorean therom.
      dist = (float)Math.sqrt((dx * dx) + (dy * dy));

      // if the distance between the two bodies is less than sum of the radii,
      // the two bodies have collided.
      // return((radius + b2.getRadius()) > dist);

      realDist = radius + b2.getRadius();
      if(realDist > dist) {
         
         correct = dist / (realDist);

         xPos -= xVel * correct;
         yPos -= yVel * correct;

         xP2 -= xV2 * correct;
         yP2 -= yV2 * correct;

         b2.setPosition(xP2, yP2);

         nVx = (b2.getMass() + mass) * (xV2 - xVel);
         nVy = (b2.getMass() + mass) * (yV2 - yVel);

         if(b2.getMass() > mass) setVelocity(xV2, yV2);
         else b2.setVelocity(xVel, yVel);           

      //   b2.setVelocity(nVx / b2.getMass(), nVy / b2.getMass());
      //   setVelocity(nVx / mass, nVy / mass);

         return true;

         } // ends if

      return false;

      } // ends method weCollide

   /*
    ** Updates a single CelestialBody. Update's position by adding the
    ** velocity components to their respective positional counterparts.
    ** Also calculates gravitational pull using the nifty method I describe
    ** in my article. (NOTE: It is best to calculate gravity, and collision
    ** after the entire system has been updated once. The collision and 
    ** gravitational calculations are done only on the preceding elements
    ** which have already been updated, so all goes well)
    */
   public void update() {

      CelestialBody body;    // place holder for the previous bodies.
      float dist, dx, dy;

      xVel += xForce / mass;
      yVel += yForce / mass;

      xPos += xVel;
      yPos += yVel;

      xForce = yForce = 0;

      body = prev;
      // loop while we are in the middle of the list.
      while(body != null) {

         dx = body.getXPos() - xPos;
         dy = body.getYPos() - yPos;
         dist = (float)Math.sqrt((dx * dx) + (dy * dy));

         if(dist > body.getRadius() + radius) doGravity(body);
         body = body.prev; // set our body pointer to the one just before it.

         } // ends while

      } // ends method update   

   } // ends class CelestialBody
