/*****************************************************************************
 *
 *  Copyright (C) 2003 Cédric Brégardis <cedric.bregardis@free.fr>
 *
 *  This file is part of BRIQUOLO
 *
 *  BRIQUOLO 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.
 *
 *  BRIQUOLO 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 BRIQUOLO; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *****************************************************************************/
#include "TableauJeu.h"
#include "BonusFast.h"
#include "BonusSlow.h"
#include "BonusPetit.h"
#include "BonusGros.h"
#include "BonusPetitPlateau.h"
#include "BonusGrosPlateau.h"
#include "BonusDoubleBalle.h"
#include "BonusSuperPuissance.h"
#include "BonusVueSubjective.h"
#include "ExplosionBrique.h"
#include "BibliothequeSon.h"
#include "Option.h"
#include <MOGL_Temps.h>

TableauJeu::TableauJeu(MOGL_Fenetre * p_Fenetre)
  : TableauBase(p_Fenetre), _ProbaBonus(0.2f), _FinTableau(false), _NombreVie(5),
    _VibrationX(0), _VibrationY(0), _CummulDep(0)
{
  Plateau * _Plateau=new Plateau;
  _Plateau->SetPosition(20, 1);
  AjouterPlateau(_Plateau);
  _Camera=new Camera(_Plateau);


  _Univers->SetCamera(_Camera->GetMOGLCamera());

  _TemporisationFinTableau=new MOGL_Temporisation(3, false, false);
  _TemporisationFinTableau->FinTemporisation.Connecter(this, &TableauJeu::_OnFinTemporisation);
  _Univers->AjouterElement(_TemporisationFinTableau);

  _TemporisationDepartBalle=new MOGL_Temporisation(3, false, false);
  _TemporisationDepartBalle->FinTemporisation.Connecter(this, &TableauJeu::_OnDepartBalle);
  _Univers->AjouterElement(_TemporisationDepartBalle);
  _NombreVie--;
  DepartTableau();
}

TableauJeu::~TableauJeu()
{
  for(Set_Balle::iterator itBa=_SetBalle.begin(); itBa!=_SetBalle.end(); itBa++)
  {
    delete (*itBa);
  }

  for(Set_Plateau::iterator itPla=_SetPlateau.begin(); itPla!=_SetPlateau.end(); itPla++)
  {
    delete (*itPla);
  }

  for(Set_Bonus::iterator itBo=_SetBonus.begin(); itBo!=_SetBonus.end(); itBo++)
  {
    delete (*itBo);
  }

  Set_Generateur::iterator itGe;
  for(itGe=_SetGenerateur.begin(); itGe!=_SetGenerateur.end(); itGe++)
  {
    _Univers->RetirerElement(*itGe);
    delete (*itGe);
  }

  for(itGe=_SetGenerateurADetruire.begin(); itGe!=_SetGenerateurADetruire.end(); itGe++)
  {
    _Univers->RetirerElement(*itGe);
    delete (*itGe);
  }

  delete _Camera;
  delete _TemporisationDepartBalle;
  delete _TemporisationFinTableau;
}

void TableauJeu::DepartTableau()
{
    _Camera->SetVueSubjective(false);
    for(Set_Balle::iterator itBa=_SetBalle.begin(); itBa!=_SetBalle.end(); itBa++)
    {
        _Univers->RetirerElement((*itBa)->GetElementArbreMOGL());
        delete (*itBa);
    }
    _SetBalle.clear();

    for(Set_Plateau::iterator itPla=_SetPlateau.begin(); itPla!=_SetPlateau.end(); itPla++)
    {
        (*itPla)->SetPosition(20, 1);

        (*itPla)->SetTaille(5);
    }

    if (_NombreVie>=0)
    {
        ElementTableau::SetDerniereCollision(NULL, NULL);
        Balle * balle=new Balle;
        balle->SetTaille(1.5);
        AjouterBalle(balle);
        _BallePartie=false;
        _TemporisationDepartBalle->Demarrer();

        balle->SetPosition((*_SetPlateau.begin())->GetPositionX(),
                           (*_SetPlateau.begin())->GetPositionY() + (*_SetPlateau.begin())->GetTailleY()/2 + balle->GetRayon());
    }
    else
    {
        FinBalle.Emettre();
    }
    // RAZ du cumul de déplacement
    _CummulDep = 0;  
}

