#include <iostream>
#include <string>
#include <map>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/Substruct/SubstructMatch.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/FileParsers/MolWriters.h>
#include <GraphMol/MolOps.h>
#include <GraphMol/Resonance.h>

using namespace RDKit;

void addFormalChargeIndices(const ROMol *mol,
                            std::map<unsigned int, int> &fcMap) {
  for (ROMol::ConstAtomIterator it = mol->beginAtoms(); it != mol->endAtoms();
       ++it) {
    int fc = (*it)->getFormalCharge();
    unsigned int idx = (*it)->getIdx();
    if (fc) {
      if (fcMap.find(idx) == fcMap.end())
        fcMap[idx] = fc;
      else
        fcMap[idx] += fc;
    }
  }
}

int getTotalFormalCharge(const ROMol *mol) {
  int totalFormalCharge = 0;
  for (ROMol::ConstAtomIterator it = mol->beginAtoms(); it != mol->endAtoms();
       ++it)
    totalFormalCharge += (*it)->getFormalCharge();
  return totalFormalCharge;
}

void cmpFormalChargeBondOrder(const ROMol *mol1, const ROMol *mol2) {
  TEST_ASSERT(mol1->getNumAtoms() == mol2->getNumAtoms());
  TEST_ASSERT(mol1->getNumBonds() == mol2->getNumBonds());
  for (unsigned int i = 0; i < mol1->getNumAtoms(); ++i)
    TEST_ASSERT(mol1->getAtomWithIdx(i)->getFormalCharge() ==
                mol2->getAtomWithIdx(i)->getFormalCharge());
  for (unsigned int i = 0; i < mol1->getNumBonds(); ++i)
    TEST_ASSERT(mol1->getBondWithIdx(i)->getBondType() ==
                mol2->getBondWithIdx(i)->getBondType());
}

void testBaseFunctionality() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testBaseFunctionality" << std::endl;
  ResonanceMolSupplier *resMolSuppl;
  RWMol *mol;
  mol = SmilesToMol("CC");
  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT((resMolSuppl->getAtomConjGrpIdx(0) == -1) &&
              (resMolSuppl->getAtomConjGrpIdx(1) == -1))
  delete resMolSuppl;
  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->getNumConjGrps() == 0);
  TEST_ASSERT(resMolSuppl->length() == 1);
  TEST_ASSERT(resMolSuppl->getNumConjGrps() == 0);
  delete resMolSuppl;
  delete mol;

  mol = SmilesToMol("NC(=[NH2+])c1ccc(cc1)C(=O)[O-]");
  int totalFormalCharge = getTotalFormalCharge(mol);
  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(!resMolSuppl->getIsEnumerated());
  TEST_ASSERT(resMolSuppl->length() == 4);
  TEST_ASSERT(resMolSuppl->getIsEnumerated());
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(!resMolSuppl->getIsEnumerated());
  resMolSuppl->enumerate();
  TEST_ASSERT(resMolSuppl->getIsEnumerated());
  TEST_ASSERT(((*resMolSuppl)[0]->getBondBetweenAtoms(0, 1)->getBondType() !=
               (*resMolSuppl)[1]->getBondBetweenAtoms(0, 1)->getBondType()) ||
              ((*resMolSuppl)[0]->getBondBetweenAtoms(9, 10)->getBondType() !=
               (*resMolSuppl)[1]->getBondBetweenAtoms(9, 10)->getBondType()));
  delete resMolSuppl;

  resMolSuppl =
      new ResonanceMolSupplier((ROMol &)*mol, ResonanceMolSupplier::KEKULE_ALL);
  TEST_ASSERT(resMolSuppl->length() == 8);
  std::map<Bond::BondType, bool> bondTypeMap;
  // check that we actually have two alternate Kekule structures
  bondTypeMap[(*resMolSuppl)[0]->getBondBetweenAtoms(3, 4)->getBondType()] =
      true;
  bondTypeMap[(*resMolSuppl)[1]->getBondBetweenAtoms(3, 4)->getBondType()] =
      true;
  TEST_ASSERT(bondTypeMap.size() == 2);
  bondTypeMap.clear();
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_INCOMPLETE_OCTETS |
                         ResonanceMolSupplier::UNCONSTRAINED_CATIONS |
                         ResonanceMolSupplier::UNCONSTRAINED_ANIONS);
  TEST_ASSERT(resMolSuppl->length() == 32);
  for (unsigned int i = 0; i < resMolSuppl->length(); ++i) {
    ROMol *resMol = (*resMolSuppl)[i];
    TEST_ASSERT(getTotalFormalCharge(resMol) == totalFormalCharge);
    delete resMol;
  }
  while (!resMolSuppl->atEnd()) {
    ROMol *resMol = resMolSuppl->next();
    TEST_ASSERT(getTotalFormalCharge(resMol) == totalFormalCharge);
    delete resMol;
  }
  resMolSuppl->reset();
  cmpFormalChargeBondOrder((*resMolSuppl)[0], resMolSuppl->next());
  resMolSuppl->moveTo(12);
  cmpFormalChargeBondOrder((*resMolSuppl)[12], resMolSuppl->next());
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_INCOMPLETE_OCTETS |
                         ResonanceMolSupplier::UNCONSTRAINED_CATIONS |
                         ResonanceMolSupplier::UNCONSTRAINED_ANIONS,
      10);
  TEST_ASSERT(resMolSuppl->length() == 10);
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_INCOMPLETE_OCTETS |
                         ResonanceMolSupplier::UNCONSTRAINED_CATIONS |
                         ResonanceMolSupplier::UNCONSTRAINED_ANIONS,
      0);
  TEST_ASSERT(resMolSuppl->length() == 0);
  delete resMolSuppl;
}

