/***************************************************************************
                          ball.cpp  -  description
                             -------------------
    begin                : Mon Jan 14 2002
    copyright            : (C) 2002 by Samuele Catuzzi
    email                : samuele_catuzzi@yahoo.it
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <bitset>
#include <math.h>
#include "ball.h"
#include "edgeobject.h"

using namespace std;

Ball_Physics::Ball_Physics() {
 vect_Pos.null();
 vect_Vel.null();
}

Ball::Ball(QCanvasPixmapArray *bitmap,QCanvasPixmapArray *shadow_bitmap,QCanvas *canvas,Kbilliards * applic,int number,int Z ) :QCanvasSprite( bitmap ,canvas){
 images=bitmap;
 application=applic;
 setZ(Z);
 ballnumber=number;
 deceleration = 0.028;
 hitedgedecel = 0.020;
 hitballdecel = 0.010;
 setVelocity(0,0);
 anim_speed=12;
 framespeed=0;
 light=false;
 is_draggable=false;
 //graphicvector=new GraphicVector(canvas); //experimental
 shadow=new QCanvasSprite( shadow_bitmap ,canvas);
 shadow->setZ(1); //under all objects
 //graphicvector->show(); //experimental
}

Ball::~Ball() {
 delete(images);
}

/** No descriptions */
void Ball::advance(int stage) {

 setPosition(vect_Pos.x,vect_Pos.y);

 switch(stage) {
   case 0:
     if (visible())
      {
       //if (ballnumber < 2)
       //     cout<<"Ball:"<<ballnumber<<"Position X,Y ="<<vect_Pos.x<<","<<vect_Pos.y<<endl;

       double xvel = vect_Vel.x;
       double yvel = vect_Vel.y;

       QCanvasItemList l=canvas()->collisions(boundingRect());// only rectangular area no chunk!
       l.remove(this);

       QList<EdgeObject> listedge;
       bool is_ledge=false;
       bool hit_edge=false;

       for(QCanvasItemList::Iterator it=l.begin();it != l.end(); ++it)
          {
           QCanvasItem *hit = *it;

           if ( hit->rtti()==2000 ) //check edge items
               if ( hit->collidesWith(this) )
                  {
                   EdgeObject *p=(EdgeObject *)hit;
                   if (( p->name == "hole" )||( p->name == "ledge" ))
                          {
                           listedge.prepend(p);
                           is_ledge=true;
                          }
                  }
          };// end collision-for

       /*
       double modhit;
       double projection;
       */

       for(EdgeObject *edge=listedge.first(); edge != 0; edge=listedge.next())
          {
           /*
           if ( edge->is_a_bouncer )
              {
               modhit=edge->vect_Dir.abs();
               projection=(edge->vect_Dir.x/modhit)*xvel + (edge->vect_Dir.y/modhit)*yvel;

               if ( projection < 0 ) // the ball direction is opposite of the edge one
                  {
                   xvel=xvel - 2*projection*edge->vect_Dir.x;
                   yvel=yvel - 2*projection*edge->vect_Dir.y;

                   setVelocity(xvel,yvel);
                   hit_edge=true;
                  }
              }
           */
           if ( edge->is_a_bouncer )
                hit_edge=true;

           if ( edge->name == "hole" )
              {
               hide();
               application->ingameballs.remove(this);
               application->outgameballs.append(this);
               setVelocity(0,0);
               application->BallinHole(ballnumber);
               break;
              }

          }//end for rtti==2000

       double speed;

       speed=sqrt(xvel*xvel + yvel*yvel);

       double cos,sin;
       cos = xvel/speed ;
       sin = yvel/speed ;
       double total_deceleration=deceleration;

       if ( hit_edge ) total_deceleration += hitedgedecel;

       if ( speed < total_deceleration ) // stop a ball !
           {
            xvel=0;
            yvel=0;
            speed=0;
           }
       else { // total deceleration:
             speed -= total_deceleration ;
             xvel=speed*cos ;
             yvel=speed*sin ;
            }

       setVelocity(xvel,yvel);

       //update shadow position:
       shadow->move(x()-3,y()+3);
       //no shadow near bottom border:
       if (( vect_Pos.y > 352 )||( vect_Pos.y < 46 )) shadow->hide();
       else shadow->show();

      } else {
       //if ball is not visible, then shadow hide()
       shadow->hide();
      }
      break;
   case 1:

       framespeed=(framespeed + 1)%anim_speed;// animation speed
       if (( framespeed == 0 ) && light ) {
           if ( frame() == 0 ) {
                setFrame(1);
                shadow->setFrame(1);
           }
           else {
                setFrame(0);
                shadow->setFrame(0);
           }
       }
       break;
   } // end switch

   //graphicvector->setDirection(vect_Pos.x,vect_Pos.y,vect_Pos.x + vect_Vel.x,vect_Pos.y + vect_Vel.y); //experimental
}