void TableauJeu::AjouterBalle(Balle * p_Balle)
{
  _SetBalle.insert(p_Balle);
  _Univers->AjouterElement(p_Balle->GetElementArbreMOGL());
}

bool TableauJeu::RetirerBalle(Balle * p_Balle)
{
  Set_Balle::iterator it=_SetBalle.find(p_Balle);
  if (it!=_SetBalle.end())
  {
    _Univers->RetirerElement(p_Balle->GetElementArbreMOGL());
    delete (*it);
        
    _SetBalle.erase(it);
    return true;
  }
  else
  {
    return false;
  }
}

void TableauJeu::AjouterPlateau(Plateau * p_Plateau)
{
  p_Plateau->SetMinX(_XMin);
  p_Plateau->SetMaxX(_XMax);
  
  _SetPlateau.insert(p_Plateau);
  _Univers->AjouterElement(p_Plateau->GetElementArbreMOGL());
}

bool TableauJeu::RetirerPlateau(Plateau * p_Plateau)
{
  Set_Plateau::iterator it=_SetPlateau.find(p_Plateau);
  if (it!=_SetPlateau.end())
  {
    _Univers->RetirerElement(p_Plateau->GetElementArbreMOGL());
    _SetPlateau.erase(it);
    return true;
  }
  else
  {
    return false;
  }
}

