/*

    Bist: a chemical drawing tool
    Copyright (C) 2008 Valerio Benfante

    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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <global.hpp>

#include <cstdio>


#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <cairo_t_singleton.hpp>
#include <glib.h>


#include <typeinfo>

#include <FL/fl_draw.H>
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Scroll.H>

#include <interfacce.hpp>
#include <legame.hpp>
#include <etichetta.hpp>
#include <atomo.hpp>
#include <procedura.hpp>
#include <gruppo.hpp>
#include <prefs.hpp>
#include <util.hpp>

extern Preferences  __pref;



/*************utilita'*************/


bool ordina_legame_id_atomo(legame  uno, legame  altro){
  return uno.id_atomo() < altro.id_atomo();
}

bool ordina_legame_id_legame(legame  uno, legame  altro){
  return uno.id_legame() < altro.id_legame();
}


bool ordina_atomo_id(atomo  uno, atomo  altro){
  return uno.id() < altro.id();
}



/**************atomo****************/

atomo::atomo()
  :_visitato(false),
   _tipo_atomo(DEFAULT_TYPE_ATOMO),
   _cr(0),
   _cg(0),
   _cb(0),
   _massa(14),
   _cariche(0),
   _position_charge(R_TOP),
   _doppietti(0),
   _el_spaiati(0),
   _pos_x(0),
   _pos_y(0),
   _pos_z(0),
   _can_attach(false)
{
 
  _lati_liberi[0]=true;
  _lati_liberi[1]=true;
  _lati_liberi[2]=true;
  _lati_liberi[3]=true;

 
}


atomo& atomo::operator=(const atomo& altro){
  _visitato=altro._visitato;
  _id=altro._id;
  _tipo_atomo=altro._tipo_atomo;
  _cr=altro._cr;
  _cg=altro._cg;
  _cb=altro._cb;
  _massa=altro._massa;
  _etich=altro._etich;
  _cariche=altro._cariche;
  _position_charge=altro._position_charge;
  _doppietti=altro._doppietti;
  _el_spaiati=altro._el_spaiati;
  _pos_x=altro._pos_x;
  _pos_y=altro._pos_y;
  _pos_z=altro._pos_z;
  _legami=altro._legami;
  _arrivati=altro._arrivati;
  _can_attach=altro._can_attach;
  genitore::_il_genitore=altro.genitore::_il_genitore;

  for(int i=0;i<4;i++){
    _lati_liberi[i]=altro._lati_liberi[i];
  }
  return (*this);
}

atomo::atomo(const atomo& altro)
  :_visitato(altro._visitato),
   _id(altro._id),
   _tipo_atomo(altro._tipo_atomo),
   _cr(altro._cr),
   _cg(altro._cg),
   _cb(altro._cb),
   _massa(altro._massa),
   _etich(altro._etich),
   _cariche(altro._cariche),
   _position_charge(altro._position_charge),
   _doppietti(altro._doppietti),
   _el_spaiati(altro._el_spaiati),
   _pos_x(altro._pos_x),
   _pos_y(altro._pos_y),
   _pos_z(altro._pos_z),
   _legami(altro._legami),
   _arrivati(altro._arrivati),
   _can_attach(altro._can_attach)
{
  genitore::_il_genitore=altro.genitore::_il_genitore;

  for(int i=0;i<4;i++){
    _lati_liberi[i]=altro._lati_liberi[i];
  }

}


atomo::~atomo(){

}


bool  atomo::can_attach(){
  return _can_attach;
}

void  atomo::can_attach(bool nw){
  _can_attach=nw;
}




int atomo::id_gruppo(){
  gruppo* grp=dynamic_cast<gruppo*>(_il_genitore);

  if(grp){
    return grp->id();
  }else{
     return -1;
  }

}



void atomo::aggiungi_genitore(genitore* gentr){
  _il_genitore=gentr;
 }