void testBenzylCation() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testBenzylCation" << std::endl;
  RWMol *mol = SmilesToMol("[CH2+]c1ccccc1");
  ResonanceMolSupplier *resMolSuppl;
  std::map<unsigned int, int> fcMap;
  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 4);
  while (!resMolSuppl->atEnd()) {
    ROMol *resMol = resMolSuppl->next();
    addFormalChargeIndices(resMol, fcMap);
    delete resMol;
  }
  unsigned int indices[] = {0, 2, 4, 6};
  for (unsigned int i = 0; i < sizeof(indices) / sizeof(unsigned int); ++i) {
    TEST_ASSERT(fcMap.find(indices[i]) != fcMap.end());
    TEST_ASSERT(fcMap[indices[i]] == 1);
  }
  delete resMolSuppl;
}

void testBenzylAnion() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testBenzylAnion" << std::endl;
  RWMol *mol = SmilesToMol("[CH2-]c1ccccc1");
  ResonanceMolSupplier *resMolSuppl;
  std::map<unsigned int, int> fcMap;
  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 4);
  while (!resMolSuppl->atEnd()) {
    ROMol *resMol = resMolSuppl->next();
    addFormalChargeIndices(resMol, fcMap);
    delete resMol;
  }
  unsigned int indices[] = {0, 2, 4, 6};
  for (unsigned int i = 0; i < sizeof(indices) / sizeof(unsigned int); ++i) {
    TEST_ASSERT(fcMap.find(indices[i]) != fcMap.end());
    TEST_ASSERT(fcMap[indices[i]] == -1);
  }
  delete resMolSuppl;
}

