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

#include <global.hpp>

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


#include <wordexp.h>
#include <sys/stat.h>
#include <dirent.h>
#include <cerrno>
//portability problem?
#include <pthread.h>

#include <cstdio>


#include <FL/Fl.H>
#include <FL/Fl_Image.H>
#include <FL/Fl_Pixmap.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Scroll.H>
#include <FL/fl_ask.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Menu_Item.H>
#include <FL/Fl_Toggle_Button.H>
#include <FL/Fl_Menu_Button.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Light_Button.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Help_Dialog.H>
#include <FL/Fl_Check_Button.H>
//#include <FL/Fl_PNG_Image.H>

#include <interfacce.hpp>
#include <legame.hpp>
#include <etichetta.hpp>
#include <multiline_label.hpp>
#include <multifont_label.hpp>
#include <paragraph_text.hpp>
#include <atomo.hpp>
#include <procedura.hpp>
#include <gruppo.hpp>
#include <immagine.hpp>
#include <bist_plugin.hpp>
#include <mol_canvas.hpp>
#include <finestra_pr.hpp>
#include <editor.hpp>

#include <prefs.hpp>

#include <set_conf.hpp>
#include <command_line.hpp>
#include <2D_vector.hpp>

#include <util.hpp>



#include <align_elements_dialog.hpp>

#include <align_elements.hpp>

extern finestra_pr* MainWindow;

extern Preferences  __pref;

extern bool __close;





align_elements::align_elements(immagine* image,string libpath)
  :bist_plugin(image,libpath),
   _has_to_act(true),
   _has_acted(false)
{

}


void align_elements::inizialize(){
  
  align_elements_dialog dialog_ch;

  while(dialog_ch.shown()){
    Fl::wait();
  }

  //std::cerr << "settato:" << dialog_ch.get_alignment_h()<< std::endl;

  align_elements_type_align horiz_align=dialog_ch.get_alignment();


  switch(horiz_align){
  case ALIGN_HOR:
    align_hor_vert(true);
    break;

  case ALIGN_VERT:
    align_hor_vert(false);
    break;

  case ALIGN_CIRC:
    bool add_arrow=fl_choice("Add arrows?", "No", "Yes", NULL);
    align_circ(true, add_arrow);
    break;


  }

  _has_to_act=false;
}


