/**********************************************************************
  Molecule - Molecule class derived from the base Primitive class

  Copyright (C) 2007 Donald Ephraim Curtis
  Copyright (C) 2008-2009 Marcus D. Hanwell

  This file is part of the Avogadro molecular editor project.
  For more information, see <http://avogadro.cc/>

  Avogadro 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 2 of the License, or
  (at your option) any later version.

  Avogadro 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 "config.h"

#include "molecule.h"

#include "atom.h"
#include "bond.h"
#include "cube.h"
#include "fragment.h"
#include "mesh.h"
#include "obeigenconv.h"
#include "primitivelist.h"
#include "residue.h"
#include "zmatrix.h"

#include <Eigen/Geometry>
#include <Eigen/LeastSquares>

#include <vector>

#include <openbabel/mol.h>
#include <openbabel/math/vector3.h>
#include <openbabel/griddata.h>
#include <openbabel/grid.h>
#include <openbabel/generic.h>
#include <openbabel/forcefield.h>
#include <openbabel/obiter.h>

#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QVariant>
#include <QtCore/QVector>

namespace Avogadro{

  using std::vector;
  using Eigen::Vector3d;

  class MoleculePrivate {
    public:
      MoleculePrivate() : farthestAtom(0), invalidGeomInfo(true),
                          invalidRings(true), invalidGroupIndices(true),
                          obmol(0), obunitcell(0),
                          obvibdata(0), obdosdata(0),
                          obelectronictransitiondata(0)
    {}
    // These are logically cached variables and thus are marked as mutable.
    // Const objects should be logically constant (and not mutable)
    // http://www.highprogrammer.com/alan/rants/mutable.html
      mutable Eigen::Vector3d       center;
      mutable Eigen::Vector3d       normalVector;
      mutable double                radius;
      mutable Atom *                farthestAtom;
      mutable bool                  invalidGeomInfo;
      mutable bool                  invalidRings;
      mutable bool                  invalidGroupIndices;
      mutable std::vector<double>   energies;

      // std::vector used over QVector due to index issues, QVector uses ints
      std::vector<Cube *>           cubes;
      std::vector<Mesh *>           meshes;
      std::vector<Residue *>        residues;
      std::vector<Fragment *>       rings;
      std::vector<ZMatrix *>        zMatrix;

      // Used to store the index based list (not unique ids)
      QList<Cube *>                 cubeList;
      QList<Mesh *>                 meshList;
      QList<Residue *>              residueList;
      QList<Fragment *>             ringList;
      QList<ZMatrix *>              zMatrixList;

      // Our OpenBabel OBMol object
      OpenBabel::OBMol *            obmol;
      // Our OpenBabel OBUnitCell object (if any)
      OpenBabel::OBUnitCell *       obunitcell;
      // Our OpenBabel OBVibrationData object (if any)
      // TODO: Cache an OBMol, in which case the vib. data (and others)
      //       won't be necessary
      OpenBabel::OBVibrationData *  obvibdata;
      OpenBabel::OBDOSData *        obdosdata;
      OpenBabel::OBElectronicTransitionData *
                                    obelectronictransitiondata;
  };

  Molecule::Molecule(QObject *parent) : Primitive(MoleculeType, parent),
                                        d_ptr(new MoleculePrivate),
                                        m_atomPos(0),
                                        m_currentConformer(0),
                                        m_estimatedDipoleMoment(true),
                                        m_dipoleMoment(0),
                                        m_invalidPartialCharges(true),
                                        m_invalidAromaticity(true),
                                        m_lock(new QReadWriteLock)
  {
    connect(this, SIGNAL(updated()), this, SLOT(updatePrimitive()));
    // Assign a default path and file name to new molecules.
    m_fileName = QDir::homePath() + '/' +
                 tr("untitled", "Name of a new, untitled molecule file") +
                 ".cml";
  }

  Molecule::Molecule(const Molecule &other) :
    Primitive(MoleculeType, other.parent()), d_ptr(new MoleculePrivate),
    m_atomPos(0), m_dipoleMoment(0), m_invalidPartialCharges(true),
    m_invalidAromaticity(true), m_lock(new QReadWriteLock)
  {
    *this = other;
    connect(this, SIGNAL(updated()), this, SLOT(updatePrimitive()));
  }

  Molecule::~Molecule()
  {
    // Need to iterate through all atoms/bonds and destroy them
    disconnect(this, 0);
    blockSignals(true);
    clear();
    delete m_lock;
    delete d_ptr;
  }

  void Molecule::setFileName(const QString& name)
  {
    m_fileName = name;
  }

  QString Molecule::fileName() const
  {
    return m_fileName;
  }

  Atom *Molecule::addAtom()
  {
    // Add an atom with the next unique id
    return addAtom(m_atoms.size());
  }

    // do some fancy footwork when we add an atom previously created
  Atom *Molecule::addAtom(unsigned long id)
  {
    Q_D(const Molecule);
    d->invalidGeomInfo = true;
    Atom *atom = new Atom(this);

    if (!m_atomPos) {
      m_atomConformers.resize(1);
      m_atomConformers[0] = new vector<Vector3d>;
      m_atomPos = m_atomConformers[0];
      m_atomPos->reserve(100);
    }

    if(id >= m_atoms.size()) {
      m_atoms.resize(id+1,0);
      m_atomPos->resize(id+1, Vector3d::Zero());
    }
    m_atoms[id] = atom;
    // Does this still want to have the same index as before somehow?
    m_atomList.push_back(atom);

    atom->setId(id);
    atom->setIndex(m_atomList.size()-1);
    // now that the id is correct, emit the signal
    connect(atom, SIGNAL(updated()), this, SLOT(updateAtom()));
    d->invalidGroupIndices = true;
    emit atomAdded(atom);
    return atom;
  }

  Atom *Molecule::addAtom(int atomicNum, const Eigen::Vector3d &pos)
  {
    // Add a new atom with the next unique id
    const unsigned long newId = m_atoms.size();
    Atom *newAtom = this->addAtom(newId);

    newAtom->m_atomicNumber = atomicNum;
    (*m_atomPos)[newId] = pos;

    return newAtom;
  }

  Atom *Molecule::addAtom(const Atom &other)
  {
    // Add a new atom with the next unique id
    Atom *newAtom = this->addAtom(m_atoms.size());
    *newAtom = other;

    return newAtom;
  }

  void Molecule::setAtomPos(unsigned long id, const Eigen::Vector3d& vec)
  {
    Q_D(const Molecule);
    if (id < m_atomPos->size()) {
      (*m_atomPos)[id] = vec;
      d->invalidGeomInfo = true;
    }
  }

  void Molecule::setAtomPos(unsigned long id, const Eigen::Vector3d *vec)
  {
    if (vec)
      setAtomPos(id, *vec);
  }

  void Molecule::removeAtom(Atom *atom)
  {
    Q_D(const Molecule);
    if(atom && atom->parent() == this) {
      // When deleting an atom this also implicitly deletes any bonds to the atom
      foreach (unsigned long bond, atom->bonds()) {
        removeBond(bond);
      }

      m_atoms[atom->id()] = 0;
      // 1 based arrays stored/shown to user
      int index = atom->index();
      m_atomList.removeAt(index);
      for (int i = index; i < m_atomList.size(); ++i)
        m_atomList[i]->setIndex(i);
      atom->deleteLater();

      disconnect(atom, SIGNAL(updated()), this, SLOT(updateAtom()));
      d->invalidGroupIndices = true;
      emit atomRemoved(atom);
    }
  }

  void Molecule::removeAtom(unsigned long id)
  {
    removeAtom(atomById(id));
  }

  Bond *Molecule::addBond()
  {
    return addBond(m_bonds.size());
  }

  Bond *Molecule::addBond(unsigned long id)
  {
    Q_D(Molecule);
    Bond *bond = new Bond(this);

    d->invalidRings = true;
    m_invalidPartialCharges = true;
    m_invalidAromaticity = true;
    if(id >= m_bonds.size())
      m_bonds.resize(id+1,0);
    m_bonds[id] = bond;
    m_bondList.push_back(bond);

    bond->setId(id);
    bond->setIndex(m_bondList.size()-1);
    // now that the id is correct, emit the signal
    connect(bond, SIGNAL(updated()), this, SLOT(updateBond()));
    emit bondAdded(bond);
    return(bond);
  }

  Bond *Molecule::addBond(unsigned long beginAtomId, unsigned long endAtomId,
                          short order)
  {
    // Add a new bond with the next unique id
    Bond *newBond = this->addBond(m_bonds.size());
    newBond->setAtoms(beginAtomId, endAtomId, order);
    return newBond;
  }

  Bond *Molecule::addBond(Atom *beginAtom, Atom *endAtom, short order)
  {
    // Add a new bond with the next unique id
    Bond *newBond = this->addBond(m_bonds.size());
    newBond->setAtoms(beginAtom->m_id, endAtom->m_id, order);
    return newBond;
  }

  void Molecule::removeBond(Bond *bond)
  {
    if(bond && bond->parent() == this) {
      removeBond(bond->id());
    }
  }

  void Molecule::removeBond(unsigned long id)
  {
    if (id < m_bonds.size()) {
      Q_D(Molecule);
      if (m_bonds[id] == 0)
        return;

      d->invalidRings = true;
      m_invalidPartialCharges = true;
      m_invalidAromaticity = true;
      Bond *bond = m_bonds[id];
      m_bonds[id] = 0;
      // Delete the bond from the list and reorder the remaining bonds
      int index = bond->index();
      m_bondList.removeAt(index);
      for (int i = index; i < m_bondList.size(); ++i) {
        m_bondList[i]->setIndex(i);
      }

      // Also delete the bond from the attached atoms
      if (m_atoms.size() > bond->beginAtomId()) {
        if (m_atoms[bond->beginAtomId()])
          m_atoms[bond->beginAtomId()]->removeBond(id);
      }
      if (m_atoms.size() > bond->endAtomId()) {
        if (m_atoms[bond->endAtomId()])
          m_atoms[bond->endAtomId()]->removeBond(id);
      }

      disconnect(bond, SIGNAL(updated()), this, SLOT(updateBond()));
      emit bondRemoved(bond);
      bond->deleteLater();
    }
  }

  Residue *Molecule::residue(int index)
  {
    Q_D(Molecule);

    if (index >= 0 && index < d->residueList.size())
      return d->residueList[index];
    else
      return 0;
  }

  const Residue *Molecule::residue(int index) const
  {
    Q_D(const Molecule);
    if (index >= 0 && index < d->residueList.size())
      return d->residueList[index];
    else
      return 0;
  }

  Residue *Molecule::residueById(unsigned long id) const
  {
    Q_D(const Molecule);
    if(id < d->residues.size())
      return d->residues[id];
    else
      return 0;
  }

  Cube *Molecule::cube(int index) const
  {
    Q_D(const Molecule);
    if (index >= 0 && index < d->cubeList.size())
      return d->cubeList[index];
    else
      return 0;
  }

  Cube *Molecule::cubeById(unsigned long id) const
  {
    Q_D(const Molecule);
    if(id < d->cubes.size())
      return d->cubes[id];
    else
      return 0;
  }

  Cube *Molecule::addCube()
  {
    Q_D(const Molecule);
    return addCube(d->cubes.size());
  }

  Cube *Molecule::addCube(unsigned long id)
  {
    Q_D(Molecule);

    Cube *cube = new Cube(this);

    if(id >= d->cubes.size())
      d->cubes.resize(id+1,0);
    d->cubes[id] = cube;
    // Does this still want to have the same index as before somehow?
    d->cubeList.push_back(cube);

    cube->setId(id);
    cube->setIndex(d->cubeList.size()-1);

    // now that the id is correct, emit the signal
    connect(cube, SIGNAL(updated()), this, SLOT(updatePrimitive()));
    emit primitiveAdded(cube);
    return(cube);
  }

  void Molecule::removeCube(Cube *cube)
  {
    Q_D(Molecule);
    if(cube && cube->parent() == this) {
      d->cubes[cube->id()] = 0;
      // 0 based arrays stored/shown to user
      int index = cube->index();
      d->cubeList.removeAt(index);
      for (int i = index; i < d->cubeList.size(); ++i)
        d->cubeList[i]->setIndex(i);

      cube->deleteLater();
      disconnect(cube, SIGNAL(updated()), this, SLOT(updatePrimitive()));
      emit primitiveRemoved(cube);
    }
  }

  void Molecule::removeCube(unsigned long id)
  {
    Q_D(Molecule);
    if (id < d->cubes.size())
      removeCube(d->cubes[id]);
  }

  Mesh * Molecule::addMesh()
  {
    Q_D(const Molecule);
    return addMesh(d->meshes.size());
  }

  Mesh * Molecule::addMesh(unsigned long id)
  {
    Q_D(Molecule);

    Mesh *mesh = new Mesh(this);

    if (id >= d->meshes.size())
      d->meshes.resize(id+1,0);
    d->meshes[id] = mesh;
    d->meshList.push_back(mesh);

    mesh->setId(id);
    mesh->setIndex(d->meshList.size()-1);

    // now that the id is correct, emit the signal
    connect(mesh, SIGNAL(updated()), this, SLOT(updatePrimitive()));
    emit primitiveAdded(mesh);
    return(mesh);
  }

  void Molecule::removeMesh(Mesh *mesh)
  {
    Q_D(Molecule);
    if(mesh && mesh->parent() == this) {
      d->meshes[mesh->id()] = 0;
      // 0 based arrays stored/shown to user
      int index = mesh->index();
      d->meshList.removeAt(index);
      for (int i = index; i < d->meshList.size(); ++i)
        d->meshList[i]->setIndex(i);

      mesh->deleteLater();
      disconnect(mesh, SIGNAL(updated()), this, SLOT(updatePrimitive()));
      emit primitiveRemoved(mesh);
    }
  }

  void Molecule::removeMesh(unsigned long id)
  {
    Q_D(Molecule);
    if (id < d->meshes.size())
      removeMesh(d->meshes[id]);
  }

  Mesh * Molecule::mesh(int index) const
  {
    Q_D(const Molecule);
    if (index >= 0 && index < d->meshList.size())
      return d->meshList[index];
    else
      return 0;
  }

  Mesh * Molecule::meshById(unsigned long id) const
  {
    Q_D(const Molecule);
    if(id < d->meshes.size())
      return d->meshes[id];
    else
      return 0;
  }

  Residue * Molecule::addResidue()
  {
    Q_D(const Molecule);
    return addResidue(d->residues.size());
  }

  Residue * Molecule::addResidue(unsigned long id)
  {
    Q_D(Molecule);

    Residue *residue = new Residue(this);

    if (id >= d->residues.size())
      d->residues.resize(id+1,0);
    d->residues[id] = residue;
    d->residueList.push_back(residue);

    residue->setId(id);
    residue->setIndex(d->residueList.size()-1);

    // now that the id is correct, emit the signal
    connect(residue, SIGNAL(updated()), this, SLOT(updatePrimitive()));
    emit primitiveAdded(residue);
    return(residue);
  }

  void Molecule::removeResidue(Residue *residue)
  {
    Q_D(Molecule);
    if(residue && residue->parent() == this) {
      d->residues[residue->id()] = 0;
      // 0 based arrays stored/shown to user
      int index = residue->index();
      d->residueList.removeAt(index);
      for (int i = index; i < d->residueList.size(); ++i) {
        d->residueList[i]->setIndex(i);
      }

      residue->deleteLater();
      disconnect(residue, SIGNAL(updated()), this, SLOT(updatePrimitive()));
      emit primitiveRemoved(residue);
    }
  }

  void Molecule::removeResidue(unsigned long id)
  {
    Q_D(Molecule);
    if (id < d->residues.size())
      removeResidue(d->residues[id]);
  }

  Fragment * Molecule::addRing()
  {
    Q_D(const Molecule);
    return addRing(d->rings.size());
  }

  Fragment * Molecule::addRing(unsigned long id)
  {
    Q_D(Molecule);

    Fragment *ring = new Fragment(this);

    if (id >= d->rings.size())
      d->rings.resize(id+1,0);
    d->rings[id] = ring;
    d->ringList.push_back(ring);

    ring->setId(id);
    ring->setIndex(d->ringList.size()-1);

    // now that the id is correct, emit the signal
    connect(ring, SIGNAL(updated()), this, SLOT(updatePrimitive()));
//    emit primitiveAdded(ring);
    return(ring);
  }

  void Molecule::removeRing(Fragment *ring)
  {
    Q_D(Molecule);
    if(ring && ring->parent() == this) {
      d->rings[ring->id()] = 0;
      // 0 based arrays stored/shown to user
      int index = ring->index();
      d->ringList.removeAt(index);
      for (int i = index; i < d->ringList.size(); ++i) {
        d->ringList[i]->setIndex(i);
      }

      ring->deleteLater();
      disconnect(ring, SIGNAL(updated()), this, SLOT(updatePrimitive()));
//      emit primitiveRemoved(ring);
    }
  }

  void Molecule::removeRing(unsigned long id)
  {
    Q_D(Molecule);
    if (id < d->rings.size())
      removeRing(d->rings[id]);
  }

  ZMatrix * Molecule::addZMatrix()
  {
    Q_D(Molecule);
    ZMatrix *zmatrix = new ZMatrix(this);
    d->zMatrixList.push_back(zmatrix);

    return zmatrix;
  }

  void Molecule::removeZMatrix(ZMatrix *zmatrix)
  {
    Q_D(Molecule);
    if (zmatrix) {
      d->zMatrixList.removeAll(zmatrix);
      delete zmatrix;
    }
  }

  ZMatrix * Molecule::zMatrix(int index) const
  {
    Q_D(const Molecule);
    if (index < d->zMatrixList.size())
      return d->zMatrixList.at(index);
    else
      return 0;
  }

  QList<ZMatrix *> Molecule::zMatrices() const
  {
    Q_D(const Molecule);
    return d->zMatrixList;
  }

  unsigned int Molecule::numZMatrices() const
  {
    Q_D(const Molecule);
    return d->zMatrixList.size();
  }

  void Molecule::addHydrogens(Atom *a,
                              const QList<unsigned long> &atomIds,
                              const QList<unsigned long> &bondIds)
  {
    if (atomIds.size() != bondIds.size()) {
      qDebug() << "Error, addHydrogens called with atom & bond id lists of different size!";
    }

    // Construct an OBMol, call AddHydrogens and translate the changes
    OpenBabel::OBMol obmol = OBMol();
    if (a) {
      OpenBabel::OBAtom *obatom = obmol.GetAtom(a->index()+1);
      // Set implicit valence for unusual elements not handled by OpenBabel
      // PR#2803076
      switch (obatom->GetAtomicNum()) {
      case 3:
      case 11:
      case 19:
      case 37:
      case 55:
      case 85:
      case 87:
        obatom->SetImplicitValence(1);
        obatom->SetHyb(1);
        obmol.SetImplicitValencePerceived();
        break;

      case 4:
      case 12:
      case 20:
      case 38:
      case 56:
      case 88:
        obatom->SetImplicitValence(2);
        obatom->SetHyb(2);
        obmol.SetImplicitValencePerceived();
        break;

      case 84: // Po
        obatom->SetImplicitValence(2);
        obatom->SetHyb(3);
        obmol.SetImplicitValencePerceived();
        break;

      default: // do nothing
        break;
      }
      obmol.AddHydrogens(obatom);
    }
    else
      obmol.AddHydrogens();
    // All new atoms in the OBMol must be the additional hydrogens
    unsigned int numberAtoms = numAtoms();
    int j = 0;
    for (unsigned int i = numberAtoms+1; i <= obmol.NumAtoms(); ++i, ++j) {
      if (obmol.GetAtom(i)->IsHydrogen()) {
        OpenBabel::OBAtom *obatom = obmol.GetAtom(i);
        Atom *atom;
        if (atomIds.isEmpty())
          atom = addAtom();
        else if (j < atomIds.size())
          atom = addAtom(atomIds.at(j));
        else {
          qDebug() << "Error - not enough unique ids in addHydrogens.";
          break;
        }
        atom->setOBAtom(obatom);
        // Get the neighbor atom
        OpenBabel::OBBondIterator iter;
        OpenBabel::OBAtom *next = obatom->BeginNbrAtom(iter);
        Bond *bond;
        if (bondIds.isEmpty())
          bond = addBond();
        else // Already confirmed by atom ids
          bond = addBond(bondIds.at(j));
        bond->setEnd(Molecule::atom(atom->index()));
        bond->setBegin(Molecule::atom(next->GetIdx()-1));
      }
    }
    for (unsigned int i = 1; i <= numberAtoms; ++i) {
      // Warning -- OB atom index off-by-one here
      atom(i-1)->setPartialCharge(obmol.GetAtom(i)->GetPartialCharge());
    }
  }

  void Molecule::removeHydrogens(Atom *atom)
  {
    if (atom) {
      // Delete any connected hydrogen atoms
      QList<unsigned long> neighbors = atom->neighbors();

      foreach (unsigned long a, neighbors) {
        Atom *nbrAtom = atomById(a);
        // we need to check if the atom still exists
        if (nbrAtom) {
          if (nbrAtom->isHydrogen()) {
            removeAtom(a);
          }
        }
        else {
          qDebug() << "Error, atom advertising deleted neighbor" << atom->id()
                   << a;
        }
      }
    }
    // Delete all of the hydrogens
    else {
      foreach (Atom *atom, m_atomList) {
        if (atom->isHydrogen()) {
          removeAtom(atom);
        }
      }
    }
  }

  void Molecule::setDipoleMoment(const Eigen::Vector3d &moment)
  {
    if(!m_dipoleMoment)
      m_dipoleMoment = new Vector3d(moment);
    else
      *m_dipoleMoment = moment;
    m_estimatedDipoleMoment = false;
  }

  Eigen::Vector3d Molecule::dipoleMoment(bool *estimate) const
  {
    if (m_dipoleMoment && !m_estimatedDipoleMoment) {
      if (estimate != NULL) // passed this as an argument
        *estimate = false; // genuine calculated dipole moment
      return *m_dipoleMoment;
    }
    else {
      // Calculate a new estimate (e.g., the geometry changed
      Vector3d dipoleMoment(0.0, 0.0, 0.0);

      foreach (Atom *a, atoms())
        dipoleMoment += *a->pos() * a->partialCharge();

      // convert from electrons * Angstrom to Debye
      // (1.602176487×10−19 C / electron) *  (1.0e-10 m/Ang / 3.33564e-30 C/m)
      // use the negative to go from positive to negative charge (Chemistry)
      dipoleMoment *= -4.80321;

      if (estimate)
        *estimate = true;

      m_estimatedDipoleMoment = true;
      return dipoleMoment;
    }
  }

  void Molecule::calculatePartialCharges() const
  {
    if (numAtoms() < 1 || !m_invalidPartialCharges) {
      return;
    }
    OpenBabel::OBMol obmol = OBMol();
    for (unsigned int i = 0; i < numAtoms(); ++i) {
      // Warning: OB off-by-one index
      atom(i)->setPartialCharge(obmol.GetAtom(i+1)->GetPartialCharge());
    }
    m_invalidPartialCharges = false;
  }

  void Molecule::calculateAromaticity() const
  {
    if (numBonds() < 1 || !m_invalidAromaticity)
      return;

    OpenBabel::OBMol obmol = OBMol();
    for (unsigned int i = 0; i < obmol.NumBonds(); ++i) {
      bond(i)->setAromaticity(obmol.GetBond(i)->IsAromatic());
    }
    m_invalidAromaticity = false;
  }

  void Molecule::calculateGroupIndices() const
  {
    Q_D(const Molecule);
    if(d->invalidGroupIndices) {
      QVector<unsigned int> group_number;   // numbers of atoms in each group
      QVector<int> group_ele;    // elements of each group
      QVector<unsigned int> atomGroupNumber;
      atomGroupNumber.resize(numAtoms());

      for (unsigned int i = 0;
           i < numAtoms() && static_cast<int>(i) < atomGroupNumber.size();
           ++i) {
        bool match = false;
        for (int j=0; j < group_number.size(); ++j) {
          if ((atom(i)->atomicNumber()) == group_ele.at(j)) {
            group_number[j] += 1;
            atomGroupNumber[i] = group_number[j];
            match = true;
          }
        }
        if (!match) {
          group_ele.push_back(atom(i)->atomicNumber());
          group_number.push_back(1);
          atomGroupNumber[i] = 1;
        }
      }

      for (unsigned int i = 0;
           i < numAtoms() && static_cast<int>(i) < atomGroupNumber.size();
           ++i) {
        bool match = false;
        for (int j=0; j<group_number.size(); ++j) {
          if ((atom(i)->atomicNumber()) == group_ele.at(j) &&
              (group_number.at(j) == 1)) {
            match = true;
          }
        }
        if (match) {
          atom(i)->setGroupIndex(0);
        } else {
          atom(i)->setGroupIndex(atomGroupNumber.at(i));
        }
      }
      d->invalidGroupIndices = false;
    }
  }

  unsigned int Molecule::numAtoms() const
  {
    return m_atomList.size();
  }

  unsigned int Molecule::numBonds() const
  {
    return m_bondList.size();
  }

  unsigned int Molecule::numCubes() const
  {
    Q_D(const Molecule);
    return d->cubeList.size();
  }

  unsigned int Molecule::numMeshes() const
  {
    Q_D(const Molecule);
    return d->meshList.size();
  }

  unsigned int Molecule::numResidues() const
  {
    Q_D(const Molecule);
    return d->residueList.size();
  }

  unsigned int Molecule::numRings() const
  {
    Q_D(const Molecule);
    return d->ringList.size();
  }

  void Molecule::updateMolecule()
  {
    Q_D(Molecule);
    d->invalidGeomInfo = true;
    emit moleculeChanged();
    emit updated();
  }

  void Molecule::updatePrimitive()
  {
    Q_D(Molecule);
    Primitive *primitive = qobject_cast<Primitive *>(sender());
    d->invalidGeomInfo = true;
    emit primitiveUpdated(primitive);
  }

  void Molecule::updateAtom()
  {
    Q_D(Molecule);
    Atom *atom = qobject_cast<Atom *>(sender());
    d->invalidGeomInfo = true;
    d->invalidGroupIndices = true;
    emit atomUpdated(atom);
  }

  void Molecule::updateBond()
  {
    Bond *bond = qobject_cast<Bond *>(sender());
    emit bondUpdated(bond);
  }

  void Molecule::update()
  {
    emit updated();
  }

  Bond* Molecule::bond(unsigned long id1, unsigned long id2)
  {
    // Take two atom IDs and see if we have a bond between the two
    if (atomById(id1)) {
      QList<unsigned long> bonds = atomById(id1)->bonds();
      foreach (unsigned long id, bonds) {
        Bond *bond = bondById(id);
        if (bond) {
          if (bond->otherAtom(id1) == id2) {
            return bond;
          }
        }
      }
    }
    return 0;
  }

  Bond* Molecule::bond(const Atom *a1, const Atom *a2)
  {
    if (a1 && a2) {
      return bond(a1->id(), a2->id());
    }
    else {
      return 0;
    }
  }

  bool Molecule::addConformer(const std::vector<Eigen::Vector3d> &conformer,
                              unsigned int index)
  {
    if (conformer.size() != m_atomPos->size())
      return false;

    if (m_atomConformers.size() < index+1) {
      unsigned int size = m_atomConformers.size();
      // If there is a gap between the current last conformer and the new index, pad it
      for (unsigned int i = size; i <= index; ++i)
        m_atomConformers.push_back( new vector<Vector3d>(m_atomPos->size()) );
    }
    *m_atomConformers[index] = conformer;
    return true;
  }

  vector<Vector3d> * Molecule::addConformer(unsigned int index)
  {
    if (index < m_atomConformers.size())
      return m_atomConformers[index];
    else {
      unsigned int size = m_atomConformers.size();
      m_atomConformers.resize(index+1);
      for (unsigned int i = size; i <= index; ++i)
        m_atomConformers[i] = new vector<Vector3d>(m_atomPos->size());
      return m_atomConformers[index];
    }
  }

  vector<Vector3d> * Molecule::conformer(unsigned int index)
  {
    if (index && index < m_atomConformers.size())
      return m_atomConformers[index];
    else if (index == 0)
      return m_atomPos;
    else
      return NULL;
  }

  const std::vector<std::vector<Eigen::Vector3d> *>& Molecule::conformers() const
  {
    return m_atomConformers;
  }

  bool Molecule::setConformer(unsigned int index)
  {
    // If the index is higher than the size
    if (m_atomConformers.size() < index + 1)
      return false;
    else {
      // m_atomPos is resized if atoms are added/deleted, we store it's size here
      unsigned int size = m_atomPos->size();
      // Set the current m_atomPos
      m_atomPos = m_atomConformers[index];
      // It is possible new atoms got added since the conformers were set.
      // Here, we pad m_atomPos to match the current number of atoms.
      while (m_atomPos->size() < size)
        m_atomPos->push_back(Eigen::Vector3d::Zero());
      // set the current conformer index
      m_currentConformer = index;
      return true;
    }
  }

  bool Molecule::setAllConformers(const std::vector< std::vector<Eigen::Vector3d>* > conformers, bool deleteExisting)
  {
    if (!conformers.size()) {
      clearConformers();
      return true;
    }
    unsigned long size = m_atomPos->size();

    // delete any previous conformers
    // TODO: Combine this code with clearConformers()
    if (deleteExisting) {
      for (unsigned int i = 0; i < m_atomConformers.size(); ++i)
        delete m_atomConformers[i];
    }
    m_atomConformers.clear();

    // add the new conformers
    for (unsigned int i = 0; i < conformers.size(); ++i) {
      if (conformers[i]->size() != size)
        return false;
      m_atomConformers.push_back(conformers[i]);
    }

    m_atomPos = m_atomConformers[0];
    m_currentConformer = 0;
    return true;
  }

  void Molecule::clearConformers()
  {
    if (m_atomConformers.size() > 1) {
      for (unsigned int i = 1; i < m_atomConformers.size(); ++i)
        delete m_atomConformers[i];
      m_atomConformers.resize(1);
      m_atomPos = m_atomConformers[0];
    }
    m_currentConformer = 0;
  }

  unsigned int Molecule::numConformers() const
  {
    return m_atomConformers.size();
  }

  unsigned int Molecule::currentConformer() const
  {
    return m_currentConformer;
  }

  const std::vector<double>& Molecule::energies() const
  {
    Q_D(const Molecule);
    while (d->energies.size() < numConformers())
      d->energies.push_back(0.0);
    if (d->energies.size() > numConformers())
      d->energies.resize(numConformers());
    return d->energies;
  }

  double Molecule::energy(int index) const
  {
    Q_D(const Molecule);
    if (index == -1 && d->energies.size()) // if there are any...
      return d->energies[m_currentConformer];
    else if (index >= 0 && index < static_cast<int>(d->energies.size()))
      return d->energies[index];
    else
      return 0.0;
  }

  void Molecule::setEnergy(double energy)
  {
    Q_D(const Molecule);
    while (d->energies.size() < numConformers())
      d->energies.push_back(0.0);
    if (m_currentConformer < d->energies.size())
      d->energies[m_currentConformer] = energy;
  }

  void Molecule::setEnergy(int index, double energy)
  {
    Q_D(const Molecule);
    if (index > static_cast<int>(numConformers() - 1) || index < 0)
      return;
    while (d->energies.size() != numConformers())
      d->energies.push_back(0.0);
    d->energies[index] = energy;
  }

  void Molecule::setEnergies(const std::vector<double>& energies)
  {
    Q_D(const Molecule);
    d->energies = energies;
  }


  QList<Atom *> Molecule::atoms() const
  {
    return m_atomList;
  }

  QList<Bond *> Molecule::bonds() const
  {
    return m_bondList;
  }

  QList<Cube *> Molecule::cubes() const
  {
    Q_D(const Molecule);
    return d->cubeList;
  }

  QList<Mesh *> Molecule::meshes() const
  {
    Q_D(const Molecule);
    return d->meshList;
  }

  QList<Residue *> Molecule::residues() const
  {
    Q_D(const Molecule);
    return d->residueList;
  }

  QList<Fragment *> Molecule::rings()
  {
    Q_D(Molecule);
    // Check is the rings need updating before returning the list
    if(d->invalidRings) {
      // Now update the rings
      foreach(Fragment *ring, d->ringList) {
        removeRing(ring);
      }
      OpenBabel::OBMol obmol = OBMol();
      std::vector<OpenBabel::OBRing *> rings;
      rings = obmol.GetSSSR();
      foreach(OpenBabel::OBRing *r, rings) {
        Fragment *ring = addRing();
        foreach(int index, r->_path) {
          ring->addAtom(atom(index-1)->id());
        }
      }
      d->invalidRings = false;
    }
    return d->ringList;
  }

  OpenBabel::OBMol Molecule::OBMol() const
  {
    Q_D(const Molecule);
    // Right now we make an OBMol each time
    OpenBabel::OBMol obmol;
    obmol.BeginModify();

    foreach(Atom *atom, m_atomList) {
      OpenBabel::OBAtom *a = obmol.NewAtom();
      OpenBabel::OBAtom obatom = atom->OBAtom();
      *a = obatom;
    }
    // we are copying partial charges above
    obmol.SetPartialChargesPerceived();
    foreach(Bond *bond, m_bondList) {
      Atom *beginAtom = atomById(bond->beginAtomId());
      if (!beginAtom)
        continue;

      Atom *endAtom = atomById(bond->endAtomId());
      if (!endAtom)
        continue;

      obmol.AddBond(beginAtom->index() + 1,
                    endAtom->index() + 1, bond->order());

      QString label = bond->customLabel();
      if(!label.isEmpty()) {
        OpenBabel::OBPairData *dp = new OpenBabel::OBPairData();
        dp->SetAttribute("label");
        dp->SetValue(label.toLatin1());
        obmol.GetBond(obmol.NumBonds()-1)->SetData(dp);
      }
    }
    // We're doing this after copying all atoms, so we can grab them ourselves
    foreach(Residue *residue, d->residueList) {
      OpenBabel::OBResidue *r = obmol.NewResidue();
      // Copy per-residue information
      r->SetNum(residue->number().toStdString());
      r->SetChain(residue->chainID());
      r->SetName(residue->name().toUpper().toStdString());

      OpenBabel::OBAtom *a;
      foreach(unsigned long atomId, residue->atoms()){
        // Avogadro indexes from 0, but OB from 1. Watch out!
        Atom *avoAtom = this->atomById(atomId);
        if (!avoAtom)
          continue;
        a = obmol.GetAtom(avoAtom->index() + 1);
        r->AddAtom(a);
        r->SetSerialNum(a, a->GetIdx());
        QString atomLabel = residue->atomId(atomId);
        if (!atomLabel.isEmpty())
          r->SetAtomID(a, atomLabel.toStdString());
        else {
          r->SetAtomID(a, OpenBabel::etab.GetSymbol(avoAtom->atomicNumber()));
          r->SetHetAtom(a, true);
        }
      }
    }
    foreach(Cube *cube, d->cubeList) {
      OpenBabel::OBGridData *obgrid = new OpenBabel::OBGridData;
      obgrid->SetOrigin(OpenBabel::fileformatInput);
      obgrid->SetAttribute(cube->name().toLatin1().data());
      obgrid->SetUnit(OpenBabel::OBGridData::ANGSTROM);
      obgrid->SetNumberOfPoints(cube->dimensions().x(),
                                cube->dimensions().y(),
                                cube->dimensions().z());
      OpenBabel::vector3 origin(cube->min().x(), cube->min().y(), cube->min().z());
      OpenBabel::vector3 x(cube->spacing().x(), 0.0, 0.0);
      OpenBabel::vector3 y(0.0, cube->spacing().y(), 0.0);
      OpenBabel::vector3 z(0.0, 0.0, cube->spacing().z());
      obgrid->SetLimits(origin, x, y, z);
      obgrid->SetValues(cube->m_data);
      obmol.SetData(obgrid);
    }

    obmol.EndModify();

    // Copy energy
    obmol.SetEnergy(this->energy() / KCAL_TO_KJ);

    // Copy unit cells
    if (d->obunitcell != NULL) {
      OpenBabel::OBUnitCell *obunitcell = new OpenBabel::OBUnitCell;
      *obunitcell = *d->obunitcell;
      obmol.SetData(obunitcell);
    }

    // Copy OBPairData, if needed
    OpenBabel::OBPairData *obproperty;
    foreach(const QByteArray &propertyName, dynamicPropertyNames()) {
      obproperty = new OpenBabel::OBPairData;
      obproperty->SetAttribute(propertyName.data());
      obproperty->SetValue(property(propertyName).toByteArray().data());
      obmol.SetData(obproperty);
    }

    // Copy vibrations, if needed
    if (d->obvibdata != NULL) {
      obmol.SetData(d->obvibdata->Clone(&obmol));
    }

    // Copy dos, if needed
    if (d->obdosdata != NULL) {
      obmol.SetData(d->obdosdata->Clone(&obmol));
    }

    // Copy excited states data, if needed
    if (d->obelectronictransitiondata != NULL) {
      obmol.SetData(d->obelectronictransitiondata->Clone(&obmol));
    }

    return obmol;
  }

  bool Molecule::setOBMol(OpenBabel::OBMol *obmol)
  {
    // Take an OBMol, copy everything we need and store this object
    Q_D(Molecule);
    clear();
    // Copy all the parts of the OBMol to our Molecule
    blockSignals(true);

    std::vector<OpenBabel::OBAtom*>::iterator i;

    // If available, copy the unit cell
    // We actually do this first, since the obmol will have more atoms afterwards
    OpenBabel::OBUnitCell *obunitcell = static_cast<OpenBabel::OBUnitCell *>
      (obmol->GetData(OpenBabel::OBGenericDataType::UnitCell));
    if (obunitcell) {
      d->obunitcell = new OpenBabel::OBUnitCell;
      *d->obunitcell = *obunitcell;

      // Check if there's a space group set
      // If so, chances are good that we should fill the unit cell
      const OpenBabel::SpaceGroup *sg = d->obunitcell->GetSpaceGroup();
      if (sg) {
        // Check if it's likely an organic system
        int numCarbons = 0;
        int numHydrogens = 0;
        for (OpenBabel::OBAtom *obatom = obmol->BeginAtom(i); obatom; obatom = obmol->NextAtom(i)) {
          if (obatom->IsCarbon())
            numCarbons++;
          if (obatom->IsHydrogen())
            numHydrogens++;
        }
        // Here's the heuristic. If there are >4 carbons and/or carbon + hydrogen (e.g. methane),
        // then we assume it's organic or other molecular solid and leave it alone
        // If there wasn't an assigned space group, it's likely from a comp package (e.g., VASP)
        // and we don't need to bother
        if (numCarbons < 4 && !(numCarbons && numHydrogens)){
          d->obunitcell->FillUnitCell(obmol);
        }
      }
    }

    // Begin by copying all of the atoms
    for (OpenBabel::OBAtom *obatom = obmol->BeginAtom(i); obatom; obatom = obmol->NextAtom(i)) {
      Atom *atom = addAtom();
      atom->setOBAtom(obatom);
    }

    // Now bonds, we use the indices of the atoms to get the bonding right
    std::vector<OpenBabel::OBBond*>::iterator j;
    for (OpenBabel::OBBond *obbond = obmol->BeginBond(j); obbond; obbond = obmol->NextBond(j)) {
      Bond *bond = addBond();
      // Get the begin and end atoms - we use a 0 based index, OB uses 1 based
      bond->setAtoms(obbond->GetBeginAtom()->GetIdx()-1,
                     obbond->GetEndAtom()->GetIdx()-1,
                     obbond->GetBondOrder());
      if (obbond->HasData("label"))
        bond->setCustomLabel(obbond->GetData("label")->GetValue().c_str());
    }

    // Now for the volumetric data
    std::vector<OpenBabel::OBGenericData*> data = obmol->GetAllData(OpenBabel::OBGenericDataType::GridData);
    for (unsigned int i = 0; i < data.size(); ++i) {
      QString name = QString(data[i]->GetAttribute().c_str());
      OpenBabel::OBGridData *grid = static_cast<OpenBabel::OBGridData *>(data[i]);
      OpenBabel::vector3 obmin = grid->GetOriginVector();
      Eigen::Vector3d min(obmin.x(), obmin.y(), obmin.z());
      OpenBabel::vector3 obmax = grid->GetMaxVector();
      Eigen::Vector3d max(obmax.x(), obmax.y(), obmax.z());
      int x, y, z;
      grid->GetNumberOfPoints(x, y, z);
      Eigen::Vector3i points(x, y, z);
      Cube *cube = addCube();
      cube->setLimits(min, max, points);
      cube->setData(grid->GetValues());
      cube->setName(name);
      cube->setCubeType(Cube::FromFile);
    }

    // Copy the residues across...
    std::vector<OpenBabel::OBResidue *> residues;
    OpenBabel::OBResidueIterator iResidue;
    short chainNumber = 0;
    QHash<char, short> chains;
    for (OpenBabel::OBResidue *obres = static_cast<OpenBabel::OBResidue *>(obmol->BeginResidue(iResidue));
          obres; obres = static_cast<OpenBabel::OBResidue *>(obmol->NextResidue(iResidue))) {
      /// Copy these residues!
      Residue *residue = addResidue();
      residue->setName(obres->GetName().c_str());
      residue->setNumber(obres->GetNumString().c_str());
      if (!chains.contains(obres->GetChain())) {
        chains[obres->GetChain()] = chainNumber;
        chainNumber++;
      }
      residue->setChainID(obres->GetChain());
      residue->setChainNumber(chains.value(obres->GetChain()));
      std::vector<OpenBabel::OBAtom*> obatoms = obres->GetAtoms();
      foreach (OpenBabel::OBAtom *obatom, obatoms) {
        unsigned long atomId = atom(obatom->GetIdx()-1)->id();
        residue->addAtom(atomId);
        residue->setAtomId(atomId, obres->GetAtomID(obatom).c_str());
      }
      std::vector<OpenBabel::OBBond*> obbonds = obres->GetBonds();
      foreach (OpenBabel::OBBond *obbond, obbonds) {
        residue->addBond(bond(obbond->GetIdx())->id());
      }
    }

    // Copy conformers, if present
    if (obmol->NumConformers() > 1) {
      for (int i = 0; i < obmol->NumConformers(); ++i) {
        obmol->SetConformer(i);
        // copy the coordinates
        double *coordPtr = obmol->GetCoordinates();
        std::vector<Eigen::Vector3d> conformer;
        foreach (Atom *atom, atoms()) {
          while (conformer.size() < atom->id())
            conformer.push_back(Eigen::Vector3d(0.0, 0.0, 0.0));
          conformer.push_back(Eigen::Vector3d(coordPtr));
          coordPtr += 3;
        } // end foreach atom
        addConformer(conformer, i);
      } // end for(conformers)
      setConformer(obmol->NumConformers() - 1);
      // energies and forces will be set below
    } else {
      // one conformer, so use setEnergy
      setEnergy(obmol->GetEnergy() * KCAL_TO_KJ);
    }

    // Copy conformer data (e.g., energies, forces) if present and valid
    if (obmol->HasData(OpenBabel::OBGenericDataType::ConformerData)) {
      OpenBabel::OBConformerData *cd = static_cast<OpenBabel::OBConformerData*>(obmol->GetData(OpenBabel::OBGenericDataType::ConformerData));
      if (cd) {
        // copy energies -- should be one for each conformer
        std::vector<double> energies = cd->GetEnergies();
        for (unsigned int i = 0; i < energies.size(); ++i)
          energies[i] *= KCAL_TO_KJ;
        setEnergies(energies);

        // check for validity (i.e., we have some forces, one for each atom
        std::vector< std::vector<OpenBabel::vector3> > allForces = cd->GetForces();
        if (allForces.size() && allForces[0].size() == numAtoms()) {
          OpenBabel::vector3 force;
          foreach (Atom *atom, m_atomList) { // loop through each atom
            force = allForces[0][atom->index()];
            atom->setForceVector(Eigen::Vector3d(force.x(), force.y(), force.z()));
          } // end setting forces on each atom
        } // forces
      } // end if (cd)
    }  // end HasData(ConformerData)

    // Copy any vibration data if possible
    if (obmol->HasData(OpenBabel::OBGenericDataType::VibrationData)) {
      OpenBabel::OBVibrationData *vibData = static_cast<OpenBabel::OBVibrationData*>(obmol->GetData(OpenBabel::OBGenericDataType::VibrationData));
      d->obvibdata = vibData;
    }

    // Copy DOS data
    if (obmol->HasData(OpenBabel::OBGenericDataType::DOSData)) {
      OpenBabel::OBDOSData *dosData = static_cast<OpenBabel::OBDOSData*>(obmol->GetData(OpenBabel::OBGenericDataType::DOSData));
      d->obdosdata = dosData;
    }

    // Copy electronic transition data
    if (obmol->HasData(OpenBabel::OBGenericDataType::ElectronicTransitionData)) {
      OpenBabel::OBElectronicTransitionData *etd =
        static_cast<OpenBabel::OBElectronicTransitionData*>
        (obmol->GetData(OpenBabel::OBGenericDataType::ElectronicTransitionData));
      d->obelectronictransitiondata = etd;
    }

    // Copy orbital energies, symbols, and occupations to dynamic properties (as QList<>)
    if (obmol->HasData(OpenBabel::OBGenericDataType::ElectronicData)) {
      OpenBabel::OBOrbitalData *od =
        static_cast<OpenBabel::OBOrbitalData*>(obmol->GetData(OpenBabel::OBGenericDataType::ElectronicData));

      // Source (from OBMol)
      std::vector<OpenBabel::OBOrbital> alphaOrbitals, betaOrbitals;
      std::vector<OpenBabel::OBOrbital>::iterator orbitalIter;
      alphaOrbitals = od->GetAlphaOrbitals();

      // Destinations
      QList<QVariant> alphaEnergies, alphaOcc;
      QStringList alphaSymmetries;
      for (orbitalIter = alphaOrbitals.begin(); orbitalIter != alphaOrbitals.end(); ++orbitalIter) {
        alphaEnergies.append(QVariant(orbitalIter->GetEnergy() * 27.21138)); // convert to eV
        alphaSymmetries.append(orbitalIter->GetSymbol().c_str());
        alphaOcc.append(QVariant(orbitalIter->GetOccupation()));
      }
      setProperty("alphaOrbitalEnergies", alphaEnergies);
      setProperty("alphaOrbitalSymmetries", alphaSymmetries);
      setProperty("alphaOrbitalOccupations", alphaOcc);

      if (od->IsOpenShell()) { // Find and set the beta orbitals
        betaOrbitals = od->GetBetaOrbitals(); // shouldn't be empty
        QList<QVariant> betaEnergies, betaOcc;
        QStringList betaSymmetries;
        for (orbitalIter = betaOrbitals.begin(); orbitalIter != betaOrbitals.end(); ++orbitalIter) {
          betaEnergies.append(QVariant(orbitalIter->GetEnergy() * 27.21138)); // convert to eV
          betaSymmetries.append(orbitalIter->GetSymbol().c_str());
          betaOcc.append(QVariant(orbitalIter->GetOccupation()));
        }
        setProperty("betaOrbitalEnergies", betaEnergies);
        setProperty("betaOrbitalSymmetries", betaSymmetries);
        setProperty("betaOrbitalOccupations", betaOcc);
      }
    }

    // Finally, sync OBPairData to dynamic properties
    OpenBabel::OBDataIterator dIter;
    OpenBabel::OBPairData *property;
    data = obmol->GetAllData(OpenBabel::OBGenericDataType::PairData);
    for (dIter = data.begin(); dIter != data.end(); ++dIter) {
      property = static_cast<OpenBabel::OBPairData *>(*dIter);
      setProperty(property->GetAttribute().c_str(), property->GetValue().c_str());
    }

    computeGeomInfo();

    // Copy the dipole moment of the molecule - do this after calling the
    // initial computeGeomInfo call
    OpenBabel::OBVectorData *vd = (OpenBabel::OBVectorData*)obmol->GetData("Dipole Moment");
    if (vd) {
      OpenBabel::vector3 moment = vd->GetData();
      if (!m_dipoleMoment)
        m_dipoleMoment = new Vector3d(moment.x(), moment.y(), moment.z());
      else
        *m_dipoleMoment = Vector3d(moment.x(), moment.y(), moment.z());
      m_estimatedDipoleMoment = false;
    }

    // we set the partial charges above
    m_invalidPartialCharges = false;

    blockSignals(false);
    emit update();
    return true;
  }

  OpenBabel::OBUnitCell *Molecule::OBUnitCell() const
  {
    Q_D(const Molecule);
    return d->obunitcell;
  }

  bool Molecule::setOBUnitCell(OpenBabel::OBUnitCell *obunitcell)
  {
    Q_D(Molecule);
    d->obunitcell = obunitcell;
    if (obunitcell == NULL) {
      // delete it from our private obmol
      if (d->obmol)
        d->obmol->DeleteData(OpenBabel::OBGenericDataType::UnitCell);
    }
    return true;
  }

  const Eigen::Vector3d Molecule::center() const
  {
    Q_D(const Molecule);
    if( d->invalidGeomInfo ) computeGeomInfo();
    return d->center;
  }

  const Eigen::Vector3d Molecule::normalVector() const
  {
    Q_D(const Molecule);
    if( d->invalidGeomInfo ) computeGeomInfo();
    return d->normalVector;
  }

  double Molecule::radius() const
  {
    Q_D(const Molecule);
    if( d->invalidGeomInfo ) computeGeomInfo();
    return d->radius;
  }

  const Atom * Molecule::farthestAtom() const
  {
    Q_D(const Molecule);
    if( d->invalidGeomInfo ) computeGeomInfo();
    return d->farthestAtom;
  }

  void Molecule::translate(const Eigen::Vector3d& offset)
  {
    if (!m_atomPos)
      return; // nothing to do

    Q_D(const Molecule);
    d->invalidGeomInfo = true;
    foreach (Atom *atom, m_atomList) {
      (*m_atomPos)[atom->id()] += offset;
      emit atomUpdated(atom);
    }
  }

  void Molecule::clear()
  {
    Q_D(Molecule);
    m_atoms.clear();
    foreach (Atom *atom, m_atomList) {
      atom->deleteLater();
      emit primitiveRemoved(atom);
    }
    m_atomList.clear();
    clearConformers();
    delete m_atomPos;
    m_atomPos = 0;
    delete m_dipoleMoment;
    m_dipoleMoment = 0;
    delete d->obunitcell;
    d->obunitcell = 0;

    m_bonds.clear();
    foreach (Bond *bond, m_bondList) {
      bond->deleteLater();
      emit primitiveRemoved(bond);
    }
    m_bondList.clear();

    d->cubes.clear();
    foreach (Cube *cube, d->cubeList) {
      cube->deleteLater();
      emit primitiveRemoved(cube);
    }
    d->cubeList.clear();

    d->meshes.clear();
    foreach (Mesh *mesh, d->meshList) {
      mesh->deleteLater();
      emit primitiveRemoved(mesh);
    }
    d->meshList.clear();

    d->residues.clear();
    foreach (Residue *residue, d->residueList) {
      residue->deleteLater();
      emit primitiveRemoved(residue);
    }
    d->residueList.clear();

    d->rings.clear();
    foreach (Fragment *ring, d->ringList) {
      ring->deleteLater();
      emit primitiveRemoved(ring);
    }
    d->ringList.clear();
  }

  QReadWriteLock * Molecule::lock() const
  {
    return m_lock;
  }

  Molecule &Molecule::operator=(const Molecule& other)
  {
    // FIXME: Copy all the other stuff in the molecule!
    clear();
    //const MoleculePrivate *e = other.d_func();
    m_atoms.resize(other.m_atoms.size(), 0);
    if (other.m_atomPos) {
      m_atomConformers.resize(other.m_atomConformers.size());
      m_currentConformer = other.m_currentConformer;
      for (size_t i = 0; i < m_atomConformers.size(); ++i) {
        m_atomConformers[i] = new vector<Vector3d>;
        *m_atomConformers[i] = *other.m_atomConformers[i];
      }
      m_atomPos = m_atomConformers[m_currentConformer];
    }

    m_bonds.resize(other.m_bonds.size(), 0);

    // Copy the atoms and bonds over
    unsigned int size = other.m_atoms.size();
    for (unsigned int i = 0; i < size; ++i) {
      if (other.m_atoms.at(i) != 0) {
        Atom *atom = new Atom(this);
        atom->setId(other.m_atoms[i]->id());
        atom->setIndex(other.m_atoms[i]->index());
        m_atoms[i] = atom;
        m_atomList.push_back(atom);
        *atom = *(other.m_atoms[i]);
        emit primitiveAdded(atom);
      }
    }

    size = other.m_bonds.size();
    for (unsigned int i = 0; i < size; ++i) {
      if (other.m_bonds.at(i)) {
        Bond *bond = new Bond(this);
        *bond = *(other.m_bonds[i]);
        bond->setId(other.m_bonds[i]->id());
        bond->setIndex(other.m_bonds[i]->index());
        m_bonds[i] = bond;
        m_bondList.push_back(bond);
        // Add the bond to it's atoms
        bond->beginAtom()->addBond(bond);
        bond->endAtom()->addBond(bond);
        emit primitiveAdded(bond);
      }
    }

    foreach (Residue *r, other.residues()) {
      Residue *residue = addResidue();
      residue->setChainNumber(r->chainNumber());
      residue->setChainID(r->chainID());
      residue->setNumber(r->number());
      residue->setName(r->name());

      foreach(unsigned int atomId, r->atoms()) {
        residue->addAtom(atomId);
      }
      residue->setAtomIds(r->atomIds());
    }

    // Copy unit cells
    Q_D(Molecule);
    if (other.OBUnitCell() != NULL) {
      d->obunitcell = new OpenBabel::OBUnitCell;
      *d->obunitcell = *(other.OBUnitCell()); // Copy the object not the pointer
    }

    return *this;
  }

  Molecule &Molecule::operator+=(const Molecule& other)
  {
    // FIXME: Copy all the other stuff in the molecule!
    //const MoleculePrivate *e = other.d_func();
    // Create a temporary map from the old indices to the new for bonding
    QList<int> map;
    foreach (Atom *a, other.m_atomList) {
      Atom *atom = addAtom();
      *atom = *a;
      map.push_back(atom->id());
      emit primitiveAdded(atom);
    }
    foreach (Bond *b, other.m_bondList) {
      Bond *bond = addBond();
      *bond = *b;
      bond->setBegin(atomById(map.at(other.atomById(b->beginAtomId())->index())));
      bond->setEnd(atomById(map.at(other.atomById(b->endAtomId())->index())));
      emit primitiveAdded(bond);
    }
    foreach (Residue *r, other.residues()) {
      Residue *residue = addResidue();
      residue->setChainNumber(r->chainNumber());
      residue->setChainID(r->chainID());
      residue->setNumber(r->number());
      residue->setName(r->name());

      foreach(unsigned int atomId, r->atoms()) {
        residue->addAtom(map.at(atomId));
      }
      residue->setAtomIds(r->atomIds());
    }

    return *this;
  }

  PrimitiveList Molecule::copyAtomsAndBonds(const QList<Atom *> &atoms,
                                            const QList<Bond *> &bonds)
  {
    PrimitiveList newPrimitives;

    // Only need to check atoms: if bonds is not empty and atoms is, then
    // those bonds are invalid anyway
    if (atoms.isEmpty()) {
      return newPrimitives;
    }

    // Build lookup table to map the old atom ids to the new atom ids so that
    // we can get the bonding correct.
    //
    // The atomIdLUT vector is (maxId-minId) indices long, such that:
    //
    //   atomIdLUT.value(oldId - minId) = newId
    //
    // We've checked that atoms is not empty already, so use the first()
    unsigned long minId = atoms.first()->id();
    unsigned long maxId = minId;
    unsigned long maxInd = 0;
    QVector<unsigned long> atomIdLUT;
    atomIdLUT.reserve(atoms.size());
    // Initialize with an invalid value. This will be fixed in the first
    // iteration of the loop below.
    const unsigned long invalidId = std::numeric_limits<unsigned long>::max();
    atomIdLUT.push_back(invalidId);

    // Copy atomic information over, build LUT
    for (QList<Atom*>::const_iterator it = atoms.constBegin(),
         it_end = atoms.constEnd(); it != it_end; ++it) {
      // Add atom
      Atom *newAtom = this->addAtom(**it);
      newPrimitives.append(newAtom);
      const unsigned long oldId = (*it)->id();
      const unsigned long newId = newAtom->id();

      // Resize LUT if needed
      if (oldId < minId) {
        const unsigned long newIds = minId - oldId;
        atomIdLUT.insert(0, newIds, invalidId);
        maxInd += newIds;
        minId = oldId;
      }
      else if (oldId > maxId) {
        const unsigned long newIds = oldId - maxId;
        atomIdLUT.insert(maxInd+1, newIds, invalidId);
        maxInd += newIds;
        maxId = oldId;
      }

      atomIdLUT[oldId - minId] = newId;
    }

    // Copy bond info
    for (QList<Bond*>::const_iterator it = bonds.constBegin(),
         it_end = bonds.constEnd(); it != it_end; ++it) {
      // Fetch old ids
      const unsigned long oldBeginId = (*it)->m_beginAtomId;
      const unsigned long oldEndId   = (*it)->m_endAtomId;

      // Lookup new ids
      const unsigned long newBeginId = atomIdLUT[oldBeginId - minId];
      const unsigned long newEndId   = atomIdLUT[oldEndId - minId];

      // Verify ids
      Q_ASSERT_X(newBeginId != invalidId && newEndId != invalidId, Q_FUNC_INFO,
                 "An invalid ID was returned from the lookup table.");

      // Create new bond. Don't use operator=, it will overwrite ids.
      Bond *newBond = this->addBond(newBeginId,
                                    newEndId,
                                    (*it)->m_order);
      newPrimitives.append(newBond);
    }

    return newPrimitives;
  }

  PrimitiveList Molecule::copyAtomsAndBonds(const PrimitiveList &atomsAndBonds)
  {
    QList<Atom*> atoms;
    QList<Bond*> bonds;
    QList<Primitive*> atomPrims = atomsAndBonds.subList(AtomType);
    QList<Primitive*> bondPrims = atomsAndBonds.subList(BondType);
#if QT_VERSION >= QT_VERSION_CHECK(4,7,0)
    atoms.reserve(atomPrims.size());
    atoms.reserve(atomPrims.size());
#endif

    for (QList<Primitive*>::const_iterator it = atomPrims.constBegin(),
         it_end = atomPrims.constEnd(); it != it_end; ++it) {
      atoms.append(static_cast<Atom*>(*it));
    }

    for (QList<Primitive*>::const_iterator it = bondPrims.constBegin(),
         it_end = bondPrims.constEnd(); it != it_end; ++it) {
      bonds.append(static_cast<Bond*>(*it));
    }

    return this->copyAtomsAndBonds(atoms, bonds);
  }

  void Molecule::computeGeomInfo() const
  {
    Q_D(const Molecule);
    d->invalidGeomInfo = true;
    d->farthestAtom = 0;
    d->center.setZero();
    d->normalVector = Vector3d::UnitZ();
    d->radius = 1.0;

    /// FIXME This leads to the dipole moment always getting invalidated
    /// as the geometry information must be computed on load
    // invalidate the previous dipole moment
    if (m_dipoleMoment) {
      delete m_dipoleMoment; // don't leak -- this is the previous estimate
      m_dipoleMoment = 0;
      m_estimatedDipoleMoment = true;
    }

    unsigned int nAtoms = numAtoms();

    // If a unit cell is present, combine it's center and radius with
    // that of the molecule's atomic center/radius
    if (d->obunitcell) {
      this->computeGeomInfoFromUnitCell();
    }
    // If no unit cell, just compute the radius and farthest atom as normal.
    else {
      if (nAtoms > 1) {
        // compute radius and the farthest atom
        d->radius = std::numeric_limits<double>::min();
        foreach (Atom *atom, m_atomList) {
          double distanceToCenter = (*atom->pos() - d->center).squaredNorm();
          if(distanceToCenter > d->radius) {
            d->radius = distanceToCenter;
            d->farthestAtom = atom;
          }
        }
        d->radius = sqrt(d->radius);

        // Compute the normal vector to the molecule's best-fitting plane
        int i = 0;
        Vector3d ** atomPositions = new Vector3d*[nAtoms];
        // Calculate the center of the molecule too
        foreach (Atom *atom, m_atomList) {
          Vector3d *pos = &(*m_atomPos)[atom->id()];
          d->center += *pos;
          atomPositions[i++] = pos;
        }
        d->center /= static_cast<double>(nAtoms);
        Eigen::Hyperplane<double, 3> planeCoeffs;
        Eigen::fitHyperplane(numAtoms(), atomPositions, &planeCoeffs);
        delete[] atomPositions;
        d->normalVector = planeCoeffs.normal();
      }
    }

    d->invalidGeomInfo = false;
  }

  inline void Molecule::computeGeomInfoFromUnitCell() const
  {
    Q_D(const Molecule);
    const Eigen::Matrix3d ucRowMatrix =
        OB2Eigen(d->obunitcell->GetCellMatrix());
    const Eigen::Vector3d ucCenter = 0.5 * (ucRowMatrix.row(0) +
                                            ucRowMatrix.row(1) +
                                            ucRowMatrix.row(2));
    // Exploit symmetry and only calculate distance to four of the corners
    double ucSqRadii[4];
    ucSqRadii[0] = ((Eigen::Vector3d::Zero()) - ucCenter).squaredNorm();
    ucSqRadii[1] = (ucRowMatrix.row(0).transpose() - ucCenter).squaredNorm();
    ucSqRadii[2] = (ucRowMatrix.row(1).transpose() - ucCenter).squaredNorm();
    ucSqRadii[3] = (ucRowMatrix.row(2).transpose() - ucCenter).squaredNorm();

    // Select the largest radius
    double ucRadius = ucSqRadii[0];
    if (ucRadius > ucSqRadii[1])
      ucRadius = ucSqRadii[1];
    if (ucRadius > ucSqRadii[2])
      ucRadius = ucSqRadii[2];
    if (ucRadius > ucSqRadii[3])
      ucRadius = ucSqRadii[3];
    ucRadius = sqrt(ucRadius);

    // The normal will lie in the direction of the shortest cell vector
    const double aSqNorm = ucRowMatrix.row(0).squaredNorm();
    const double bSqNorm = ucRowMatrix.row(1).squaredNorm();
    const double cSqNorm = ucRowMatrix.row(2).squaredNorm();
    if (aSqNorm < bSqNorm) {
      if (aSqNorm < cSqNorm) // a < (b ? c)
        d->normalVector = -ucRowMatrix.row(0).normalized();
      else                   // c < a < b
        d->normalVector = -ucRowMatrix.row(2).normalized();
    }
    else { // b < a
      if (bSqNorm < cSqNorm) // b < (a ? c)
        d->normalVector = -ucRowMatrix.row(1).normalized();
      else                   // c < b < a
        d->normalVector = -ucRowMatrix.row(2).normalized();
    }

    // If there are no atoms, just use the cell geometry:
    if (this->numAtoms() == 0) {
      d->radius = ucRadius;
      d->center = ucCenter;
    }
    // Otherwise set the center only and calculate the radius and farthest
    // atom
    else {
      // Calculate the center between the molecule and uc centers:
      d->center = ucCenter;

      // Discover the farthestAtom info and radius
      double farthestAtomSqDistance = std::numeric_limits<double>::min();
      d->farthestAtom = NULL;
      foreach (Atom *atom, m_atomList) {
        double distanceToCenter = (*atom->pos() - d->center).squaredNorm();
        if(distanceToCenter > farthestAtomSqDistance) {
          farthestAtomSqDistance = distanceToCenter;
          d->farthestAtom = atom;
        }
      }
      const double moleculeRadius = sqrt(farthestAtomSqDistance);
      d->radius = (moleculeRadius > ucRadius) ? moleculeRadius : ucRadius;
    }
  }

} // End namespace