void testButadiene() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testButadiene" << std::endl;
  RWMol *mol = SmilesToMol("C=CC=C");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 1);
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_INCOMPLETE_OCTETS |
                         ResonanceMolSupplier::UNCONSTRAINED_CATIONS |
                         ResonanceMolSupplier::UNCONSTRAINED_ANIONS |
                         ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION);
  TEST_ASSERT(resMolSuppl->length() == 3);
  std::map<unsigned int, int> fcMap;
  while (!resMolSuppl->atEnd()) {
    ROMol *resMol = resMolSuppl->next();
    addFormalChargeIndices(resMol, fcMap);
    delete resMol;
  }
  unsigned int indices[] = {0, 3};
  for (unsigned int i = 0; i < sizeof(indices) / sizeof(unsigned int); ++i) {
    TEST_ASSERT(fcMap.find(indices[i]) != fcMap.end());
    TEST_ASSERT(fcMap[indices[i]] == 0);
  }
  delete resMolSuppl;
}

void testZwitterion() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testZwitterion" << std::endl;
  RWMol *mol = SmilesToMol("NC=CC=CC=O");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 1);
  ROMol *mol0 = (*resMolSuppl)[0];
  TEST_ASSERT(mol0->getAtomWithIdx(0)->getFormalCharge() == 0);
  TEST_ASSERT(mol0->getAtomWithIdx(6)->getFormalCharge() == 0);
  delete mol0;
  delete resMolSuppl;
  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION);
  TEST_ASSERT(resMolSuppl->length() == 2);
  mol0 = (*resMolSuppl)[0];
  ROMol *mol1 = (*resMolSuppl)[1];
  TEST_ASSERT(mol1->getAtomWithIdx(0)->getFormalCharge() == 1);
  TEST_ASSERT(mol1->getAtomWithIdx(6)->getFormalCharge() == -1);
  delete mol1;
  delete resMolSuppl;
}

void testChargeMigration() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testChargeMigration" << std::endl;
  RWMol *mol = SmilesToMol("C=CC=CC=C[CH2+]");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 4);
  std::map<unsigned int, int> fcMap;
  while (!resMolSuppl->atEnd()) {
    ROMol *resMol = resMolSuppl->next();
    addFormalChargeIndices(resMol, fcMap);
    delete resMol;
  }
  unsigned int indices[] = {0, 2, 4, 6};
  for (unsigned int i = 0; i < sizeof(indices) / sizeof(unsigned int); ++i) {
    TEST_ASSERT(fcMap.find(indices[i]) != fcMap.end());
    TEST_ASSERT(fcMap[indices[i]] == 1);
  }
  delete resMolSuppl;
}

void testChargeSeparation1() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testChargeSeparation1" << std::endl;
  RWMol *mol = SmilesToMol("[NH2+]=C1C=CC(C=C1)=CN=CN");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 3);
  std::map<unsigned int, int> fcMap;
  for (unsigned int i = 0; i < 3; ++i) {
    ROMol *resMol = (*resMolSuppl)[i];
    addFormalChargeIndices(resMol, fcMap);
    delete resMol;
  }
  unsigned int indices[] = {0, 8, 10};
  for (unsigned int i = 0; i < sizeof(indices) / sizeof(unsigned int); ++i) {
    TEST_ASSERT(fcMap.find(indices[i]) != fcMap.end());
    TEST_ASSERT(fcMap[indices[i]] == 1);
  }
  delete resMolSuppl;
  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION);
  TEST_ASSERT(resMolSuppl->length() == 4);
  resMolSuppl->moveTo(3);
  ROMol *resMol = resMolSuppl->next();
  TEST_ASSERT(resMol->getAtomWithIdx(0)->getFormalCharge() == 1);
  TEST_ASSERT(resMol->getAtomWithIdx(10)->getFormalCharge() == 1);
  TEST_ASSERT(resMol->getAtomWithIdx(8)->getFormalCharge() == -1);
  delete resMol;
  delete resMolSuppl;
}