void TableauJeu::Maj()
{
  Set_Balle setBalleADetruire;
 
  if (!_FinTableau)
  {
    // On détruit les générateur de particules
    for(Set_Generateur::iterator itGenerateur=_SetGenerateurADetruire.begin();
        itGenerateur!=_SetGenerateurADetruire.end();
        itGenerateur++)
    {
      _Univers->RetirerElement(*itGenerateur);
      delete (*itGenerateur);
    }
    _SetGenerateurADetruire.clear();

    // On regarde les bonus pour voir si on en attrape
    Set_Bonus setBonusADetruire;
    Set_Bonus::iterator itBonus;
    for(itBonus=_SetBonus.begin(); itBonus!=_SetBonus.end(); itBonus++)
    {
      for(Set_Plateau::iterator itPlat=_SetPlateau.begin(); itPlat!=_SetPlateau.end(); itPlat++)
      {
        if ((*itBonus)->Mouvement(*itPlat, this) ||
            (*itBonus)->GetPositionY()+(*itBonus)->GetTailleY()/2<_YMin)
        {
          setBonusADetruire.insert(*itBonus);
          break;
        }
      }
    }
    for(itBonus=setBonusADetruire.begin(); itBonus!=setBonusADetruire.end(); itBonus++)
    {
      _Univers->RetirerElement((*itBonus)->GetElementArbreMOGL());
      //delete (*itBonus);

      Set_Bonus::iterator it=_SetBonus.find(*itBonus);
      if (it!=_SetBonus.end())
      {
        delete (*it);
        _SetBonus.erase(it);
      }
    }

    // On fait une mise à jour des élément du tableau
    for(Set_ElementTableau::iterator itElem=_SetElementTableau.begin(); itElem!=_SetElementTableau.end(); itElem++)
    {
      (*itElem)->Maj();
    }

    // Mise à jour de la vibration
    _VibrationX -= MOGL_Temps::GetVariationTemps();
    if (_VibrationX < 0)
    {
      _VibrationX = 0;
    }
    _VibrationY -= MOGL_Temps::GetVariationTemps();
    if (_VibrationY < 0)
    {
      _VibrationY = 0;
    }
    float offx = cosf(_VibrationX * 20000) * 0.2 * _VibrationX;
    float offy = sinf(_VibrationY * 20000) * 0.2 * _VibrationY;
    _Camera->SetOffset(offx, offy);
  
    // On s'occupe de la collision de la balle
    Set_Balle::iterator itBalle;
    for(itBalle=_SetBalle.begin(); itBalle!=_SetBalle.end(); itBalle++)
    {      
      (*itBalle)->MajVitesse();
      float remainingTime = MOGL_Temps::GetVariationTemps();
      while (remainingTime>0)
      {
        (*itBalle)->SetTempsMax(remainingTime);
        // #######################
        // ## On regarde tous les trucs sur lesquels il peut y avoir collision
        // #######################
        Struct_Collision collision, collisionTemps;

        collision.TempsCollision=(*itBalle)->GetTempsMax();
        //ElementTableau * element=NULL;
        bool collisionElementTableau=false;

        //(*itBalle)->PreparerDeplacement(facteur);

        // On regarde tous les plateaux
        if (_BallePartie)
        {
          for(Set_Plateau::iterator itPlat=_SetPlateau.begin(); itPlat!=_SetPlateau.end(); itPlat++)
          {
            if ((*itPlat)->IsDerniereCollisionOk(*itBalle))
            {
              collisionTemps=(*itPlat)->TesterCollision(*itBalle);
              if (collisionTemps.TempsCollision < collision.TempsCollision)
              {
                collision=collisionTemps;
                collisionElementTableau=true;
                collision.ResponsableCollision = *itPlat;
              }
            }
          }
        }

        // On regarde les bords
        collisionTemps=_CollisionBord(*itBalle);
        if (collisionTemps.TempsCollision < collision.TempsCollision)
        {
          collision=collisionTemps;
        }
    
        // On regarde tous les éléments du tableau
        for(Set_ElementTableau::iterator itElem=_SetElementTableau.begin(); itElem!=_SetElementTableau.end(); itElem++)
        {
          if ((*itElem)->IsDerniereCollisionOk(*itBalle))
          {
            collisionTemps=(*itElem)->TesterCollision(*itBalle);
            if (collisionTemps.TempsCollision < collision.TempsCollision)
            {
              collision=collisionTemps;
              collisionElementTableau=true;
              collision.ResponsableCollision = *itElem;
            }
          }
        }

        // #######################
        // Parmis les différentes collisions on regarde cel
        // #######################
        
        // MAJ vitesse déplacement des plateaux selon le déplacement
        // cumulé
        DeplacerPlateaux(_CummulDep);
        // RAZ du cumul de déplacement
        _CummulDep = 0;

        // On déplace les plateaux

        for(Set_Plateau::iterator itPlat=_SetPlateau.begin(); itPlat!=_SetPlateau.end(); itPlat++)
        {
          (*itPlat)->Deplacer(collision.TempsCollision);
        }

        // On déplace la balle
        (*itBalle)->Deplacer(collision.TempsCollision);

        // La structure de collision indique un temps inférieur au temps max de placement.
        // Cela signifie qu'une collision a eu lieu
        if (collision.TempsCollision<remainingTime)
        {
          // Il y a eu collision
          // On modifie donc la balle avec les nouveaux paramètres provemenants de la collision
          //(*itBalle)->SetVitesse(collision.VitesseX, collision.VitesseY);

          // Si c'est un élément on lui dit qu'il a été touché
          bool detruire=false;
          if (collisionElementTableau)
          {
            detruire=collision.ResponsableCollision->Toucher(*itBalle, collision.TempsCollision);
          }
          else
          {
            // C'est pas une brique donc on joue le son du rebond standard
            BibliothequeSon::SonRebond->Jouer(false);
          }

          // On fait apparaitre un système de particules ?
          if (collisionElementTableau)
          {
            Generateur * generateur=collision.ResponsableCollision->CreerGenerateurParticules();
            if (generateur!=NULL)
            {
              AjouterGenerateur(generateur);
            }
          }

          // On stocke ce qui a causé la collision
          if (collisionElementTableau)
          {
            ElementCollision::SetDerniereCollision(collision.ResponsableCollision, *itBalle);
          }
          else
          {
            ElementCollision::SetDerniereCollision(NULL, NULL);
          }

          if (detruire)
          {
            // Il faut détruire l'élément de la collision
            // On fait apparaitre un bonus ?
            Point pt = collision.ResponsableCollision->GetVisualCenter();
            CreerBonus(pt.x, pt.y);

            // On efface l'élément
            ElementTableau * elem = dynamic_cast<ElementTableau *>(collision.ResponsableCollision);
            if (elem != NULL)
            {
              RetirerElementTableau(elem);
            }
            delete collision.ResponsableCollision;
          }

          if (!detruire || !(*itBalle)->GetSuperPuissance())
          {
            (*itBalle)->SetPosition(collision.PositionX, collision.PositionY);
            (*itBalle)->SetVitesseTotale(collision.VitesseX, collision.VitesseY);
            (*itBalle)->AddAcceleration(collision.AddFacteurCollision);
          }

        } 
        else
        {
          // Il n'y a pas eu collision
          //(*itBalle)->SetPosition((*itBalle)->GetArriveeX(), (*itBalle)->GetArriveeY());
          //(*itBalle)->Deplacer(remainingTime);
        }

        // On regarde si la balle disparait
        if ((*itBalle)->GetPositionY()<_YMin)
        {
          setBalleADetruire.insert(*itBalle);
        }
        remainingTime -= collision.TempsCollision;
      }
    }

    for(itBalle=setBalleADetruire.begin(); itBalle!=setBalleADetruire.end(); itBalle++)
    {
      RetirerBalle(*itBalle);
    }

    // On regarde si il reste des éléments à détruire
    unsigned int nbElemReste=0;
    for(Set_ElementTableau::iterator itElem2=_SetElementTableau.begin(); itElem2!=_SetElementTableau.end(); itElem2++)
    {
      if (!(*itElem2)->EstDecors())
      {
        nbElemReste++;
      }
    }
    if (nbElemReste==0)
    {
      _FinTableau=true;
      _TemporisationFinTableau->Demarrer();
    }
    if (_SetBalle.size()==0)
    {
      _NombreVie--;
      DepartTableau();

      for(Set_Bonus::iterator itBo=_SetBonus.begin(); itBo!=_SetBonus.end(); itBo++)
      {
        _Univers->RetirerElement((*itBo)->GetElementArbreMOGL());
        delete (*itBo);
      }
      _SetBonus.clear();

    }
  }

  _Camera->Maj();
}


