#include <config.h>
#include <config_path.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_Output.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 <util.hpp>


#include <matr.hpp>

#include <gsl/gsl_poly.h>

#include <TRE_res.hpp>
#include <TREcalc.hpp>

#include <string_tokenizer.hpp>


extern finestra_pr* MainWindow;

extern Preferences  __pref;

extern bool __close;



std::string TRE_polinomyal_characteristic_coefficients;

/**
 *Un plugin deve avere :
 *
 *una funzione  bool (*need_group)()  che ritorna true  se il  plugin ha
 *bisogno di conoscre quali atomi sono selezionati;
 *
 *una funzione  bool (*need_leg)()  che ritorna true  se il  plugin ha
 *bisogno di conoscere quali legami sono selezionati;
 *
 *una funzione  bool (*act)()  che computa quello  per cui il  plugin e'
 *stato scritto
 *
 */



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


void TREcalc::inizialize(){
  
  /**
   *Contiene  triplette: gruppo, atomo di appartenenza e id dei legami selezionati
   */
  vector<int*>* bonds_selected=r_legami_selected();
  
  /**
   *Contiene  coppie <tipo, < gruppo, id > > selezionati, se  si e'
   *selezionato una etichetta gruppo e' uguale a NO_VALID_GROUP
   */

  vector< pair < int, pair<int,int> > >* elem_selected=r_elem_selected();



  if(bonds_selected->size()<1 && elem_selected->size()<1){ //no elem selected
    calculateTRE();
    _has_acted=true;
    _has_to_act=false;
    return;
  }else{

    gruppo* sel_group=0;
    if(bonds_selected->size()>0){
      sel_group=_the_image->find_group_id(((*bonds_selected)[0])[0]);
    }else{
      sel_group=_the_image->find_group_id( ((*elem_selected)[0]).second.first );
    }

    //checkin for error in selection
    bool error_selection=false;
    if(bonds_selected->size()>0 && 
       elem_selected->size()>0){
      for(unsigned int i=0;i<bonds_selected->size();i++){
	if(((*bonds_selected)[i])[0] != sel_group->id_gruppo()){
	  //user selected a bond from another molecule: abort
	  error_selection=true;
	}
      }

      for(unsigned int i=0;i<elem_selected->size();i++){
	if( ((*elem_selected)[0]).second.first != sel_group->id_gruppo()){
	  //user selected an atom from another molecule: abort
	  error_selection=true;
	}
	if( ((*elem_selected)[0]).second.first==NO_VALID_GROUP){
	  //user selected an etichetta: abort
	  error_selection=true;
	}
      }
    }

    if(error_selection){
      _has_acted=true;
      _has_to_act=false;
      return;
    }

    matrix<double> adjac_matrix_cycle=calculate_adjac_matrix(sel_group);
    matrix<double> adjac_matrix_no_bonds=calculate_adjac_matrix(sel_group);

    
    //eliminate bond
    std::vector<atomo>::iterator beg=sel_group->iniz_atom();
    std::vector<atomo>::iterator end=sel_group->fin_atom();
    unsigned int size_matrix=0;
    /* first= id in internal format second= matrix index */
    std::map<int,int> id_map; 
    while(beg!=end){
      pair<int,int> tmp;
      tmp.first=beg->id();
      tmp.second=size_matrix;
      id_map.insert(tmp);
      size_matrix++;
      beg++;
    }

    for(unsigned int i=0;i<bonds_selected->size();i++){
      int group_atomo_id_start=id_map[ ((*bonds_selected)[i])[1] ];
      int group_atomo_id_end=0;
      atomo* start=sel_group->find_atomo_id( ((*bonds_selected)[i])[1]);
      vector<legame>::iterator beg_leg=start->iniz_leg();
      vector<legame>::iterator end_leg=start->fin_leg();
      while(beg_leg!=end_leg){
	if(beg_leg->id_legame()==((*bonds_selected)[i])[2]){
	  group_atomo_id_end=id_map[ beg_leg->id_atomo() ];
	  break;
	}
	beg_leg++;
      }
      adjac_matrix_no_bonds(group_atomo_id_start,group_atomo_id_end)=0;
      adjac_matrix_no_bonds(group_atomo_id_end,group_atomo_id_start)=0;
    }
    
    //cerr << "cicle \n" << adjac_matrix_cycle << std::endl;
    //cerr << "nobond \n" <<  adjac_matrix_no_bonds << std::endl;

    //at   this  point   adjac_matrix_no_bonds  contains   the  molecule
    //withount del bonds user have deleted, now is time to eliminate the
    //atoms

    /* first= id in internal format second= matrix index 
       std::map<int,int> id_map; 
        
       *Contiene  coppie <tipo, < gruppo, id > > selezionati, se  si e'
       *selezionato una etichetta gruppo e' uguale a NO_VALID_GROUP
       *
       vector< pair < int, pair<int,int> > >* elem_selected=r_elem_selected();
    */
    
    unsigned int atom_deleted=0;
    for(unsigned int i=0;i<elem_selected->size();i++){
      int target_id_group=((*elem_selected)[i]).second.first;
      int target_id_atom=((*elem_selected)[i]).second.second;
      if(sel_group->id_gruppo()==target_id_group){
	//matrix<double> elim_col=result_matrix.ElimCol(id_map[target_id_atom]);
	//result_matrix=elim_col.ElimRow(id_map[target_id_atom]);
	//cerr << adjac_matrix_no_bonds.Rows()
	//     << " eliminerei: " << id_map[target_id_atom] << std::endl;
	for(unsigned int row=0;row<adjac_matrix_no_bonds.Rows();row++){
	  adjac_matrix_no_bonds(row,id_map[target_id_atom])=-1;
	  adjac_matrix_no_bonds(id_map[target_id_atom],row)=-1;
	}
	atom_deleted++;
      }
    }
    
    //cerr << "tmpmatrix\n" << adjac_matrix_no_bonds << std::endl;
    
    matrix<double> result_matrix(adjac_matrix_no_bonds.Rows()-atom_deleted,
				 adjac_matrix_no_bonds.Cols()-atom_deleted);

    //cerr << "dimension : " << result_matrix.Rows() << " " 
    //	 <<result_matrix.Cols() << std::endl;
    result_matrix.Null();
    unsigned int row_target=0;
    unsigned int col_target=0;
    
    for(unsigned int row=0;row<adjac_matrix_no_bonds.Rows();row++){
      //if(adjac_matrix_no_bonds(row,0)>=0){
	//cerr << "row " << row << " e' maggiore di 0" << std::endl;
      bool allInvalid=true;
	for(unsigned int col=0;col<adjac_matrix_no_bonds.Cols();col++){
	  if(adjac_matrix_no_bonds(row,col)>=0){
	    //cerr << "setto " << row_target << " " << col_target 
	    //	 <<  " valore " << adjac_matrix_no_bonds(row,col)<<std::endl;
	    result_matrix(row_target,col_target)=adjac_matrix_no_bonds(row,col);
	    col_target++;
	    allInvalid=false;
	  }
	}
	col_target=0;
	if(!allInvalid){
	  row_target++;
	}
	//}
    }
    
    //cerr << "results \n" <<  result_matrix << std::endl;

    _has_acted=true;
    _has_to_act=false;
       

    //std::vector<double> coeff_vec_cycle=calculate_characteristic_polynomial(adjac_matrix_cycle);
    std::vector<double> coeff_vec_results=calculate_characteristic_polynomial(result_matrix);

    //cerr << "cyclic: " << format_polynomial(coeff_vec_cycle,TRE_POLY_OOFFICE_FORMAT) 
    //	 << std::endl;

    for(unsigned int i=0;i< coeff_vec_results.size(); i++){
      ostringstream outs;
      outs << coeff_vec_results[i] << " ";
      TRE_polinomyal_characteristic_coefficients+=outs.str();
      //cerr << "#" << coeff_vec_results[i] << std::endl;
    }

    //cerr << "no cyclic: " << format_polynomial(coeff_vec_results,TRE_POLY_OOFFICE_FORMAT) 
    //	 << std::endl;

    _the_image->elimina_elem_selected();
    _the_image->elimina_legami_selected();

    Fl_Double_Window* w=make_TRE_poly_res_window();
    
    Fl_Output* tex_out=dynamic_cast<Fl_Output*>(w->child(1));
    tex_out->value(format_polynomial(coeff_vec_results,TRE_POLY_TEX_FORMAT).c_str());

    Fl_Output* odt_out=dynamic_cast<Fl_Output*>(w->child(2));
    odt_out->value(format_polynomial(coeff_vec_results,TRE_POLY_OOFFICE_FORMAT).c_str());
  
    Fl_Output* raw_out=dynamic_cast<Fl_Output*>(w->child(3));
    raw_out->value(format_polynomial(coeff_vec_results,TRE_POLY_RAW_FORMAT).c_str());


    w->show();
        
    _has_acted=true;
    _has_to_act=false;
       

    
  }

}



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

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