void testChargeSeparation2() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testChargeSeparation2" << std::endl;
  RWMol *mol = SmilesToMol("NC(C(=O)[O-])=CC=CC=O");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 2);
  delete resMolSuppl;
  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION);
  TEST_ASSERT(resMolSuppl->length() == 8);
  // less charge separation in the first 2,
  // more charge separation in the last 6
  // the last 4 feature carbanions
  std::map<unsigned int, int> fcMap;
  for (unsigned int i = 0; i < 2; ++i) {
    ROMol *resMol = (*resMolSuppl)[i];
    addFormalChargeIndices(resMol, fcMap);
    TEST_ASSERT(fcMap.size() == 1);
    fcMap.clear();
    delete resMol;
  }
  for (unsigned int i = 2; i < 8; ++i) {
    ROMol *resMol = (*resMolSuppl)[i];
    addFormalChargeIndices(resMol, fcMap);
    TEST_ASSERT(fcMap.size() == 3);
    fcMap.clear();
    delete resMol;
  }
  for (unsigned int i = 0; i < 4; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCarbanion = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 6) && ((*it)->getFormalCharge() == -1))
        haveCarbanion = true;
    }
    TEST_ASSERT(!haveCarbanion);
    delete resMol;
  }
  for (unsigned int i = 4; i < 8; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCarbanion = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 6) && ((*it)->getFormalCharge() == -1))
        haveCarbanion = true;
    }
    TEST_ASSERT(haveCarbanion);
    delete resMol;
  }
  delete resMolSuppl;
}

void testMultipleConjGroups1() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testMultipleConjGroups1" << std::endl;
  RWMol *mol = SmilesToMol("NC(C(=O)[O-])=CC=C(CCC(=O)[O-])C=O");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION);
  TEST_ASSERT(resMolSuppl->length() == 16);
  for (unsigned int i = 0; i < 8; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCarbanion = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 6) && ((*it)->getFormalCharge() == -1))
        haveCarbanion = true;
    }
    TEST_ASSERT(!haveCarbanion);
    delete resMol;
  }
  for (unsigned int i = 8; i < 16; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCarbanion = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 6) && ((*it)->getFormalCharge() == -1))
        haveCarbanion = true;
    }
    TEST_ASSERT(haveCarbanion);
    delete resMol;
  }
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION, 8);
  TEST_ASSERT(resMolSuppl->length() == 8);
  for (unsigned int i = 0; i < 8; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCarbanion = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 6) && ((*it)->getFormalCharge() == -1))
        haveCarbanion = true;
    }
    TEST_ASSERT(!haveCarbanion);
    delete resMol;
  }
  delete resMolSuppl;
}

void testMultipleConjGroups2() {
  // if breadth-first resonance structure enumeration works properly
  // all N(2-) should be at the end of the supplier
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testMultipleConjGroups2" << std::endl;
  RWMol *mol = SmilesToMol("[N-]=[N+]=NCN=[N+]=[N-]");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 9);
  bool haveFirstDoubleNeg = false;
  for (unsigned int i = 0; i < resMolSuppl->length(); ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveDoubleNeg = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 7) && ((*it)->getFormalCharge() == -2))
        haveDoubleNeg = true;
    }
    if (!haveFirstDoubleNeg && haveDoubleNeg) haveFirstDoubleNeg = true;
    TEST_ASSERT(!haveFirstDoubleNeg || (haveFirstDoubleNeg && haveDoubleNeg));
    delete resMol;
  }
  delete resMolSuppl;
}

void testDimethylMalonate() {
  // carbanion should be last
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testDimethylMalonate" << std::endl;
  RWMol *mol = SmilesToMol("COC(=O)[CH-]C(=O)OC");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 3);
  for (unsigned int i = 0; i < 3; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCarbanion = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 6) && ((*it)->getFormalCharge() == -1))
        haveCarbanion = true;
    }
    TEST_ASSERT(((i < 2) && !haveCarbanion) || ((i == 2) && haveCarbanion));
    delete resMol;
  }
  delete resMolSuppl;
}

