/* coot-utils/residue-and-atom-specs.hh
 * 
 * Copyright 2011, 2012 by The University of Oxford
 * Copyright 2013, 2015 by Medical Research Council
 * Author: Paul Emsley
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License and
 * the GNU Lesser 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.
 */

#ifndef RESIDUE_AND_ATOM_SPEC_HH
#define RESIDUE_AND_ATOM_SPEC_HH

#include <sstream>
#ifndef __MMDB_Defs__
#include <mmdb2/mmdb_manager.h>
#endif

namespace coot {

   // using an atom spec as the key in a map: strange order/find() failures?
   //
   class atom_spec_t {
   public:
      std::string chain_id;
      int res_no;
      std::string ins_code;
      std::string atom_name;
      std::string alt_conf;
      int int_user_data;
      float float_user_data;
      std::string string_user_data;
      int model_number;
      atom_spec_t() : chain_id("unset") {
         res_no = mmdb::MinInt4;
         model_number = -1;
         int_user_data = -1;
         float_user_data = -1;
      }
      atom_spec_t(const std::string &chain_in,
                  int resno_in,
                  const std::string &insertion_code_in,
                  const std::string &atom_name_in,
                  const std::string &alt_conf_in) : chain_id(chain_in), ins_code(insertion_code_in), atom_name(atom_name_in), alt_conf(alt_conf_in) {
         res_no = resno_in;
         model_number = 1;
         int_user_data = -1;
         float_user_data = -1;
      }
      // This presumes at is a member of a coordinate hierarchy.
      explicit atom_spec_t(mmdb::Atom *at) {
         if (at) {
            chain_id       = at->GetChainID();
            res_no         = at->GetSeqNum();
            ins_code       = at->GetInsCode();
            model_number   = at->GetModelNum();
            atom_name      = at->name;
            alt_conf       = at->altLoc;
         } else {
            chain_id = "unset";
            res_no = mmdb::MinInt4;
            ins_code = "";
            model_number = -1;
         }
         int_user_data = -1; // mark as "unset" (better than not setting it)
         float_user_data = -1;
      }
      // This presumes at is a member of a coordinate hierarchy.
      atom_spec_t(mmdb::Atom *at, const std::string &user_data_string) {
         model_number = at->GetModelNum();
         chain_id = at->GetChainID();
         res_no = at->GetSeqNum();
         ins_code = at->GetInsCode();
         atom_name = at->name;
         alt_conf = at->altLoc;
         string_user_data = user_data_string;
         float_user_data = -1;
         int_user_data = -1;
      }

      bool empty() const {
         if (res_no == mmdb::MinInt4)
            return true;
         else
            return false;
      }

      void selectatoms(mmdb::Manager *mol, int SelHnd) {
         const char *chainid = chain_id.c_str();
         const char *inscode = ins_code.c_str();
         const char *atname  = atom_name.c_str(); // atom name
         const char *altconf = alt_conf.c_str();

         mol->SelectAtoms(SelHnd, 0, chainid, res_no, inscode, res_no, inscode,
                          "*", atname, "*", altconf);
      }

      // Presumes that atom can get to SeqNum() and InsCode()? Need
      // tested against a residue not in a hierarchy.
      bool matches_spec(mmdb::Atom *atom) const;

      std::string format() const;

      std::string label() const;

      std::string label(const std::string &residue_name) const;

      // A 65 CA
      std::string simple_label(const std::string &residue_name="") const;

#ifndef SWIG
      bool operator==(const atom_spec_t &matcher) const {
         bool r = false;
         if (matcher.model_number == model_number) {
            if (matcher.chain_id == chain_id) {
               if (matcher.res_no == res_no) {
                  if (matcher.ins_code == ins_code) {
                     if (matcher.atom_name == atom_name) {
                        if (matcher.alt_conf == alt_conf) {
                           r = true;
                        }
                     }
                  }
               }
            }
         }
         return r;
      }
      bool operator !=(const atom_spec_t &matcher) const {
         return ! operator==(matcher);
      }
#endif

#ifndef SWIG
      // we need this if atom_spec_t are used in a std::map.
      bool operator<(const atom_spec_t &matcher) const {
         if (matcher.empty())
            return false;
         if (empty())
            return true;
         if (matcher.model_number < model_number) {
            return true;
         } else {
            if (matcher.chain_id < chain_id) {
               return true;
            } else {
               if (matcher.res_no < res_no) {
                  return true;
               } else {
                  if (matcher.ins_code < ins_code) {
                     return true;
                  } else {
                     if (matcher.atom_name < atom_name) {
                        return true;
                     } else {
                        if (matcher.alt_conf < alt_conf) {
                           return true;
                        }
                     }
                  }
               }
            }
         }
         return false;
      }
#endif // SWIG

      // like operator==() but we don't test the model
      bool is_same(const atom_spec_t &matcher) const {
         bool r = false;
         if (matcher.chain_id == chain_id) {
            if (matcher.res_no == res_no) {
               if (matcher.ins_code == ins_code) {
                  if (matcher.atom_name == atom_name) {
                     if (matcher.alt_conf == alt_conf) {
                        r = true;
                     }
                  }
               }
            }
         }
         return r;
      }