Struct_Collision TableauJeu::_CollisionBord(Balle * p_Balle)
{
  Struct_Collision collision;
  collision.TempsCollision=p_Balle->GetTempsMax();
  collision.VitesseX=p_Balle->GetVitesseTotaleX();
  collision.VitesseY=p_Balle->GetVitesseTotaleY();
/*  collision.VitesseX=p_Balle->GetVitesseX();
  collision.VitesseY=p_Balle->GetVitesseY();
*/

  float rayon=p_Balle->GetRayon();

  if (p_Balle->GetPositionX()<_XMin+rayon)// && t<collision.PositionCollision)
  {
    collision.TempsCollision=0.5*p_Balle->GetTempsMax();
    collision.PositionX=_XMin+rayon+0.001;
    collision.PositionY= p_Balle->GetPositionY();//t*(p_Balle->GetArriveeY()-p_Balle->GetPositionY())+p_Balle->GetPositionY();
    collision.VitesseX=-p_Balle->GetVitesseTotaleX();
    collision.VitesseY=p_Balle->GetVitesseTotaleY();
  }

//  t=(_XMax-rayon-p_Balle->GetPositionX())/(p_Balle->GetArriveeX()-p_Balle->GetPositionX());
  if (p_Balle->GetPositionX()>_XMax-rayon)// && t<collision.PositionCollision)
  {
    collision.TempsCollision=0.5*p_Balle->GetTempsMax();
    collision.PositionX=_XMax-rayon-0.001;
    collision.PositionY= p_Balle->GetPositionY(); // t*(p_Balle->GetArriveeY()-p_Balle->GetPositionY())+p_Balle->GetPositionY();
    collision.VitesseX=-p_Balle->GetVitesseTotaleX();
    collision.VitesseY=p_Balle->GetVitesseTotaleY();
  }

//  t=(_YMax-rayon-p_Balle->GetPositionY())/(p_Balle->GetArriveeY()-p_Balle->GetPositionY());
  if (p_Balle->GetPositionY()>_YMax-rayon)// && t<collision.PositionCollision)
  {
    collision.TempsCollision=0.5*p_Balle->GetTempsMax();
    collision.PositionY=_YMax-rayon;
    collision.PositionX=p_Balle->GetPositionX(); //t*(p_Balle->GetArriveeX()-p_Balle->GetPositionX())+p_Balle->GetPositionX();
    collision.VitesseY=-p_Balle->GetVitesseTotaleY();
    collision.VitesseX=p_Balle->GetVitesseTotaleX();
  }

  return collision;
}

