/* 
    This file handles generic ball

    Copyright, 2003 Caleb Moore
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#include "ball.h"
#include "killerball.h"
#include "game.h"
#include "graphics.h"
#include "boringball.h"
#include "splitball.h"
#include "explosiveball.h"
#include "chronoball.h"
#include "goody.h"
#include <iostream>
using namespace std;

static unsigned int serialupto;

ball::ball()
{
  data = new boringball(this);
  serial = serialupto++;
}
 
ball::ball(char type)
{
  if (type == 'b')
    data = new boringball(this);
  if (type == 's')
    data = new splitball(this);
  if (type == 'e')
    data = new expball(this);
  if (type == 'k')
    data = new kball(this);
  if (type == 'c')
    data = new chronoball(this);
  if (type == 'z')
    data = new lazershot(this);
  if (type == 'l')
    data = new lifeup(this);
  if (type == 'm')
    data = new magnetitem(this);
  if (type == 'd')
    data = new bonus(this);
  if (type == 'r')
    data = new brpu(this);
  serial = serialupto++;
}

ball::ball(const ball & other)
{
  data = other.data->duplicate(this);
  serial = other.serial;
}

ball::ball(ball_base * other)
{
  data = other;
  serial = serialupto++;
}


ball & ball::operator=(const ball & other)
{
  serial = other.serial;
  if (data != NULL)
   delete data;
  data = other.data->duplicate(this);
  return *this;
}

ball_base::ball_base()
{
}

ball_base::ball_base(ball * pack)
{
  package = pack;
}

//updates the ball's position

void ball_base::update(const float amount)
{

  floatpoint tempcenter; 

  tempcenter.x = center.x + velocity.x * amount;
  tempcenter.y = center.y + velocity.y * amount;
  
  //sees if the ball is exceeding it's proper bounds
  if (tempcenter.x - getradius() < int(boundary->leftx) ||
      tempcenter.x + getradius() > int(boundary->rightx))
    {
      velocity.x *= -1;
      return;
    }
  
  if (tempcenter.y - getradius() < int(boundary->topy) ||
      tempcenter.y + getradius() > int(boundary->bottomy))
    {
      velocity.y *= -1;
      return;
    }

  ball * collision;
  //checks to see if the ball is going to crash into another ball
  collision = iscloseto(tempcenter, getradius(), package);

  if (collision != NULL)
    {
      collide(collision);
      return;
    }

  center = tempcenter;
}


//some balls like to do weird stuff after they collide
void ball_base::specialcollide(ball * other)
{
  other->specialcollideb(package);
}

void ball_base::specialcollideb(ball * other)
{
}

//causes the trajectories of two balls to change so they don't overlap
//i.e. they bounce
void ball_base::collide(ball * other)
{
  floatpoint force1, force2;
  double direction;
  double flatv1, flatv2;

  direction = atan2(double(other->getcenter().y - center.y),
		    double(other->getcenter().x - center.x));

  flatv1 = velocity.magval() * cos(direction - velocity.angval());
  flatv2 = other->getvelocity().magval() * 
    cos(direction - other->getvelocity().angval());  
  
  force1.polar(flatv1 * getmass(), direction);
  if (flatv2 < 0)
    force2.polar(flatv2 * other->getmass(), direction);
  else
    force2.polar(0,direction);


  double total = velocity.magval() * getmass() + 
    other->getvelocity().magval() * other->getmass();


  other->data->velocity = other->getvelocity() - force2 / other->getmass() + force1 / other->getmass();
  velocity = velocity - force1 / getmass() + force2 / getmass();
  
  double total2 = velocity.magval() * getmass() + 
    other->getvelocity().magval() * other->getmass();

  other->data->velocity = other->data->velocity * total / total2;
  velocity = velocity * total / total2;


  specialcollide(other);
  //other->specialcollide(package);
}

//checks to see if a ball is inside a square
bool ball_base::isinside(square & bounding)
{
  return bounding.isinside(center);
}

//throwes the ball onto the screen
void ball_base::display()
{
}

//makes a new copy of the ball, simply for internals
ball_base * ball_base::duplicate(ball * pack)
{
  ball_base * output = new ball_base(pack);

  output->setcenter(center);
  output->setvelocity(velocity);
  output->setboundary(boundary);

  return output;
}