genitore* atomo::ritorna_genitore(){
  return _il_genitore;
}


bool atomo::is_son(atomo atm){
  bool res=false;
  for(unsigned int i=0;i<_legami.size();i++){
    if(_legami[i].id_atomo()==atm.id()){
      res=true;
    }
  }

  return res;
}

bool atomo::dentro_bb(float x, float y, float w , float h){
  if(pos_x()>x && pos_x()<x+w &&
     pos_y()>y && pos_y()<y+h){
    return true;
  }else{
    return false;
  }

}

int atomo::sotto_mouse(int posx_m, int posy_m){
  gruppo* grp=dynamic_cast<gruppo*>(_il_genitore);
  etichetta cp_etic=_etich;
  grp->allinea_etichetta(cp_etic,(*this));
  int w=cp_etic.w();
  int h=cp_etic.h();
  float st_xpos=cp_etic.x();
  float st_ypos=cp_etic.y();
  float ics_e=st_xpos;//_etich.x()+pos_x()-_etich.w()/2;
  float ips_e=st_ypos;//_etich.y()+pos_y()-_etich.h()/2;
  float w_e=w;
  float h_e=h;

  if(posx_m > ics_e && posx_m < ics_e+w_e &&
     posy_m > ips_e && posy_m < ips_e+h_e){
    return id();
  }else{
    return -1;
  }
  
}


void atomo::libera_tutti(){
  for(int i=0;i<4;i++){
    _lati_liberi[i]=true;
  }
}

bool* atomo::ritorna_lati_liberi(){
  return _lati_liberi;
}


/**
 *Setta a false tutte le visite.
 */

void atomo::reset_arrivati(){
  for(unsigned int i=0; i<_arrivati.size();i++){
    _arrivati[i]=false;
  }
}

bool atomo::e_arrivato(int altro){
  int pos_atm=-1;
  for(unsigned int i=0;i<_legami.size();i++){
    if(_legami[i].id_atomo()==altro){
      pos_atm=i;
      break;
    }
  }
  if( ritorna_arrivati(pos_atm) ){
    return true;
  }else{
    return false;
  }
}

/**
 *Setta a true le vistite in posizione pos.
 */

void atomo::setta_arrivati(int ida){
  for(unsigned int i=0;i<_arrivati.size();i++){
    if(_legami[i].id_atomo()==ida){
      _arrivati[i]=true;
    }
  }

}


bool atomo::ritorna_arrivati(int pos){
  return _arrivati[pos];
}


void atomo::costruisci_arrivati(){

  _arrivati.erase(_arrivati.begin(),_arrivati.end());

  for(unsigned int i=0;i<_legami.size();i++){
    _arrivati.push_back(false);
  }
}



 /**
  *\return true se altro ha lo stesso id di quest'istanza
  **/

bool atomo::operator==(atomo altro){
  if(id()==altro.id()){
    return true;
  }else{
    return false;
  }
}


/*setta i valori*/

void atomo::visitato(bool nw){
  _visitato=nw;
}

void atomo::id(int nw_id){
  _id=nw_id;
}
void atomo::tipo_atomo(int nw_tipo_atomo){
  _tipo_atomo=nw_tipo_atomo;
}

void atomo::etich(etichetta nw){
  _etich=nw;
}


void atomo::cariche(int nw_cariche){
  _cariche=nw_cariche;
}

void atomo::position_charge(int nw_pos){
  if(nw_pos<=ATOM_MAX_POSITION_CHARGE && nw_pos >= 0){
    _position_charge=nw_pos;
  }else{
    _position_charge=L_TOP;
  }
}
void atomo::doppietti(int nw_doppietti){
  _doppietti=nw_doppietti;
}
void atomo::el_spaiati(int nw_el_spaiati){
  _el_spaiati=nw_el_spaiati;
}



void atomo::massa(int nw){
  _massa=nw;
}

void atomo::cr(int nw){
  _cr=nw;
}