void TableauJeu::DeplacerPlateaux(float p_Distance)
{
  // On regarde tous les plateaux
  for(Set_Plateau::iterator itPlat=_SetPlateau.begin(); itPlat!=_SetPlateau.end(); itPlat++)
  {
    if (MOGL_Temps::GetVariationTemps() != 0)
    {
      (*itPlat)->SetVitesse(p_Distance/MOGL_Temps::GetVariationTemps());
    }
  }
/*
// On déplace les plateaux
for(Set_Plateau::iterator itPlat=_SetPlateau.begin(); itPlat!=_SetPlateau.end(); itPlat++)
{
(*itPlat)->Deplacer(p_Distance);
}
*/
}

TableauJeu::Set_ElementTableau * TableauJeu::GetSetElementTableau()
{
  return &_SetElementTableau;
}

TableauJeu::Set_Balle * TableauJeu::GetSetBalle()
{
  return &_SetBalle;
}

TableauJeu::Set_Plateau * TableauJeu::GetSetPlateau()
{
  return &_SetPlateau;
}

TableauJeu::Set_Bonus * TableauJeu::GetSetBonus()
{
  return &_SetBonus;
}

TableauJeu::Set_Generateur * TableauJeu::GetSetGenerateur()
{
  return &_SetGenerateur;
}

void TableauJeu::CreerBonus(float p_X, float p_Y)
{
  // Il faut détruire l'élément de la collision
  // On fait apparaitre un bonus ?
  if (rand()/static_cast<float>(RAND_MAX)<_ProbaBonus)
  {
    Bonus * bonus=_CreerBonus();
    bonus->SetPosition(p_X, p_Y);

    _SetBonus.insert(bonus);
    _Univers->AjouterElement(bonus->GetElementArbreMOGL());
  }
}

void TableauJeu::AjouterGenerateur(Generateur * p_Generateur)
{
  _SetGenerateur.insert(p_Generateur);
  p_Generateur->FinGenerateur.Connecter(this, &TableauJeu::OnFinGenerateur);
  _Univers->AjouterElement(p_Generateur);
}

void TableauJeu::OnFinGenerateur(Generateur * p_Generateur)
{
  Set_Generateur::iterator it=_SetGenerateur.find(p_Generateur);
  if (it!=_SetGenerateur.end())
  {
    _SetGenerateur.erase(it);
  }
  _SetGenerateurADetruire.insert(p_Generateur);
}