void testMethylAcetate() {
  // cation on oxygen should appear only when UNCONSTRAINED_CATIONS
  // and ALLOW_CHARGE_SEPARATION are set
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testMethylAcetate" << std::endl;
  RWMol *mol = SmilesToMol("CC(=O)OC");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 1);
  const ROMol *resMol = (*resMolSuppl)[0];
  bool haveCation = false;
  for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
       it != resMol->endAtoms(); ++it) {
    if (((*it)->getAtomicNum() == 8) && ((*it)->getFormalCharge() == 1))
      haveCation = true;
  }
  delete resMol;
  TEST_ASSERT(!haveCation);
  delete resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier(
      (ROMol &)*mol, ResonanceMolSupplier::ALLOW_CHARGE_SEPARATION |
                         ResonanceMolSupplier::UNCONSTRAINED_CATIONS);
  TEST_ASSERT(resMolSuppl->length() == 2);
  for (unsigned int i = 0; i < 2; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    bool haveCation = false;
    for (ROMol::ConstAtomIterator it = resMol->beginAtoms();
         it != resMol->endAtoms(); ++it) {
      if (((*it)->getAtomicNum() == 8) && ((*it)->getFormalCharge() == 1))
        haveCation = true;
    }
    TEST_ASSERT(((i == 0) && !haveCation) || ((i == 1) && haveCation));
    delete resMol;
  }
  delete resMolSuppl;
}

void testPyranium2_6dicarboxylate() {
  // when there is no alternative cation on elements right of N
  // should be accepted even though the total formal charge is not
  // positive
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testPyranium2_6dicarboxylate" << std::endl;
  RWMol *mol = SmilesToMol("[o+]1c(C(=O)[O-])cccc1C(=O)[O-]");
  ResonanceMolSupplier *resMolSuppl;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  TEST_ASSERT(resMolSuppl->length() == 4);
  for (unsigned int i = 0; i < 4; ++i) {
    const ROMol *resMol = (*resMolSuppl)[i];
    TEST_ASSERT(resMol->getAtomWithIdx(0)->getFormalCharge() == 1);
    delete resMol;
  }
  delete resMolSuppl;
}

void testSubstructMatchAcetate() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testSubstructMatchAcetate" << std::endl;
  RWMol *mol = SmilesToMol("CC(=O)[O-]");
  RWMol *query = SmartsToMol("C(=O)[O-]");
  ResonanceMolSupplier *resMolSuppl;
  unsigned int n;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  std::vector<MatchVectType> matchVect;
  n = SubstructMatch(*mol, *query, matchVect, false, true, false, false);
  TEST_ASSERT(n == 1);
  matchVect.clear();
  n = SubstructMatch(*resMolSuppl, *query, matchVect, false, true, false,
                     false);
  TEST_ASSERT(n == 2);
  delete mol;
  delete query;
  delete resMolSuppl;
}

void testSubstructMatchDMAP() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testSubstructMatchDMAP" << std::endl;
  RWMol *mol = SmilesToMol("C(C)Nc1cc[nH+]cc1");
  RWMol *query = SmartsToMol("[#7+]");
  ResonanceMolSupplier *resMolSuppl;
  unsigned int n;
  MatchVectType p;

  resMolSuppl = new ResonanceMolSupplier((ROMol &)*mol);
  std::vector<MatchVectType> matchVect;
  n = SubstructMatch(*mol, *query, matchVect, false, true, false, false);
  TEST_ASSERT((n == 1) && (matchVect.size() == 1));
  p = matchVect[0];
  TEST_ASSERT((p.size() == 1) && (p[0].second == 6));
  matchVect.clear();
  n = SubstructMatch(*resMolSuppl, *query, matchVect, false, true, false,
                     false);
  TEST_ASSERT((n == 2) && (matchVect.size() == 2));
  std::vector<unsigned int> v;
  p = matchVect[0];
  TEST_ASSERT(p.size() == 1);
  v.push_back(p[0].second);
  p = matchVect[1];
  TEST_ASSERT(p.size() == 1);
  v.push_back(p[0].second);
  std::sort(v.begin(), v.end());
  TEST_ASSERT(v[0] == 2);
  TEST_ASSERT(v[1] == 6);
  delete mol;
  delete query;
  delete resMolSuppl;
}