void atomo::cg(int nw){
  _cg=nw;
}

void atomo::cb(int nw){
  _cb=nw;
}


void atomo::pos_x(float nw_pos_x){
  _pos_x=nw_pos_x;
}
void atomo::pos_y(float nw_pos_y){
  _pos_y=nw_pos_y;
}

void atomo::pos_z(float nw_pos_z){
  _pos_z=nw_pos_z;
}

/*recupera i valori*/


bool atomo::visitato(){
  return _visitato;
}

etichetta atomo::etich(){
  return  _etich;
}

etichetta& atomo::etich_ref(){
  return  _etich;
}

etichetta* atomo::etich_punt(){
  return  &_etich;
}

int atomo::id(){
  return _id ;
}
int atomo::tipo_atomo(){
  return _tipo_atomo;
}
int atomo::cariche(){
  return _cariche;
}

int atomo::position_charge(){
  return _position_charge;
}

int atomo::doppietti(){
  return _doppietti;
}
int atomo::el_spaiati(){
  return _el_spaiati;
}

int atomo::massa(){
  return _massa;
}

int atomo::cr(){
  return _cr;
}

int atomo::cg(){
  return _cg;
}

int atomo::cb(){
  return _cb;
}


float atomo::pos_x(){
  if(cairo_t_singleton::can_export()){
    return phys_pos_x();
  }else{
    return visual_pos_x();
  }
}

float atomo::phys_pos_x(){
  return _pos_x;
}


float atomo::visual_pos_x(){
  return phys_pos_x() * __pref.getZoom();
}


float atomo::pos_y(){
  if(cairo_t_singleton::can_export()){
    return phys_pos_y();
  }else{
    return visual_pos_y();
  }
}

float atomo::visual_pos_y(){
  return phys_pos_y() * __pref.getZoom();
}

float atomo::phys_pos_y(){
  return _pos_y;
}

float atomo::pos_z(){
  return _pos_z;
}

float atomo::visual_pos_z(){
  return pos_z(); //Va bene o anche la componente z va scalata?
}


float atomo::phys_pos_z(){
  return pos_z(); //Va bene o anche la componente z va scalata?
}


void atomo::disegna(){
  cout << "disegnare la molecola e' compito del gruppo di appartenenza"
       << endl;
}

void atomo::trasla(float dx, float dy){
  pos_x(descale(pos_x()+dx));
  pos_y(descale(pos_y()+dy));
}

void atomo::phys_translate(float dx, float dy){
  pos_x(phys_pos_x()+dx);
  pos_y(phys_pos_y()+dy);
}


void atomo::scale(float sc){
  pos_x(phys_pos_x()*sc);
  pos_y(phys_pos_y()*sc);
}

void atomo::ruota(float xpiv, float ypiv, float angl){
  xpiv/=__pref.getZoom();
  ypiv/=__pref.getZoom();
  float x=phys_pos_x()-xpiv;
  float y=phys_pos_y()-ypiv;
  float xp=x*cos(angl)+y*sin(angl);
  float yp=y*cos(angl)-x*sin(angl);
  pos_x(xp+xpiv);
  pos_y(yp+ypiv);
}





void atomo::aggiungi_legame(int	nw_leg_id_atomo, int nw_leg_id_legame,
			    int nw_leg_tipo_legame,int nwcr,
			    int nwcg , int nwcb){
  legame nuovo(nw_leg_id_atomo,nw_leg_id_legame,nw_leg_tipo_legame,
	       nwcr,nwcg,nwcb);
  _legami.push_back(nuovo);
  sort(_legami.begin(),_legami.begin(),ordina_legame_id_legame);
}


