/* ligand/torsion-general.cc
 * 
 * Copyright 2007 by The University of Oxford
 * Author: Paul Emsley
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms ofn 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include <iostream>

#include <clipper/core/clipper_util.h>

#include "torsion-general.hh"

coot::torsion_general::torsion_general(mmdb::Residue *res, mmdb::Manager *residue_mol_in,
				       const std::vector<atom_spec_t> &user_defined_torsion_atoms_in) {
   setup_correctly = NO;
   mol = residue_mol_in;
   residue_p = res;
   user_defined_torsion_atoms = user_defined_torsion_atoms_in;

   int index_0 = atom_index(user_defined_torsion_atoms[0]);
   int index_1 = atom_index(user_defined_torsion_atoms[1]);
   int index_2 = atom_index(user_defined_torsion_atoms[2]);
   int index_3 = atom_index(user_defined_torsion_atoms[3]);

   if (index_0 != FAIL) {
      if (index_1 != FAIL) {
	 if (index_2 != FAIL) {
	    if (index_3 != FAIL) {
	       contact_indices = get_contact_indices();
	       clicked_atom_indices.clear();
	       clicked_atom_indices.push_back(index_0);
	       clicked_atom_indices.push_back(index_1);
	       clicked_atom_indices.push_back(index_2);
	       clicked_atom_indices.push_back(index_3);
	       setup_correctly = YES;
	    } else {
	       std::cout << "ERROR:: failed to find " << user_defined_torsion_atoms[3] << std::endl;
	    }
	 } else {
	    std::cout << "ERROR:: failed to find " << user_defined_torsion_atoms[2] << std::endl;
	 }
      } else {
	 std::cout << "ERROR:: failed to find " << user_defined_torsion_atoms[1] << std::endl;
      }
   } else {
      std::cout << "ERROR:: failed to find " << user_defined_torsion_atoms[0] << std::endl;
   }
}

Tree
coot::torsion_general::GetTree_0_based() const {

   Tree tree;
   if (setup_correctly == YES) {

      mmdb::Atom **residue_atoms = nullptr;
      int n_residue_atoms = 0;
      residue_p->GetAtomTable(residue_atoms, n_residue_atoms);
      std::vector< ::Cartesian > coords;
      for(int i=0; i<n_residue_atoms; i++) {
	 ::Cartesian c(residue_atoms[i]->x,
		       residue_atoms[i]->y,
		       residue_atoms[i]->z);
	 coords.push_back(c);
      }
      int base_index = 0;
      tree.SetCoords(coords, base_index, contact_indices);
   }
   return tree;
}

Tree
coot::torsion_general::GetTree() const {

   Tree tree;
   if (setup_correctly == YES) {

      mmdb::Atom **residue_atoms = nullptr;
      int n_residue_atoms = 0;
      residue_p->GetAtomTable(residue_atoms, n_residue_atoms);
      std::vector< ::Cartesian > coords;
      for(int i=0; i<n_residue_atoms; i++) {
	 ::Cartesian c(residue_atoms[i]->x,
		       residue_atoms[i]->y,
		       residue_atoms[i]->z);
	 coords.push_back(c);
      }
      int base_index = clicked_atom_indices[0];
      base_index = 0;
      tree.SetCoords(coords, base_index, contact_indices);
   }
   return tree;
}

int
coot::torsion_general::change_by(double diff, Tree *tree) {

   bool debug = false;

   int r=1;

   std::cout << "user_defined_torsion_atoms.size(): " << user_defined_torsion_atoms.size()
 	     << std::endl;

   if (setup_correctly) {

      mmdb::PPAtom residue_atoms;
      int n_residue_atoms;
      residue_p->GetAtomTable(residue_atoms, n_residue_atoms);
      std::vector< ::Cartesian > coords;
      for(int i=0; i<n_residue_atoms; i++) {
	 ::Cartesian c(residue_atoms[i]->x,
		       residue_atoms[i]->y,
		       residue_atoms[i]->z);
	 coords.push_back(c);
      }

      TreeVertex *tv = tree->GetCoord(clicked_atom_indices[1]);

      if (debug) // debugging
	 std::cout << "children of tree vertex of clicked atom[1] "
		   << tv->GetNumberOfChildren() << std::endl;

      if (tv->GetNumberOfChildren() > 0) {
	 TreeVertex *tvc0 = tv->GetChild(0);
	 float tors = clipper::Util::d2rad(diff);

	 if (debug) { // debugging
	    std::cout << "debug:: change_by() clicked atom indices: "
		      << clicked_atom_indices[0] << " "
		      << clicked_atom_indices[1] << " "
		      << clicked_atom_indices[2] << " "
		      << clicked_atom_indices[3] << std::endl;
	    std::cout << "debug:: change_by() rotating about "
		      << clicked_atom_indices[1] << " "
		      << clicked_atom_indices[2] << " by "
		      << tors << "radians" << std::endl;
	 }

	 tree->RotateAboutBond(clicked_atom_indices[1],
			       clicked_atom_indices[2], tors);

	 std::vector< ::Cartesian > coords_rotatated =
	    tree->GetAllCartesians();
	 if (int(coords_rotatated.size()) != n_residue_atoms) {
	    std::cout << "disaster in atom selection, tors_general\n";
	 } else {
	    for (int iat=0; iat<n_residue_atoms; iat++) {

	       if (debug) {
		  float dx = residue_atoms[iat]->x - coords_rotatated[iat].get_x();
		  float dy = residue_atoms[iat]->x - coords_rotatated[iat].get_x();
		  float dz = residue_atoms[iat]->x - coords_rotatated[iat].get_x();
		  float dd = dx * dx + dy * dy + dz * dz;
		  float d = sqrt(dd);
		  std::cout << residue_atoms[iat]->name << "debug:: change_by(): "
			    << residue_atoms[iat]->x << " "
			    << residue_atoms[iat]->y << " "
			    << residue_atoms[iat]->z << " to  "
			    << coords_rotatated[iat] << " moved by "
			    << d << std::endl;
	       }

	       residue_atoms[iat]->x = coords_rotatated[iat].get_x();
	       residue_atoms[iat]->y = coords_rotatated[iat].get_y();
	       residue_atoms[iat]->z = coords_rotatated[iat].get_z();
	    }
	    r = 0; // return good status
	 }
      } else {
	 std::cout << "WARNING: this vertex " << clicked_atom_indices[2]
		   << " has no children (strangely)\n";
      }
      if (debug) {
	 for(int i=0; i<n_residue_atoms; i++) {
	    ::Cartesian c(residue_atoms[i]->x,
			  residue_atoms[i]->y,
			  residue_atoms[i]->z);
	 }
      }
   } else {
      std::cout << "Sorry torsion_general not setup correctly" << std::endl;
   }
   return r;
}

// return the atom index in residue of the atom with the given
// spec.  Return -1 on failure to find the atom.
int
coot::torsion_general::atom_index(const coot::atom_spec_t &spec) const {

   int r = FAIL;

   if (residue_p) {
      mmdb::PPAtom residue_atoms;
      int n_residue_atoms;
      residue_p->GetAtomTable(residue_atoms, n_residue_atoms);
      for (int i=0; i<n_residue_atoms; i++) {
	 if (spec.matches_spec(residue_atoms[i])) {
	    // 	    std::cout << "Found a spec match for " << spec << std::endl;
	    return i;
 	 } else {
//  	    std::cout << spec << " does not match " << coot::atom_spec_t(residue_atoms[i])
// 		      << std::endl;
	 }
      }
   }
   return r;
}

// This may well become a general purpose bonding calculator, in a
// base class from which chi angles and monomer are derived.
//
std::vector<std::vector<int> >
coot::torsion_general::get_contact_indices() const {

   std::vector<std::vector<int> > v;
   mmdb::Contact *pscontact = NULL;
   int n_contacts;
   float min_dist = 0.1;
   float max_dist = 1.9; // CB->SG CYS 1.8A
   float max_dist_bond_to_H = 1.42; // is this a good value?
   if (std::string(residue_p->GetResName()) == "MSE")
      max_dist = 2.0;
   long i_contact_group = 1;
   mmdb::mat44 my_matt;
   mmdb::SymOps symm;
   for (int i=0; i<4; i++) 
      for (int j=0; j<4; j++) 
	 my_matt[i][j] = 0.0;      
   for (int i=0; i<4; i++) my_matt[i][i] = 1.0;

   mmdb::PPAtom residue_atoms;
   int n_residue_atoms;
   mmdb::PPAtom non_H_residue_atoms;
   int n_non_H_residue_atoms = 0;
   mmdb::PPAtom H_residue_atoms;
   int n_H_residue_atoms = 0;
   residue_p->GetAtomTable(residue_atoms, n_residue_atoms);

   // count the number of Hydrogens and non-Hydrogrens in the residue,
   // then fill the non_H_residue_atoms and H_residue_atoms.
   // 
   // There is some trickiness because the indices returned from
   // SeekContacts (in the contacts variable) are for the local atom
   // selections, whereas we need them for the residue as a whole, so
   // we keep a mapping from local indexing to residue indexing.
   // 
   for (int i=0; i<n_residue_atoms; i++) {
      std::string element(residue_atoms[i]->element);
      if (element == " H" || element == " D") {
	 n_H_residue_atoms++;
      } else {
	 n_non_H_residue_atoms++;
      }
   }
   non_H_residue_atoms = new mmdb::PAtom[n_non_H_residue_atoms];
   H_residue_atoms = new mmdb::PAtom[n_H_residue_atoms];
   std::vector<int>     H_atom_orig_indcies(    n_H_residue_atoms);
   std::vector<int> non_H_atom_orig_indcies(n_non_H_residue_atoms);
   int iH=0;
   int inH=0;
   for (int i=0; i<n_residue_atoms; i++) {
      std::string element(residue_atoms[i]->element);
      if (element == " H" || element == " D") {
	 H_residue_atoms[iH] = residue_atoms[i];
	 H_atom_orig_indcies[iH]=i;
	 iH++;
      } else {
	 non_H_residue_atoms[inH] = residue_atoms[i];
	 non_H_atom_orig_indcies[inH]=i;
	 inH++;
      }
   }
   
   mol->SeekContacts(non_H_residue_atoms, n_non_H_residue_atoms,
		     non_H_residue_atoms, n_non_H_residue_atoms,
		     min_dist, max_dist, // min, max distances
		     0,        // seqDist 0 -> in same res also
		     pscontact, n_contacts,
		     0, &my_matt, i_contact_group);

   v.resize(n_residue_atoms);
   for (int ic=0; ic< n_contacts; ic++) {
      v[non_H_atom_orig_indcies[pscontact[ic].id1]].push_back(non_H_atom_orig_indcies[pscontact[ic].id2]);
      //       v[non_H_atom_orig_indcies[pscontact[ic].id2]].push_back(non_H_atom_orig_indcies[pscontact[ic].id1]);
   }
   delete [] pscontact;
   pscontact = 0;

   mol->SeekContacts(H_residue_atoms, n_H_residue_atoms,
		     non_H_residue_atoms, n_non_H_residue_atoms,
		     min_dist, max_dist_bond_to_H,
		     0,        // seqDist 0 -> in same res also
		     pscontact, n_contacts,
		     0, &my_matt, i_contact_group);

   v.resize(n_residue_atoms);
   for (int ic=0; ic< n_contacts; ic++) {
      v[H_atom_orig_indcies[pscontact[ic].id1]].push_back(non_H_atom_orig_indcies[pscontact[ic].id2]);
      // v[non_H_atom_orig_indcies[pscontact[ic].id2]].push_back(H_atom_orig_indcies[pscontact[ic].id1]);
   }

   
   if (n_non_H_residue_atoms > 0)
      delete [] non_H_residue_atoms;
   if (n_H_residue_atoms > 0)
      delete [] H_residue_atoms;
   return v;
}