bool TREcalc::act(int e){

  return _has_to_act;
}


TREcalc::~TREcalc(){
  
  cout << "TREcalc distruzione!!! " << _the_image <<endl;
}

void TREcalc::register_plugin(){

}

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

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


 void TREcalc::zerify(matrix<double>& m){
   for(unsigned int i=0;i<m.RowNo();i++){
     for(unsigned int j=0;j<m.RowNo();j++){
       if(m(i,j) <= TRE_TRESHOLD && m(i,j) > -TRE_TRESHOLD){
	 m(i,j)=0;
       }
       
     }
   }
 }


matrix<double> TREcalc::calculate_adjac_matrix(gruppo* group){
  matrix<double> res;
  std::vector<atomo>::iterator beg=group->iniz_atom();
  std::vector<atomo>::iterator end=group->fin_atom();
  
  unsigned int size_matrix=0;
  /* first= id in internal format second= matrix index */
  std::map<int,int> id_map; 
  while(beg!=end){
    pair<int,int> tmp;
    tmp.first=beg->id();
    tmp.second=size_matrix;
    id_map.insert(tmp);
    size_matrix++;
    beg++;
  }
  
  
  if(size_matrix==0){ //no atom in group 
    return res;
  }

  matrix<double> adjac_matrix(size_matrix,size_matrix);
  adjac_matrix.Null();

  beg=group->iniz_atom();
  end=group->fin_atom();

  while(beg!=end){
    vector<legame>::iterator in_leg=beg->iniz_leg();
    vector<legame>::iterator fin_leg=beg->fin_leg();
    
    while(in_leg!=fin_leg){
      int matr_index_me=id_map[beg->id()];
      int matr_index_bonded=id_map[in_leg->id_atomo()];
      adjac_matrix(matr_index_me,matr_index_bonded)=1;
      
      in_leg++;
    }
    
    beg++;
  }
  res=adjac_matrix;
  return res;
}