/** No descriptions */
int Ball::rtti() const {
 return 4000;
}

/** No descriptions */
void Ball::setPosition(double xval,double yval) {
 vect_Pos.x=xval;
 vect_Pos.y=yval;
 move(xval-(diameter/2),yval-(diameter/2));
 shadow->move(xval-(diameter/2) -3,yval-(diameter/2) +3);
}

/** No descriptions */
void Ball::DragTo(double xval,double yval) {
 if ( is_draggable && ( xval > 51 ) && ( xval < 693 ) && ( yval > 48 ) && ( yval < 348 ))  {
     if ( ! application->otherBallpresence( ballnumber,xval,yval) ) {
          vect_Pos.x=xval;
          vect_Pos.y=yval;
          move(xval-(diameter/2),yval-(diameter/2));
          shadow->move(xval-(diameter/2) -3,yval-(diameter/2) +3);
     }
 }
}

/** No descriptions */
void Ball::setVelocity(double xval,double yval) {
 vect_Vel.x=xval;
 vect_Vel.y=yval;
 QCanvasSprite::setVelocity(xval,yval);
}

/** No descriptions */
double Ball::xVelocity() {
 return  vect_Vel.x;
}

/** No descriptions */
double Ball::yVelocity() {
 return  vect_Vel.y;
}

/** No descriptions */
double Ball::xcenter() {
 return  vect_Pos.x; //cambiato
}

/** No descriptions */
double Ball::ycenter() {
 return  vect_Pos.y; //cambiato
}

/** No descriptions */
bool Ball::stopped() {
 return (( xVelocity() == 0 ) && ( yVelocity() == 0 )) ;
}

/** Time left before ball to ball collision ( -1 no collision ) */
double Ball::BallCPrediction(Ball *B) {

 double deltaVx,deltaVy,dx,dy;
 double a,b,c;

 //vettore differenza
 deltaVx = vect_Vel.x - B->vect_Vel.x;
 deltaVy = vect_Vel.y - B->vect_Vel.y;
 //distanza
 dx = vect_Pos.x - B->vect_Pos.x;
 dy = vect_Pos.y - B->vect_Pos.y;

 a = deltaVx*deltaVx + deltaVy*deltaVy;

 if ( a == 0.0 ) return -1 ;

 b = 2 * (dx*deltaVx + dy*deltaVy);
 c = dx*dx + dy*dy - diameter*diameter ;
 double result1,result2;
 result1=( ( -b - sqrt(b*b - 4*a*c) ) / (2*a) );
 result2=( ( -b + sqrt(b*b - 4*a*c) ) / (2*a) );
 /*
 if ((result >= 0)&&(result < 1))
 cout<<"Prediction Ball1:"<<ballnumber<<" Ball2:"<<B->ballnumber<<" result="<<result<<" result2="<<result2<<endl;
 */
 //qui sotto filtro via l'evento di due palle che si sfiorano tangentemente in un solo punto
 //cioè non compenetrano ma si toccano, non va considerarla come collisione o si ottiene un loop infinito.
 if (( result1 == result2 )&&( result1 == 0.0 )) return -1;

 if ( result1 < 0.0 ) return -result1;

 return result1;
}