void atomo::aggiungi_legame(int	nw_leg_id_atomo,
			    int nw_leg_tipo_legame,int nwcr,
			    int nwcg , int nwcb){


  int id_leg=0;
  if(_legami.size()>0){
    id_leg=_legami.back().id_legame()+1;
  }

  legame nuovo(nw_leg_id_atomo,id_leg,nw_leg_tipo_legame,
	       nwcr,nwcg,nwcb);
  _legami.push_back(nuovo);
  sort(_legami.begin(),_legami.begin(),ordina_legame_id_legame);
}



void atomo::elimina_legame(int id){

  vector<legame>::iterator iniz=_legami.begin();
  vector<legame>::iterator fin=_legami.end();

  while(iniz!=fin){
    if((*iniz).id_atomo()==id){
      _legami.erase(iniz);
    }

    iniz++;
  }


}


void atomo::elimina_legami(){
  _legami.erase(_legami.begin(),_legami.end());
}


void atomo::modifica_legame(int id_target,
			    int nw_leg_id_atomo,
			    int nw_leg_id_legame,
			    int nw_leg_tipo_legame,
			    int nwcr, int nwcg, int nwcb){

  for(unsigned int i=0; i<_legami.size();i++){
    if(_legami[i].id_atomo()==id_target){
      _legami[i].id_legame(nw_leg_id_legame);
      _legami[i].tipo_legame(nw_leg_tipo_legame);
      _legami[i].cr(nwcr);
      _legami[i].cg(nwcg);
      _legami[i].cb(nwcb);
    }
  }


}


void atomo::modifica_legame(int id_target,
                            int* nw_leg_id_atomo,
                            int* nw_leg_id_legame,
                            int* nw_leg_tipo_legame,
                            int* nwcr, int* nwcg, int* nwcb){
  

  for(unsigned int i=0; i<_legami.size();i++){
    if(_legami[i].id_atomo()==id_target){
      if(nw_leg_id_legame!=NULL){
        _legami[i].id_legame(*nw_leg_id_legame);
      }

      if(nw_leg_tipo_legame!=NULL){
        _legami[i].tipo_legame(*nw_leg_tipo_legame);
      }

      if(nwcr!=NULL){
        _legami[i].cr(*nwcr);
      }
      if(nwcg!=NULL){
        _legami[i].cg(*nwcg);
      }
      if(nwcb!=NULL){
        _legami[i].cb(*nwcb);
      }
    }
  }



}


legame* atomo::find_leg(int id){

  vector<legame>::iterator first=primo_leg();
  vector<legame>::iterator last=ultimo_leg();
  legame* res=NULL;
  while(first!=last){
    if((*first).id_legame()==id){
      res=&(*first);
      break;
    }
    first++;
  }
  
  return res;
}



vector<legame>::iterator atomo::primo_leg(){
  return  _legami.begin();
}

/**
 *\return  ritorna un iterator  all'ultimo elemento  del vettore
 *contenente i legami.
 */


vector<legame>::iterator atomo::ultimo_leg(){
 return _legami.end();
}


std::vector<atomo*> atomo::get_all_children(){
  std::vector<atomo*> res;
  vector<legame>::iterator fst=primo_leg();
  vector<legame>::iterator end=ultimo_leg();
  gruppo* par=dynamic_cast<gruppo*>(ritorna_genitore());
  while(fst!=end){
    int id=(*fst).id_atomo();
    atomo* atm=par->find_atomo_id(id);
    res.push_back(atm);
    fst++;
  }
  return res;
}


unsigned int atomo::bond_number(){
  return _legami.size();
}


unsigned int atomo::bond_number_not_visited(){
  gruppo* grp=dynamic_cast<gruppo*>(_il_genitore);
  vector<legame>::iterator first=primo_leg();
  vector<legame>::iterator last=ultimo_leg();
  unsigned int res=0;
  while(first!=last){
    int id_atom_bond=(*first).id_atomo();
    atomo* bonded=grp->find_atomo_id(id_atom_bond);
    if( bonded!=0 && !bonded->visitato() ){
      res++;
    }
    first++;
  }
  return res;
}