void align_elements::align_circ(bool clockwise, bool add_arrow){
  vector< pair < int, pair<int,int> > >* elem=r_elem_selected();
  vector< pair < int, pair<int,int> > >::iterator eliniz=elem->begin();
  vector< pair < int, pair<int,int> > >::iterator elend=elem->end();

  float max_radius_w=-1;
  float max_radius_h=-1;
  float elem_size=0;
  std::map<int, bool> group_added;
  float x_start=0;
  float y_start=0;
  float w_start=0;
  float h_start=0;
  float center_x=0;
  float center_y=0;

  bool  is_first=true;

  if(eliniz!=elend){
    while(eliniz!=elend){
      switch((*eliniz).first){
      case ATOMO:
	{
          if(group_added.count(((*eliniz).second).first)==0){
            gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
            if(max_radius_w < the_grp->phys_w()){
              max_radius_w=the_grp->phys_w();
            }
            if(max_radius_h < the_grp->phys_h()){
              max_radius_h=the_grp->phys_h();
            }
            group_added.insert(std::pair<int,bool>((*eliniz).second.first,true));
            elem_size++;
            if(is_first){
              x_start=the_grp->phys_posx();
              y_start=the_grp->phys_posy();
              w_start=the_grp->phys_w();
              h_start=the_grp->phys_h();
            }
          }
	  break;
	}
      case PROC_BEZIER:
      case PROC_ARC:
      case PROC_ARROW:
	{
	  gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	  procedura* proc=the_grp->find_proc_id(((*eliniz).second).second);
          if(max_radius_w < proc->phys_w()){
            max_radius_w=proc->phys_w();
          }

          if(max_radius_h < proc->phys_h()){
            max_radius_h=proc->phys_h();
          }
          elem_size++;

          if(is_first){
            std::pair<float,float> lu;
            std::pair<float,float> rd;
            proc->get_phys_bounding_box(lu,rd);
            x_start=lu.first;
            y_start=lu.second;
            w_start=rd.first  - lu.first; 
            h_start=rd.second - lu.second;
          }
	  break;
	}
      case ETICHETTA:
	{
	  etichetta* laet=_the_image->ritorna_etich_pointer(((*eliniz).second).second);
          if(max_radius_w < laet->phys_w()){
            max_radius_w=laet->phys_w();
          }

          if(max_radius_h < laet->phys_h()){
            max_radius_h=laet->phys_h();
          }
          elem_size++;

          if(is_first){
            x_start=laet->phys_x();
            y_start=laet->phys_y();
            w_start=laet->phys_w();
            h_start=laet->phys_h();

          }
	  break;
	}
      }
      is_first=false;
      eliniz++;
    }
  

  

  float radius=max_radius_h +  max_radius_w;
  center_x=x_start + w_start/2  - radius;
  center_y=y_start + h_start/2;
  float angle_step=2*M_PI / elem_size;
  float angle_start=0;
  float angle=angle_start;

  is_first=true;

  group_added.clear();

  eliniz=elem->begin();
  elend=elem->end();
  while(eliniz!=elend){
    float nwx=radius * cos(angle);
    float nwy=radius * sin(angle);
      switch((*eliniz).first){
      case ATOMO:
	{

          if(group_added.count(((*eliniz).second).first)==0){
            gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
            the_grp->phys_translate(-the_grp->phys_posx() - the_grp->phys_w()/2,
                                    -the_grp->phys_posy() - the_grp->phys_h()/2);
            the_grp->phys_translate(center_x+nwx,center_y+nwy);
            group_added.insert(std::pair<int,bool>((*eliniz).second.first,true));
            angle+=angle_step;
          }
	  break;
	}
      case PROC_BEZIER:
      case PROC_ARC:
      case PROC_ARROW:
	{
	  gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	  procedura* proc=the_grp->find_proc_id(((*eliniz).second).second);
          std::pair<float,float> lu;
          std::pair<float,float> rd;
          proc->get_bounding_box(lu,rd);
          float w=rd.first  - lu.first;
          float h=rd.second - lu.second;

          the_grp->phys_translate(-lu.first  - w/2,
                                  -lu.second - h/2);
          the_grp->phys_translate(center_x+nwx,center_y+nwy);

          angle+=angle_step;

	  break;
	}
      case ETICHETTA:
	{
          etichetta* labl=_the_image->ritorna_etich_pointer(((*eliniz).second).second);
          labl->phys_translate(-labl->phys_x() - labl->phys_w()/2,
                               -labl->phys_y() - labl->phys_h()/2);
          labl->phys_translate(center_x+nwx,center_y+nwy);

          angle+=angle_step;
	  break;
	}
      }
      eliniz++;
  }

  while(bb_intersect()){

    scale_circle(center_x, center_y,radius);
  }

  float nw_rad=get_new_radius(center_x, elem->begin());
  if(add_arrow){
    patch_w_arrows(center_x,center_y,nw_rad);
  }

  }
}