      // return null on failure to find atom in mol
      // (this is the inside out version of the function in molecule_class_info_t)
      mmdb::Atom *get_atom(mmdb::Manager *mol) const;

#ifndef SWIG
      friend std::ostream& operator<< (std::ostream& s, const atom_spec_t &spec);
#endif // SWIG
   };
#ifndef SWIG
      std::ostream& operator<< (std::ostream& s, const atom_spec_t &spec);
#endif // SWIG

   bool compare_atom_specs_user_float(const atom_spec_t &a1,
                                      const atom_spec_t &a2);
   bool compare_atom_specs_user_float_in_pair(const std::pair<atom_spec_t, std::string> &a,
                                              const std::pair<atom_spec_t, std::string> &b);
   std::pair<atom_spec_t, atom_spec_t> link_atoms(mmdb::Link  *link, mmdb::Model *model_p=0);
   std::pair<atom_spec_t, atom_spec_t> link_atoms(mmdb::LinkR *link, mmdb::Model *model_p=0);

   class residue_spec_t {
   public:
      int model_number;
      std::string chain_id;
      int res_no;
      std::string ins_code;
      int int_user_data;
      float float_user_data;
      std::string string_user_data;
      explicit residue_spec_t(int r) : res_no(r) {
         model_number = -1;
         int_user_data = -1;
         float_user_data = -1;
      }
      residue_spec_t(const std::string &chain_in, int r) : chain_id(chain_in) {
         model_number = mmdb::MinInt4;
         res_no = r;
         int_user_data = -1;
         float_user_data = -1;
      }
      residue_spec_t(int model_number_in,
                     const std::string &chain_in, int r,
                     const std::string &ins_code_in) : chain_id(chain_in), ins_code(ins_code_in) {
         model_number = model_number_in;
         res_no = r;
         int_user_data = -1;
         float_user_data = -1;
      }
      residue_spec_t(const std::string &chain_in, int r,
                     const std::string &ins_code_in) : chain_id(chain_in), ins_code(ins_code_in) {
         model_number = mmdb::MinInt4;
         res_no = r;
         int_user_data = -1;
         float_user_data = -1;
      }
      explicit residue_spec_t(mmdb::Residue *res) {
         if (! res) {
            chain_id = "";
            model_number = mmdb::MinInt4;
            res_no = mmdb::MinInt4;
            ins_code = "";
         } else {
            chain_id = res->GetChainID();
            model_number = res->GetModelNum();
            res_no = res->GetSeqNum();
            ins_code = res->GetInsCode();
         }
         int_user_data = -1;
      }
      explicit residue_spec_t(const atom_spec_t &atom_spec) : chain_id(atom_spec.chain_id), ins_code(atom_spec.ins_code) {
         model_number = atom_spec.model_number;
         res_no = atom_spec.res_no;
         int_user_data = -1;
         float_user_data = -1;
      }
      // This one for coot_wrap_guile
      residue_spec_t() {
         model_number = mmdb::MinInt4;
         res_no = mmdb::MinInt4;
         int_user_data = -1;
         float_user_data = -1;
      }
      bool unset_p() const {
         bool u = true;
         if (res_no != mmdb::MinInt4)
            u = false;
         return u;
      }
      bool empty() const {
         return unset_p();
      }
      residue_spec_t next() const {
         residue_spec_t r = *this;
         if (res_no != mmdb::MinInt4)
            r.res_no += 1;
         return r;
      }
      residue_spec_t previous() const {
         residue_spec_t r = *this;
         if (res_no != mmdb::MinInt4)
            r.res_no -= 1;
         return r;
      }
#ifndef SWIG
      bool operator==(const residue_spec_t &matcher) const {
         if (matcher.chain_id == chain_id) {
            if (matcher.res_no == res_no) {
               if (matcher.ins_code == ins_code) {
                  return 1;
               }
            }
         }
         return 0;
      }
#endif // SWIG

#ifdef SWIG
#else
      bool operator<(const residue_spec_t &matcher) const{
         if (matcher.chain_id == chain_id) {
            if (matcher.res_no == res_no) {
               if (matcher.ins_code == ins_code) {
                  return 0;
               } else {
                  if (matcher.ins_code < ins_code)
                     return 0;
                  else
                     return 1;
               }
            } else {
               if (matcher.res_no < res_no)
                  return 0;
               else
                  return 1;
            }
         } else {
            if (matcher.chain_id < chain_id)
               return 0;
            else
               return 1;
         }
         // return 0; we can't here
      }
#endif

      std::string format() const;

      std::string label() const;

      std::string label(const std::string &residue_name) const;

      // return null on failure to find residue in mol
      mmdb::Residue *get_residue(mmdb::Manager *mol) const;

      // return an atom selection handle for the selection in the mol
      // that matches the spec.  Caller is responsible for deleting
      // the atom selection.
      //
      // selection_key_type is typically either mmdb::SKEY_NEW or mmdb::SKEY_OR
      //
      int select_atoms(mmdb::Manager *mol, int selhnd,
                       mmdb::SELECTION_KEY selection_key);

#ifndef SWIG
      friend std::ostream& operator<< (std::ostream& s, const residue_spec_t &spec);
#endif // SWIG
   };
#ifndef SWIG
   std::ostream& operator<< (std::ostream& s, const residue_spec_t &spec);
#endif // SWIG

}

#endif // RESIDUE_AND_ATOM_SPEC_HH