void setResidueFormalCharge(RWMol *mol, std::vector<RWMol *> &res, int fc) {
  for (std::vector<RWMol *>::const_iterator it = res.begin(); it != res.end();
       ++it) {
    std::vector<MatchVectType> matchVect;
    SubstructMatch(*mol, *(*it), matchVect);
    for (std::vector<MatchVectType>::const_iterator it = matchVect.begin();
         it != matchVect.end(); ++it)
      mol->getAtomWithIdx((*it).back().second)->setFormalCharge(fc);
  }
}

void getBtVectVect(ResonanceMolSupplier *resMolSuppl,
                   std::vector<std::vector<unsigned int> > &btVect2) {
  while (!resMolSuppl->atEnd()) {
    ROMol *resMol = resMolSuppl->next();
    std::vector<unsigned int> bt;
    bt.reserve(resMol->getNumBonds());
    for (ROMol::BondIterator bi = resMol->beginBonds();
         bi != resMol->endBonds(); ++bi)
      bt.push_back(static_cast<unsigned int>((*bi)->getBondTypeAsDouble()));
    btVect2.push_back(bt);
    delete resMol;
  }
  for (unsigned int i = 0; i < btVect2.size(); ++i) {
    bool same = true;
    for (unsigned int j = 0; same && (j < btVect2[i].size()); ++j) {
      if (!i) continue;
      if (same) same = (btVect2[i][j] == btVect2[i - 1][j]);
    }
    if (i) TEST_ASSERT(!same);
  }
}