vector<double> TREcalc::calculate_characteristic_polynomial(matrix<double>& adjac_matrix){
   matrix<double> identity(adjac_matrix.RowNo(),adjac_matrix.RowNo());
   identity.Null();
   for(unsigned int i=0;i<identity.RowNo();i++){
     identity(i,i)=1;
   }

   matrix<double> A1=adjac_matrix;
   double n=1;
   double coeff=1;
   bool stop=false;
   std::vector<double> coeff_vec;
   coeff_vec.push_back(1);
   while(true){
     //cerr << "inizio ciclo ---- " << std::endl;

     zerify(A1);
     //std::cerr << "A1=\n" << A1 << std::endl;
     
     coeff = (1/n)*A1.Trace();
     
     if(coeff <= TRE_TRESHOLD && coeff > -TRE_TRESHOLD){
       coeff=0;
     }

     if(stop){
       //cerr << "fine calcolo " << std::endl;
       break;
     }

     //std::cerr << "coeff " << -coeff << std::endl;
     
     coeff_vec.push_back(-coeff);
     matrix<double> B1 =     A1 - (coeff * identity);
     
     zerify(B1);

     stop=true;
     for(unsigned int i=0;i<B1.RowNo();i++){
       for(unsigned int j=0;j<B1.RowNo();j++){
	 if(!(B1(i,j) <= TRE_TRESHOLD && B1(i,j) >= -TRE_TRESHOLD)){
	   stop=false;
	 }
       }
     }

     //std::cerr << "B1=\n" << B1 << std::endl;
     
     matrix<double> A2 =  adjac_matrix*B1;

     zerify(A2);
    
     //std::cerr << "A2=\n" << A2 << std::endl;

     if(coeff <= TRE_TRESHOLD && coeff >= -TRE_TRESHOLD){
       coeff=0;
     }

     A1=A2;
     //std::cerr << "fine cclo " << std::endl;
     n++;
     
   }

   return coeff_vec;

}