Bonus * TableauJeu::_CreerBonus()
{
  unsigned int nbBonus=9;
  float valInd=1.0/nbBonus;

  float al=rand()/static_cast<float>(RAND_MAX);
  if (al<valInd * 1)
  {
    return new BonusDoubleBalle;
  }
  if (al<valInd * 2)
  {
    return new BonusFast;
  }
  if (al<valInd * 3)
  {
    return new BonusSlow;
  }
  if (al<valInd * 4)
  {
    return new BonusPetit;
  }
  if (al<valInd * 5)
  {
    return new BonusGros;
  }
  if (al<valInd * 6)
  {
    return new BonusPetitPlateau;
  }
  if (al<valInd * 7)
  {
    return new BonusGrosPlateau;
  }
  if (al<valInd * 8)
  {
    return new BonusSuperPuissance;
  }
  if (al<valInd * 9)
  {
    return new BonusVueSubjective;
  }

  return new BonusDoubleBalle;
}

Camera * TableauJeu::GetCamera()
{
  return _Camera;
}

void TableauJeu::ClavierUp(SDL_keysym * key)
{
  switch (key->sym)
  {
    case SDLK_c :
    {
      _Camera->ChangerVue();
      break;
    }
    case SDLK_SPACE :
    {
      _OnDepartBalle();
      break;
    }
    default:
      break;
  }
}

void TableauJeu::ClavierDown(SDL_keysym * key)
{
  switch (key->sym)
  {

    case SDLK_LEFT :
    {
      _CummulDep = MOGL_Temps::GetVariationDistance(Option::VitesseClavier.GetValeur() * -10);
      if (!_BallePartie)
      {
        for(Set_Balle::iterator it=_SetBalle.begin(); it!=_SetBalle.end(); it++)
        {
          (*it)->SetPosition((*_SetPlateau.begin())->GetPositionX(),
                             (*_SetPlateau.begin())->GetPositionY() + (*_SetPlateau.begin())->GetTailleY()/2 + (*it)->GetRayon());
        }
      }
      break;
    }
    case SDLK_RIGHT :
    {
      _CummulDep = MOGL_Temps::GetVariationDistance(Option::VitesseClavier.GetValeur() * 10);
      if (!_BallePartie)
      {
        for(Set_Balle::iterator it=_SetBalle.begin(); it!=_SetBalle.end(); it++)
        {
          (*it)->SetPosition((*_SetPlateau.begin())->GetPositionX(),
                             (*_SetPlateau.begin())->GetPositionY() + (*_SetPlateau.begin())->GetTailleY()/2 + (*it)->GetRayon());
        }
      }
      break;
    }
    default:
      break;
  }
}

void TableauJeu::DeplacementSouris(SDL_MouseMotionEvent * p_Event)
{
    static bool bNotFirstDep = false;
    if (bNotFirstDep)
    {
        int val=p_Event->xrel;
        _CummulDep += val * Option::VitesseSouris.GetValeur()/50.0;
    }
    else
    {
        bNotFirstDep = true;
        _CummulDep = 0;
    }
    if (!_BallePartie)
    {
        for(Set_Balle::iterator it=_SetBalle.begin(); it!=_SetBalle.end(); it++)
        {
            (*it)->SetPosition((*_SetPlateau.begin())->GetPositionX(),
                               (*_SetPlateau.begin())->GetPositionY() + 
                               (*_SetPlateau.begin())->GetTailleY()/2 + (*it)->GetRayon());
        }
    }
}

void TableauJeu::_OnFinTemporisation()
{
  FinTableau.Emettre();
  _FinTableau=false;
}

void TableauJeu::_OnDepartBalle()
{
  if (!_BallePartie)
  {
    _TemporisationDepartBalle->Arreter();
    for(Set_Balle::iterator it=_SetBalle.begin(); it!=_SetBalle.end(); it++)
    {
      (*it)->SetVitesseTotale(5,35);
    }
    _BallePartie=true;
  }
}

void TableauJeu::BoutonSourisDown(SDL_MouseButtonEvent * p_Event)
{
  _OnDepartBalle();
}

int TableauJeu::GetNombreVie() const
{
  return _NombreVie;
}

void TableauJeu::SetNombreVie(int p_NombreVie)
{
  _NombreVie=p_NombreVie;
}

void TableauJeu::SetVibration(float pVibrationX, float pVibrationY)
{
  _VibrationX = pVibrationX;
  _VibrationY = pVibrationY;
}