/** No descriptions */
void Ball::DoBallCollision(Ball *b) {

//parametri: BallType * b1, BallType * b2
     /*
    Ball *a=this;
    cout<<"Pre"<<endl;
    cout<<"Ball:"<<a->ballnumber<<" BallCollision vect_Vel X,Y ="<<a->vect_Vel.x<<","<<a->vect_Vel.y<<endl;
    cout<<"Ball:"<<b->ballnumber<<" BallCollision vect_Vel X,Y ="<<b->vect_Vel.x<<","<<b->vect_Vel.y<<endl;
     */

 double xvel=xVelocity();
 double yvel=yVelocity();

 double vdiffx;
 double vdiffy;
 vdiffx= b->xVelocity() - xvel ;
 vdiffy= b->yVelocity() - yvel ;

 double d;
 d=sqrt((b->x() - x())*(b->x() - x()) + (b->y() - y())*(b->y() - y()) );

 double unitdx,unitdy; // d unit vector
 double projection;

 unitdx= (b->x() - x())/d;
 unitdy= (b->y() - y())/d;

 projection=vdiffx*unitdx + vdiffy*unitdy; //this is equivalent

 xvel=xvel + projection*unitdx;
 yvel=yvel + projection*unitdy;
 double bx,by;
 bx=b->xVelocity() - projection*unitdx;
 by=b->yVelocity() - projection*unitdy;
 setVelocity(xvel,yvel);
 b->setVelocity(bx,by );

 double speed;
 speed=sqrt(xvel*xvel + yvel*yvel);

 double cos,sin;
 cos = xvel/speed ;
 sin = yvel/speed ;
 double total_deceleration=deceleration;

 total_deceleration += hitballdecel;

 if ( speed < total_deceleration ) // stop a ball !
     {
      xvel=0;
      yvel=0;
      speed=0;
        }
 else { // total deceleration:
        speed -= total_deceleration ;
        xvel=speed*cos ;
        yvel=speed*sin ;
       }

 setVelocity(xvel,yvel);
 /*
    cout<<"Post"<<endl;
    cout<<"Ball:"<<a->ballnumber<<" BallCollision vect_Vel X,Y ="<<a->vect_Vel.x<<","<<a->vect_Vel.y<<endl;
    cout<<"Ball:"<<b->ballnumber<<" BallCollision vect_Vel X,Y ="<<b->vect_Vel.x<<","<<b->vect_Vel.y<<endl;
 */
}

/** No descriptions */
void Ball::DoEdgeCollision(EdgeObject * edg) {

 double xvel,yvel;
 double xhit,yhit ;
 double modhit,projection;
 xvel=vect_Vel.x;
 yvel=vect_Vel.y;
 xhit=edg->vect_Dir.x ;
 yhit=edg->vect_Dir.y ;

 if (xhit != 0 || yhit != 0)  // x & y direction == 0 -> no bounce
    {
     modhit=sqrt(xhit*xhit + yhit*yhit);
     projection=(xhit/modhit)*xvel + (yhit/modhit)*yvel;
     if ( projection < 0 ) // the ball direction is opposite of the edge one
        {
         xvel=xvel - 2*projection*xhit;
         yvel=yvel - 2*projection*yhit;
         vect_Vel.x=xvel;
         vect_Vel.y=yvel;
         setVelocity(xvel,yvel);
        }
    }
}

/** if true is animated , if false is stopped */
void Ball::setAnimateL(bool anim) {
 if ( anim ) framespeed = 0;
 else {
       setFrame(0);
       shadow->setFrame(0);
 }
 light=anim;
}


void Ball::show() {
      setVisible(TRUE);
      shadow->setVisible(TRUE);
}

void Ball::hide() {
      setVisible(FALSE);
      shadow->setVisible(FALSE);
}