void align_elements::patch_w_arrows(float center_x, float center_y, float nw_rad){
  static const float incr_ang=0.01;

  std::pair<float,float> start_p(0,0);
  std::pair<float,float> end_p(0,0);

  bool inside=true;
  bool start_arr=true;
  bool end_arr=false;

  gruppo new_group;

  float x_bb=0;
  float y_bb=0;
  float w_bb=0;
  float h_bb=0;


  int count_el=0;

  vector< pair < int, pair<int,int> > >* elem=r_elem_selected();
  vector< pair < int, pair<int,int> > >::iterator eliniz=elem->begin();



  for(float angl=0 ;angl<=2*M_PI; angl+=incr_ang){

    float x_curr=nw_rad*cos(angl) + center_x;
    float y_curr=nw_rad*sin(angl) + center_y;
    vector< pair < int, pair<int,int> > >::iterator eliniz_curr=eliniz + (count_el % elem->size());


  switch((*(eliniz_curr)).first){
  case ATOMO:
      {
        gruppo* the_grp=_the_image->find_group_id(((*eliniz_curr).second).first);
        x_bb=the_grp->phys_posx();
        y_bb=the_grp->phys_posy();
        w_bb=the_grp->phys_w();
        h_bb=the_grp->phys_h();
        break;
      }
    case PROC_BEZIER:
    case PROC_ARC:
    case PROC_ARROW:
      {
        gruppo* the_grp=_the_image->find_group_id(((*eliniz_curr).second).first);
        procedura* proc=the_grp->find_proc_id(((*eliniz_curr).second).second);
        std::pair<float,float> lu;
        std::pair<float,float> rd;
        proc->get_bounding_box(lu,rd);
        x_bb=lu.first;
        y_bb=lu.second;
        w_bb=rd.first   - lu.first;
        h_bb=rd.second  - lu.second;
        break;
      }
    case ETICHETTA:
      {
        etichetta* labl=_the_image->ritorna_etich_pointer(((*eliniz_curr).second).second);
        x_bb=labl->phys_x();
        y_bb=labl->phys_y();
        w_bb=labl->phys_w();
        h_bb=labl->phys_h();
        break;
      }
    }


    if((x_curr > x_bb        &&  //inside
        x_curr < x_bb + w_bb &&
        y_curr > y_bb        &&
        y_curr < y_bb + h_bb)
       ){

      if(inside){

        continue;
      }else{
        if(end_arr){
          end_p.first=x_curr - center_x;
          end_p.second=y_curr  - center_y;
          end_arr=false;
          start_arr=true;
          inside=true;


          float ang1=bidimensional_vector::angle(start_p,bidimensional_vector::x_ax);
          float ang2=bidimensional_vector::angle(end_p  ,bidimensional_vector::x_ax);
          proc_bezier* nwp=NULL;
          if(ang1 * ang2 < 0){
            float min_a=std::min(ang1,ang2);
            float max_a=std::max(ang1,ang2);
            ang1=min_a + M_PI;
            ang2=max_a - M_PI;
            nwp=add_bezier_along_circle(&new_group, center_x, center_y, nw_rad , ang1, ang2);
            nwp->ruota(center_x, center_y,-M_PI);
          }else{
            nwp=add_bezier_along_circle(&new_group, center_x, center_y, nw_rad , ang1, ang2);
          }
          nwp->cr(0);
          nwp->cg(0);          
          nwp->cb(0);
          nwp->spessore(1);
          nwp->dash(0);
          nwp->punte(ARR_ETEROL_PUNT);
          nwp->arr_w(__pref.get_arr_w());            
          nwp->arr_h(__pref.get_arr_h());  
          nwp->arr_gap(__pref.get_arr_gap());

        }  
      }
    }else{ //outside
      if(start_arr){
        start_p.first=x_curr  - center_x;
        start_p.second=y_curr  - center_y;
        start_arr=false;
        end_arr=true;
        inside=false;
        count_el++;
      }
        
    }
  }

  _the_image->aggiungi_gruppo(new_group);

}

float align_elements::get_new_radius(float center_x, vector< pair < int, pair<int,int> > >::iterator eliniz){
  float res=0;
  switch((*eliniz).first){
      case ATOMO:
	{
          gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
          res= the_grp->phys_posx() + the_grp->phys_w()/2.0  - center_x;
          break;
	}
      case PROC_BEZIER:
      case PROC_ARC:
      case PROC_ARROW:
	{
	  gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	  procedura* proc=the_grp->find_proc_id(((*eliniz).second).second);
          std::pair<float,float> lu;
          std::pair<float,float> rd;
          proc->get_bounding_box(lu,rd);
          float w=rd.first  - lu.first;
          res= lu.first + w/2.0  - center_x;
	  break;
	}
      case ETICHETTA:
	{
          etichetta* labl=_the_image->ritorna_etich_pointer(((*eliniz).second).second);
          res= labl->phys_x() + labl->phys_w()/2.0  - center_x;
	  break;
	}
      }
  
  return res;

}