void TREcalc::calculateTRE(){
    char * inf_name=fl_file_chooser("pick TRE calculation file...",NULL,NULL);
    std::vector<double*> the_vectors;
    std::vector<double> cyclic_polynomial_vec;
    
    if(inf_name!=NULL){
      fstream inf(inf_name,ios::in);
      //get first polinomian (non aciclic)
      string line;
      string delim(" ");
      getline(inf,line);

      string_tokenizer tok(line,delim);

      while(!tok){
	std::string coeff_str=tok.next_token();
	double coeff=strtod(coeff_str.c_str(),NULL);
	cyclic_polynomial_vec.push_back(coeff);
	
      }
      
      double* coeff_cyclic_polinomial=new double[cyclic_polynomial_vec.size()];
      double* coeff_acyclic_polinomial=new double[cyclic_polynomial_vec.size()];
      std::copy(cyclic_polynomial_vec.begin(),
		cyclic_polynomial_vec.end(),
		coeff_cyclic_polinomial);

      
      /*
      for(unsigned int i=0;i<cyclic_polynomial_vec.size();i++){
	std::cerr << coeff_cyclic_polinomial[i] << std::endl;
      }      
      */
      while(!inf.eof()){
	string line;
	getline(inf,line);

	string delim(" ");
	string_tokenizer tok(line,delim);
	double* this_coeff_acyclic_polinomial=new double[cyclic_polynomial_vec.size()];

	for(unsigned int i=0;i<cyclic_polynomial_vec.size();i++){
	  this_coeff_acyclic_polinomial[i]=0;
	}


	int num_coeff=0;
	while(!tok){
	  std::string coeff_str=tok.next_token();
	  num_coeff++;
	}

	int padding=cyclic_polynomial_vec.size()-num_coeff;

	string_tokenizer tok2(line,delim);
	while(!tok2){
	  std::string coeff_str=tok2.next_token();
	  double coeff=strtod(coeff_str.c_str(),NULL);
	  this_coeff_acyclic_polinomial[padding]=coeff;
	  padding++;
	}
	//std::cerr << "----" << std::endl;
	//for(unsigned int i=0;i<cyclic_polynomial_vec.size();i++){
	//  std::cerr << this_coeff_acyclic_polinomial[i] << " ";
	//}      
	//std::cerr << std::endl;
	the_vectors.push_back(this_coeff_acyclic_polinomial);

      }
            
      for(unsigned int j=0;j<cyclic_polynomial_vec.size();j++){
	coeff_acyclic_polinomial[j]=the_vectors[0][j];
      }
      
      for(unsigned int j=0;j<cyclic_polynomial_vec.size();j++){
	for(unsigned int i=1;i<the_vectors.size();i++){
	  //cerr << "sottraggo " << the_vectors[i][j] << " ";
	  coeff_acyclic_polinomial[j]-=the_vectors[i][j];
	  //cerr << " = " <<   coeff_acyclic_polinomial[j] << std::endl;
	}
	//cerr << endl;
      }
      
      cerr << "##cyclic coeff##" << std::endl;
      for(unsigned int j=0;j<cyclic_polynomial_vec.size();j++){
	std::cerr << cyclic_polynomial_vec[j] << " ";
      }
      
      std::cerr << std::endl;
      

      cerr << "##acyclic coeff##" << std::endl;
      for(unsigned int j=0;j<cyclic_polynomial_vec.size();j++){
      std::cerr << coeff_acyclic_polinomial[j] << " ";
      }
      
      std::cerr << std::endl;
      
     
      //calculate roots
      //roots of cyclic polynomial
      double* cyclic_reverse_coeff = new double[cyclic_polynomial_vec.size()]; 
      double* acyclic_reverse_coeff = new double[cyclic_polynomial_vec.size()]; 
      for(unsigned int j=cyclic_polynomial_vec.size()-1;static_cast<int>(j)>=0;j--){
	cyclic_reverse_coeff[(cyclic_polynomial_vec.size()-1)-j]=cyclic_polynomial_vec[j];
      }
      double* zeroes=new double[(cyclic_polynomial_vec.size()-1)*2];
      
      /*
      for(unsigned int i=0;i<cyclic_polynomial_vec.size();i++){
	std::cerr << "coeff: " << cyclic_reverse_coeff[i] << std::endl;
	
      }
      */
      gsl_poly_complex_workspace * w 
	= gsl_poly_complex_workspace_alloc (cyclic_polynomial_vec.size());
      
      gsl_poly_complex_solve (cyclic_reverse_coeff, cyclic_polynomial_vec.size(), w, zeroes);
      
      gsl_poly_complex_workspace_free (w);
      

      std::vector<double> cyclic_zeroes;

      for (unsigned int i = 0; i < (cyclic_polynomial_vec.size()-1)*2; i+=2){
	cyclic_zeroes.push_back(zeroes[i]);
      }
      
      std::sort(cyclic_zeroes.begin(),cyclic_zeroes.end());
      std::reverse(cyclic_zeroes.begin(),cyclic_zeroes.end());
      for (unsigned int i = 0; i < cyclic_zeroes.size(); i++){
	std::cerr << "zeroes cyclic: "  << cyclic_zeroes[i] << std::endl;
      }
  
      //root of acyclic
    
      for(unsigned int j=cyclic_polynomial_vec.size()-1;static_cast<int>(j)>=0;j--){
	acyclic_reverse_coeff[(cyclic_polynomial_vec.size()-1)-j]=coeff_acyclic_polinomial[j];
      }
      
      w = gsl_poly_complex_workspace_alloc (cyclic_polynomial_vec.size());
      
      gsl_poly_complex_solve (acyclic_reverse_coeff, cyclic_polynomial_vec.size(), w, zeroes);
      
      gsl_poly_complex_workspace_free (w);

      
      std::vector<double> acyclic_zeroes;

      for (unsigned int i = 0; i < (cyclic_polynomial_vec.size()-1)*2; i+=2){
	acyclic_zeroes.push_back(zeroes[i]);
      }
      
      std::sort(acyclic_zeroes.begin(),acyclic_zeroes.end());
      std::reverse(acyclic_zeroes.begin(),acyclic_zeroes.end());
      for (unsigned int i = 0; i < cyclic_zeroes.size(); i++){
	std::cerr << "zeroes acyclic: "  << acyclic_zeroes[i] << std::endl;
      }

      //free memory      
      delete [] acyclic_reverse_coeff;
      delete [] cyclic_reverse_coeff;
      delete [] zeroes;
      delete [] coeff_cyclic_polinomial;
      delete [] coeff_acyclic_polinomial;

      for(unsigned int i=0;i<the_vectors.size();i++){
	delete [] the_vectors[i];
      }

      inf.close();


      ostringstream el_def;
      el_def << cyclic_zeroes.size();
      
      const char * pi_el=fl_input("Number of electrons?", el_def.str().c_str());

      long int num_el_pi=strtol(pi_el,NULL,0);

      if(num_el_pi<=static_cast<long int>(cyclic_zeroes.size())){
	int num_pair=num_el_pi/2;
	double E_cyclic=0;
	double E_acyclic=0;
	int index_cyclic=0;
	int index_acyclic=0;
	for( ;index_cyclic<num_pair;index_cyclic++){
	  E_cyclic+=2*cyclic_zeroes[index_cyclic];
	}
	
	
	for( ;index_acyclic<num_pair;index_acyclic++){
	  E_acyclic+=2*acyclic_zeroes[index_acyclic];
	}
	
	double TRE=(E_cyclic - E_acyclic)/num_el_pi;
	ostringstream TRE_str;
	TRE_str << "Value of TRE is: " << TRE;
	fl_message("%s",TRE_str.str().c_str());
      }

    }


    
    

}