void testCrambin() {
  BOOST_LOG(rdInfoLog) << "-----------------------\n"
                       << "testCrambin" << std::endl;
  std::string pathName = getenv("RDBASE");
  pathName += "/Code/GraphMol/FileParsers/test_data/1CRN.pdb";
  RWMol *crambin = PDBFileToMol(pathName);
  TEST_ASSERT(crambin);
  RWMol *query;
  unsigned int n;
  std::vector<RWMol *> res;
  // protonate NH2
  query = SmartsToMol("[Nh2][Ch;Ch2]");
  TEST_ASSERT(query);
  res.push_back(query);
  // protonate Arg
  query = SmartsToMol("[Nh][C]([Nh2])=[Nh]");
  TEST_ASSERT(query);
  res.push_back(query);
  setResidueFormalCharge(crambin, res, 1);
  for (std::vector<RWMol *>::const_iterator it = res.begin(); it != res.end();
       ++it)
    delete *it;
  res.clear();
  // deprotonate COOH
  query = SmartsToMol("C(=O)[Oh]");
  TEST_ASSERT(query);
  res.push_back(query);
  setResidueFormalCharge(crambin, res, -1);
  for (std::vector<RWMol *>::const_iterator it = res.begin(); it != res.end();
       ++it)
    delete *it;
  ResonanceMolSupplier *resMolSupplST =
      new ResonanceMolSupplier((ROMol &)*crambin);
  TEST_ASSERT(resMolSupplST);
  // crambin has 2 Arg (3 resonance structures each); 1 Asp, 1 Glu
  // and 1 terminal COO- (2 resonance structures each)
  // so possible resonance structures are 3^2 * 2^3 = 72
  TEST_ASSERT(resMolSupplST->length() == 72);
  TEST_ASSERT(resMolSupplST->getNumConjGrps() == 56);
  RWMol *carboxylateQuery = SmartsToMol("C(=O)[O-]");
  RWMol *guanidiniumQuery = SmartsToMol("NC(=[NH2+])N");
  std::vector<MatchVectType> matchVect;
  n = SubstructMatch(*crambin, *carboxylateQuery, matchVect, false, true, false,
                     false, 1000);
  TEST_ASSERT(n == 3);
  n = SubstructMatch(*crambin, *carboxylateQuery, matchVect, true, true, false,
                     false, 1000);
  TEST_ASSERT(n == 3);
  n = SubstructMatch(*crambin, *guanidiniumQuery, matchVect, false, true, false,
                     false, 1000);
  TEST_ASSERT(n == 0);
  n = SubstructMatch(*crambin, *guanidiniumQuery, matchVect, true, true, false,
                     false, 1000);
  TEST_ASSERT(n == 0);
  n = SubstructMatch(*resMolSupplST, *carboxylateQuery, matchVect, false, true,
                     false, false, 1000);
  TEST_ASSERT(n == 6);
  n = SubstructMatch(*resMolSupplST, *carboxylateQuery, matchVect, true, true,
                     false, false, 1000);
  TEST_ASSERT(n == 3);
  n = SubstructMatch(*resMolSupplST, *guanidiniumQuery, matchVect, false, true,
                     false, false, 1000);
  TEST_ASSERT(n == 8);
  n = SubstructMatch(*resMolSupplST, *guanidiniumQuery, matchVect, true, true,
                     false, false, 1000);
  TEST_ASSERT(n == 2);
#ifdef RDK_TEST_MULTITHREADED
  std::vector<std::vector<unsigned int> > btVect2ST;
  getBtVectVect(resMolSupplST, btVect2ST);
  ResonanceMolSupplier *resMolSupplMT =
      new ResonanceMolSupplier((ROMol &)*crambin, 0, 1000);
  TEST_ASSERT(resMolSupplMT);
  resMolSupplMT->setNumThreads(0);
  TEST_ASSERT(resMolSupplST->length() == resMolSupplMT->length());
  std::vector<std::vector<unsigned int> > btVect2MT;
  getBtVectVect(resMolSupplMT, btVect2MT);
  TEST_ASSERT(btVect2ST.size() == btVect2MT.size());
  for (unsigned int i = 0; i < btVect2ST.size(); ++i) {
    for (unsigned int j = 0; j < btVect2ST[i].size(); ++j)
      TEST_ASSERT(btVect2ST[i][j] == btVect2MT[i][j]);
  }
  ResonanceMolSupplier *ptr[2] = {resMolSupplST, resMolSupplMT};
  for (unsigned int i = 0; i < sizeof(ptr) / sizeof(ResonanceMolSupplier *);
       ++i) {
    n = SubstructMatch(*(ptr[i]), *carboxylateQuery, matchVect, false, true,
                       false, false, 1000, 0);
    TEST_ASSERT(n == 6);
    n = SubstructMatch(*(ptr[i]), *carboxylateQuery, matchVect, true, true,
                       false, false, 1000, 0);
    TEST_ASSERT(n == 3);
    n = SubstructMatch(*(ptr[i]), *guanidiniumQuery, matchVect, false, true,
                       false, false, 1000, 0);
    TEST_ASSERT(n == 8);
    n = SubstructMatch(*(ptr[i]), *guanidiniumQuery, matchVect, true, true,
                       false, false, 1000, 0);
    TEST_ASSERT(n == 2);
  }
  delete resMolSupplMT;
#endif
  delete resMolSupplST;
  delete carboxylateQuery;
  delete guanidiniumQuery;
  delete crambin;
}

int main() {
  RDLog::InitLogs();
#if 1
  testBaseFunctionality();
  testBenzylCation();
  testBenzylAnion();
  testButadiene();
  testZwitterion();
  testChargeMigration();
  testChargeSeparation1();
  testChargeSeparation2();
  testMultipleConjGroups1();
  testMultipleConjGroups2();
  testDimethylMalonate();
  testMethylAcetate();
  testPyranium2_6dicarboxylate();
  testSubstructMatchAcetate();
  testSubstructMatchDMAP();
  testCrambin();
#endif
  return 0;
}