void align_elements::align_hor_vert(bool horiz_align){

  vector< pair < int, pair<int,int> > >* elem=r_elem_selected();
  vector< pair < int, pair<int,int> > >::iterator eliniz=elem->begin();
  vector< pair < int, pair<int,int> > >::iterator elend=elem->end();

  float x_align=0;
  float y_align=0;
  float height=0;
  float width=0;


  if(eliniz!=elend){
    switch((*eliniz).first){
    case ATOMO:
      {

	gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	x_align=the_grp->posx();
	y_align=the_grp->posy();
	height=the_grp->h();
        width=the_grp->w();
	break;
      }

    case PROC_BEZIER:
    case PROC_ARC:
    case PROC_ARROW:
      {

	gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	procedura* proc=the_grp->find_proc_id(((*eliniz).second).second);
	x_align=proc->posx();
	y_align=proc->posy();
	height=proc->h();
	width=proc->w();
	break;
      }
    case ETICHETTA:
      {
	etichetta* labl=_the_image->ritorna_etich_pointer(((*eliniz).second).second);

	x_align=labl->x();
	y_align=labl->y();
	height=labl->h();
	width=labl->w();

	break;
      }
    }


    eliniz++;



    while(eliniz!=elend){
      switch((*eliniz).first){
      case ATOMO:
	{
	  gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	  float nwy= y_align - the_grp->posy() + height/2.0 - the_grp->h()/2.0 ;
	  float nwx= x_align - the_grp->posx() + width/2.0 - the_grp->w()/2.0 ;
	  if(horiz_align){
	    nwx=0;
	  }else{
	    nwy=0;
	  }

	  the_grp->trasla(nwx,nwy);

	  break;
	}
      case PROC_BEZIER:
      case PROC_ARC:
      case PROC_ARROW:
	{
	  gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
	  procedura* proc=the_grp->find_proc_id(((*eliniz).second).second);
	  float nwy= y_align - proc->posy() + height/2.0 - proc->h()/2.0;
	  float nwx= x_align - proc->posx() + width/2.0 - proc->w()/2.0 ;

	  if(horiz_align){
	    nwx=0;
	  }else{
	    nwy=0;
	  }


	  proc->trasla(nwx,nwy);
	  break;
	}
      case ETICHETTA:
	{
	  etichetta* labl=_the_image->ritorna_etich_pointer(((*eliniz).second).second);
	  float nwy= y_align - labl->y() + height/2.0 - labl->h()/2.0;
	  float nwx= x_align - labl->x() + width/2.0 - labl->w()/2.0;

	  if(horiz_align){
	    nwx=0;
	  }else{
	    nwy=0;
	  }

	  labl->trasla(nwx,nwy);
	  break;
	}
      }

      eliniz++;
    }

  }


}


bool align_elements::need_atom(){
  return false;
}

bool align_elements::need_leg(){
  return false;
}

bool align_elements::act(int e){

  return _has_to_act;
}


align_elements::~align_elements(){

  cout << "align horiz distruzione!!! " << _the_image <<endl;
}

void align_elements::register_plugin(){

}


bool align_elements::time_to_act(){
  return _has_to_act;
}


string align_elements::libpath(){
  return _lib;
}



bool align_elements::bb_intersect(){

  bool res=false;
  std::vector<etichetta*> labls=get_all_etich_as_vector();
  std::vector<gruppo*>    groups=get_all_molecule_as_vector();
  std::vector<procedura*> procs=get_all_proc_as_vector();

  std::vector< std::pair < std::pair<float,float> ,  std::pair<float,float> > > all_bb;


  for(unsigned int i=0;i<labls.size();i++){
    std::pair<float,float> lu(labls[i]->phys_x(),
                              labls[i]->phys_y());
    std::pair<float,float> rd(labls[i]->phys_x()+ labls[i]->phys_w(),
                              labls[i]->phys_y()+ labls[i]->phys_h());

    std::pair < std::pair<float,float> ,  std::pair<float,float> > bb(lu,rd);
    all_bb.push_back(bb);
  }



  for(unsigned int i=0;i<groups.size();i++){
    std::pair<float,float> lu(groups[i]->phys_posx(),
                               groups[i]->phys_posy());
    std::pair<float,float> rd(groups[i]->phys_posx()+ groups[i]->phys_w(),
                              groups[i]->phys_posy()+ groups[i]->phys_h());

    std::pair < std::pair<float,float> ,  std::pair<float,float> > bb(lu,rd);
    all_bb.push_back(bb);
  }



  for(unsigned int i=0;i<procs.size();i++){
    std::pair<float,float> lu;
    std::pair<float,float> rd;

    procs[i]->get_bounding_box(lu,rd);

    std::pair < std::pair<float,float> ,  std::pair<float,float> > bb(lu,rd);
    all_bb.push_back(bb);
  }

  
  for(unsigned int i=0;i<all_bb.size();i++){
    for(unsigned int j=0;i<all_bb.size();i++){
      std::pair<float,float> lu1(all_bb[i].first.first,
                                 all_bb[i].first.second);

      std::pair<float,float> rd1(all_bb[i].second.first,
                                 all_bb[i].second.second);


      std::pair<float,float> lu2(all_bb[j].first.first,
                                 all_bb[j].first.second);

      std::pair<float,float> rd2(all_bb[j].second.first,
                                 all_bb[j].second.second);


      
      if(lu1.first>0 && lu1.second>0 && i!=j){
        if(calc_bb_gen_intersect(lu1,rd1,lu2,rd2)){
          res=true;
        }
      }
    }
  }

  return res;

}