std::string TREcalc::format_polynomial(std::vector<double>& coeff_vec, int format){
      //coeff[0]*x^N-1 ... 
    std::string raw_pol="";
    for(unsigned int i=0;i<coeff_vec.size();i++){
      //cerr << "## " << coeff_vec[i] << " " << (coeff_vec.size()-1) - i << endl;
      if( !similar_to(coeff_vec[i],0.,TRE_TRESHOLD) ){ //is     different
	                                               //from zero?
	ostringstream outs;
	
	if(similar_to(coeff_vec[i], 1.,TRE_TRESHOLD)){
	  if(i==coeff_vec.size()-1){
	    outs << "+" << coeff_vec[i];
	  }else{
	    if(i!=0){
	      outs << "+";
	    }
	  }
	}else  if(similar_to(coeff_vec[i],-1.,TRE_TRESHOLD)){
	  if(i==coeff_vec.size()-1){
	    outs << coeff_vec[i];
	  }else{
	    outs << "-";
	  }
	}else{
	  if(coeff_vec[i]>0){
	    outs << "+";
	  }
	  outs << coeff_vec[i];
	}

	
	if(i!=coeff_vec.size()-1){
	  outs << "x" << "^";
	  switch(format){
	  case TRE_POLY_TEX_FORMAT:
	    outs << "{" << (coeff_vec.size()-1) - i << "}";
	    break;
	  case TRE_POLY_OOFFICE_FORMAT:
	    outs << "{" << (coeff_vec.size()-1) - i << "}";
	    break;
	  default: 
	    outs << (coeff_vec.size()-1) - i;
	    
	  }
	}
	   
	raw_pol+= outs.str();
	   
	   
      }
    }
    return raw_pol;
}

template <class T>  bool TREcalc::similar_to(T v, T ref, T offset){
  bool res=false;
  if( (v >= ref - offset) && (v <= ref + offset) ){
    res=!res;
  }

  return res;
}

std::string TREcalc::menu_path(){
  return "calculation/";
}


/**************fine metodi di classe**********************************/




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

extern "C" void destroy_plugin(bist_plugin* j){
  cout << "distruzione plugin: " << j <<  endl;
  delete j;
  cout << "riuscita" << endl;
}