void align_elements::scale_circle(float center_x, float center_y, float radius){
  //  static  float scale_f=1;
  vector< pair < int, pair<int,int> > >* elem=r_elem_selected();
  vector< pair < int, pair<int,int> > >::iterator eliniz=elem->begin();
  vector< pair < int, pair<int,int> > >::iterator elend=elem->end();
  std::map<int, bool> group_added;
  if(eliniz!=elend){
    while(eliniz!=elend){
      switch((*eliniz).first){
      case ATOMO:
	{
          if(group_added.count(((*eliniz).second).first)==0){
            gruppo* grp=_the_image->find_group_id(((*eliniz).second).first);

            atomo* start=grp->find_atomo_id(0);
            if(start!=NULL){
              std::pair<float,float>* data_grp=new std::pair<float,float>();
              (*data_grp).first=(grp->phys_posx() + grp->phys_w()/2.0); 
              (*data_grp).second=(grp->phys_posy() + grp->phys_h()/2.0);
              grp->generic_depth_search_appl_popped(start, 
                                                    static_cast<void*>(&center_x), 
                                                    static_cast<void*>(&center_y), 
                                                    static_cast<void*>(data_grp), 
                                                    scale_circle_atom);

              delete data_grp;
            }
            group_added.insert(std::pair<int,bool>((*eliniz).second.first,true));
          }
          break;
        }
      case PROC_BEZIER:
      case PROC_ARC:
      case PROC_ARROW:
	{

	  gruppo* the_grp=_the_image->find_group_id(((*eliniz).second).first);
          procedura* proc=the_grp->find_proc_id(((*eliniz).second).second);         
          

          /**
           *Contiene  coppie <tipo, < gruppo, id > > selezionati, se  si e'
           *selezionato una etichetta gruppo e' uguale a NO_VALID_GROUP
           */

          scale_circle_proc(proc , center_x, center_y);
	  break;
	}
      case ETICHETTA:
	{
	  etichetta* labl=_the_image->ritorna_etich_pointer(((*eliniz).second).second);
          scale_circle_etichetta(labl , center_x, center_y);
	  break;
	}
                     
      }
    
      eliniz++;
    }
  }
}





void align_elements::scale_circle_proc(procedura* proc , float center_x, float center_y){
  std::pair<float,float> lu;
  std::pair<float,float> rd;
  
  proc->get_bounding_box(lu,rd);

  std::pair<float, float> v(lu.first + ((rd.first - lu.first)/2.0),
                            lu.second + ((rd.second - lu.second)/2.0));
  std::pair<float, float> c(center_x,center_y);

  v=bidimensional_vector::diff(c,v);
  v.first=-v.first;
  v.second=-v.second;

  v=bidimensional_vector::normalize(v);

  proc->phys_translate(-center_x,-center_y); 
  proc->phys_translate(v.first,v.second);
  proc->phys_translate(center_x,center_y);

}


void align_elements::scale_circle_etichetta(etichetta* labl , float center_x, float center_y){
  std::pair<float, float> v(labl->phys_x() + labl->phys_w()/2.0, 
                            labl->phys_y() + labl->phys_h()/2.0);
  std::pair<float, float> c(center_x,center_y);
  v=bidimensional_vector::diff(c,v);
  v.first=-v.first;
  v.second=-v.second;

  v=bidimensional_vector::normalize(v);


  labl->phys_translate(-center_x,-center_y); 
  labl->phys_translate(v.first,v.second);
  labl->phys_translate(center_x,center_y);
  
}



/************************************************/



int scale_circle_atom(atomo* from , void* data, void* data2, void* data3){
  float* center_x=static_cast<float*>(data);
  float* center_y=static_cast<float*>(data2);
  std::pair<float,float>* data_grp=static_cast< std::pair<float,float>* >(data3);
  std::pair<float, float> v=*data_grp;
  std::pair<float, float> c(*center_x,*center_y);
  v=bidimensional_vector::diff(c,v);
  
  v.first=-v.first;
  v.second=-v.second;


  v=bidimensional_vector::normalize(v);
  from->phys_translate(-(*center_x),-(*center_y)); 
  


  from->phys_translate(v.first,v.second);
  from->phys_translate(*center_x,*center_y);

  return 1;

}


std::string align_elements::menu_path(){
  return "align and distribuite/";
}



extern "C" bist_plugin* create_plugin(immagine* imm, string libpath){
  return new align_elements(imm, libpath);
}

extern "C" void destroy_plugin(bist_plugin* j){
  delete j;
}


