// $Id$
//
//  Copyright (C) 2004-2006 Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include <RDGeneral/test.h>
#include <fstream>
#include <iostream>
#include <RDGeneral/Invariant.h>
#include <RDGeneral/RDLog.h>
#include <RDGeneral/utils.h>
#include <RDGeneral/StreamOps.h>

#include <sstream>
#include <boost/shared_ptr.hpp>

#include <GraphMol/RDKitBase.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/Substruct/SubstructMatch.h>

#include <GraphMol/MolChemicalFeatures/MolChemicalFeature.h>
#include <GraphMol/MolChemicalFeatures/MolChemicalFeatureDef.h>
#include <GraphMol/MolChemicalFeatures/MolChemicalFeatureFactory.h>
#include <GraphMol/MolChemicalFeatures/FeatureParser.h>

using namespace RDKit;

void test1() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "AtomType parser testing." << std::endl;

  std::string inLine;
  std::map<std::string, std::string> atomTypeDefs;
  atomTypeDefs.clear();
  bool ok;

  inLine = "AtomType donor [N,O]";
  Local::parseAtomType(inLine, atomTypeDefs, 0);
  TEST_ASSERT(atomTypeDefs.count("{donor}"));
  TEST_ASSERT(!atomTypeDefs.count("{unsaturatedDonor}"));
  TEST_ASSERT(atomTypeDefs["{donor}"] == "$([N,O])");

  // check robustness to whitespace as we expand:
  inLine = " AtomType donor\t[S]";
  Local::parseAtomType(inLine, atomTypeDefs, 0);
  TEST_ASSERT(atomTypeDefs.count("{donor}"));
  TEST_ASSERT(!atomTypeDefs.count("{unsaturatedDonor}"));
  TEST_ASSERT(atomTypeDefs["{donor}"] == "$([N,O,$([S])])");

  inLine = "AtomType !donor [NH0]";
  Local::parseAtomType(inLine, atomTypeDefs, 0);
  TEST_ASSERT(atomTypeDefs.count("{donor}"));
  TEST_ASSERT(!atomTypeDefs.count("{unsaturatedDonor}"));
  TEST_ASSERT(atomTypeDefs["{donor}"] == "$([!$([NH0]);N,O,$([S])])");

  inLine = "AtomType unsaturatedDonor [$([{donor}]!-[*])]";
  Local::parseAtomType(inLine, atomTypeDefs, 0);
  TEST_ASSERT(atomTypeDefs.count("{donor}"));
  TEST_ASSERT(atomTypeDefs.count("{unsaturatedDonor}"));
  TEST_ASSERT(atomTypeDefs["{donor}"] == "$([!$([NH0]);N,O,$([S])])");
  TEST_ASSERT(atomTypeDefs["{unsaturatedDonor}"] ==
              "$([$([$([!$([NH0]);N,O,$([S])])]!-[*])])");

  ok = false;
  try {
    inLine = "";
    Local::parseAtomType(inLine, atomTypeDefs, 0);
  } catch (FeatureFileParseException &fpe) {
    ok = true;
    TEST_ASSERT(fpe.lineNo() == 0);
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  ok = false;
  try {
    inLine = "AtomTyped donor [N,O]";
    Local::parseAtomType(inLine, atomTypeDefs, 0);
  } catch (FeatureFileParseException &fpe) {
    ok = true;
    TEST_ASSERT(fpe.lineNo() == 0);
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  ok = false;
  try {
    inLine = "AtomType";
    Local::parseAtomType(inLine, atomTypeDefs, 0);
  } catch (FeatureFileParseException &fpe) {
    ok = true;
    TEST_ASSERT(fpe.lineNo() == 0);
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  ok = false;
  try {
    inLine = "AtomType donor";
    Local::parseAtomType(inLine, atomTypeDefs, 0);
  } catch (FeatureFileParseException &fpe) {
    ok = true;
    TEST_ASSERT(fpe.lineNo() == 0);
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  ok = false;
  try {
    inLine = "AtomType donor [N,O";
    Local::parseAtomType(inLine, atomTypeDefs, 0);
  } catch (FeatureFileParseException &fpe) {
    ok = true;
    TEST_ASSERT(fpe.lineNo() == 0);
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void test2() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "FeatureDefinition parser testing." << std::endl;

  std::string inText, inLine;
  std::istringstream ss;
  MolChemicalFeatureDef *featDef;
  bool ok;

  std::map<std::string, std::string> atomTypeDefs;
  atomTypeDefs["{donor}"] = "$([O,N])";

  inText =
      "DefineFeature HDonor1 [O,N]\n"
      "  Family HDONOR\n"
      "  Weights 1.0\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  unsigned int tmpLine = 2;
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonor1");
  TEST_ASSERT(featDef->getSmarts() == "[O,N]");
  TEST_ASSERT(featDef->getNumWeights() == 1);
  TEST_ASSERT(feq(*featDef->beginWeights(), 1.0));
  delete featDef;

  inText =
      "DefineFeature HDonor1 [{donor}]\n"
      "  Family HDONOR\n"
      "  Weights 2.0\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonor1");
  TEST_ASSERT(featDef->getSmarts() == "[$([O,N])]");
  TEST_ASSERT(featDef->getNumWeights() == 1);
  TEST_ASSERT(feq(*featDef->beginWeights(), 1.0));
  delete featDef;

  inText =
      "DefineFeature HDonorPair [{donor}][{donor}]\n"
      "  Family HDONOR\n"
      "  Weights 1.0,1\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonorPair");
  TEST_ASSERT(featDef->getSmarts() == "[$([O,N])][$([O,N])]");
  TEST_ASSERT(featDef->getNumWeights() == 2);
  TEST_ASSERT(feq(*featDef->beginWeights(), 0.5));
  TEST_ASSERT(feq(*++(featDef->beginWeights()), 0.5));
  delete featDef;

  ok = false;
  inText =
      "DefineFeature HDonor1 [{donor}]\n"
      "  Family HDONOR\n"
      "  Weights 1.0\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  try {
    featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  } catch (FeatureFileParseException &) {
    ok = true;
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  ok = false;
  inText =
      "DefineFeature HDonor1 [{donor}]\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  try {
    featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  } catch (FeatureFileParseException &) {
    ok = true;
  }
  CHECK_INVARIANT(ok, "expected parse failure did not happen");

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void test3() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Test pulling feat defs from a string." << std::endl;

  // std::string pathName=getenv("RDBASE");
  // pathName +=
  // "/Code/GraphMol/MolChemicalFeatures/test_data/smallsample.feats";
  std::string inText, inLine;
  std::istringstream ss;
  int res;
  MolChemicalFeatureDef::CollectionType featureDefs;

  inText =
      "AtomType donor [N,O]\n"
      "AtomType !donor [H0]\n"
      "DefineFeature HDonor1 [{donor}]\n"
      "  Family HBondDonor\n"
      "  Weights 1.0\n"
      "EndFeature\n";
  res = parseFeatureData(inText, featureDefs);
  TEST_ASSERT(!res);
  TEST_ASSERT(featureDefs.size() == 1);
  TEST_ASSERT((*featureDefs.begin())->getSmarts() == "[$([!$([H0]);N,O])]");

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void test4() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "FeatureDef functionality testing." << std::endl;

  ROMol *testMol;
  MatchVectType mv;
  std::string inText;
  int res;
  MolChemicalFeatureDef::CollectionType featureDefs;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "DefineFeature HDonor1 [N,O;!H0]\n"
      "  Family HBondDonor\n"
      "  Weights 1.0\n"
      "EndFeature\n"
      "DefineFeature HAcceptor1 [N,O]\n"
      "  Family HAcceptor\n"
      "  Weights 1.0\n"
      "EndFeature\n";
  res = parseFeatureData(inText, featureDefs);
  TEST_ASSERT(!res);
  TEST_ASSERT(featureDefs.size() == 2);
  featDefIt = featureDefs.begin();
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getSmarts() == "[N,O;!H0]");
  TEST_ASSERT(featDef->getPattern());
  TEST_ASSERT(featDef->getPattern()->getNumAtoms() == 1);
  TEST_ASSERT(featDef->getNumWeights() == 1);

  featDef = *(++featureDefs.begin());
  TEST_ASSERT(featDef->getSmarts() == "[N,O]");
  TEST_ASSERT(featDef->getPattern());
  TEST_ASSERT(featDef->getPattern()->getNumAtoms() == 1);
  TEST_ASSERT(featDef->getNumWeights() == 1);

  testMol = SmilesToMol("COCN");
  TEST_ASSERT(testMol);
  featDef = *featureDefs.begin();
  TEST_ASSERT(SubstructMatch(*testMol, *featDef->getPattern(), mv));
  TEST_ASSERT(mv.size() == 1);
  TEST_ASSERT(mv[0].first == 0);
  TEST_ASSERT(mv[0].second == 3);
  featDef = *(++featureDefs.begin());
  TEST_ASSERT(SubstructMatch(*testMol, *featDef->getPattern(), mv));
  TEST_ASSERT(mv.size() == 1);
  TEST_ASSERT(mv[0].first == 0);
  TEST_ASSERT(mv[0].second == 1 || mv[0].second == 3);
  delete testMol;

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void test5() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "FeatureFactory testing." << std::endl;

  ROMol *testMol;
  MatchVectType mv;
  std::string inText;
  FeatSPtrList featSPtrs;
  boost::shared_ptr<MolChemicalFeature> featSPtr;

  MolChemicalFeatureFactory *factory;

  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "DefineFeature HDonor1 [N,O;!H0]\n"
      "  Family HBondDonor\n"
      "  Weights 1.0\n"
      "EndFeature\n"
      "DefineFeature HAcceptor1 [N,O]\n"
      "  Family HBondAcceptor\n"
      "  Weights 1.0\n"
      "EndFeature\n";

  factory = buildFeatureFactory(inText);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);

  testMol = SmilesToMol("COCN");
  TEST_ASSERT(testMol);
  featDef = *factory->beginFeatureDefs();
  TEST_ASSERT(SubstructMatch(*testMol, *featDef->getPattern(), mv));
  BOOST_LOG(rdErrorLog) << "1" << std::endl;
  TEST_ASSERT(mv.size() == 1);
  TEST_ASSERT(mv[0].first == 0);
  TEST_ASSERT(mv[0].second == 3);
  featDef = *(++factory->beginFeatureDefs());
  TEST_ASSERT(SubstructMatch(*testMol, *featDef->getPattern(), mv));
  BOOST_LOG(rdErrorLog) << "2" << std::endl;
  TEST_ASSERT(mv.size() == 1);
  TEST_ASSERT(mv[0].first == 0);
  TEST_ASSERT(mv[0].second == 1 || mv[0].second == 3);

  // Test using the factory to find features:
  featSPtrs = factory->getFeaturesForMol(*testMol);
  BOOST_LOG(rdErrorLog) << "3" << std::endl;
  TEST_ASSERT(featSPtrs.size() == 3);
  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "HBondDonor");
  TEST_ASSERT(featSPtr->getType() == "HDonor1");
  featSPtr = *(++featSPtrs.begin());
  TEST_ASSERT(featSPtr->getFamily() == "HBondAcceptor");
  TEST_ASSERT(featSPtr->getType() == "HAcceptor1");
  featSPtr = *(++featSPtrs.begin());
  TEST_ASSERT(featSPtr->getFamily() == "HBondAcceptor");
  TEST_ASSERT(featSPtr->getType() == "HAcceptor1");

  // Test limiting stuff with includeOnly
  featSPtrs = factory->getFeaturesForMol(*testMol, "HBondAcceptor");
  BOOST_LOG(rdErrorLog) << "4" << std::endl;
  TEST_ASSERT(featSPtrs.size() == 2);
  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "HBondAcceptor");
  TEST_ASSERT(featSPtr->getType() == "HAcceptor1");
  featSPtr = *(++featSPtrs.begin());
  TEST_ASSERT(featSPtr->getFamily() == "HBondAcceptor");
  TEST_ASSERT(featSPtr->getType() == "HAcceptor1");

  featSPtrs = factory->getFeaturesForMol(*testMol, "HBondDonor");
  BOOST_LOG(rdErrorLog) << "5" << std::endl;
  TEST_ASSERT(featSPtrs.size() == 1);
  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "HBondDonor");
  TEST_ASSERT(featSPtr->getType() == "HDonor1");

  featSPtrs = factory->getFeaturesForMol(*testMol, "NotPresent");
  BOOST_LOG(rdErrorLog) << "6" << std::endl;
  TEST_ASSERT(featSPtrs.size() == 0);

  delete testMol;
  delete factory;

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void test6() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Feature Location testing." << std::endl;

  ROMol *testMol;
  Conformer *conf;
  std::string inText;
  FeatSPtrList featSPtrs;
  boost::shared_ptr<MolChemicalFeature> featSPtr;

  MolChemicalFeatureFactory *factory;

  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "DefineFeature HDonor1 [N,O;!H0]\n"
      "  Family HBondDonor\n"
      "  Weights 1.0\n"
      "EndFeature\n"
      "DefineFeature Carboxyl1 C(=O)[O;H1,-]\n"
      "  Family ZnBinder\n"
      "  Weights 1.0,1.0,1.0\n"
      "EndFeature\n";

  factory = buildFeatureFactory(inText);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);

  testMol = SmilesToMol("C(=O)O");
  TEST_ASSERT(testMol);
  conf = new Conformer(3);
  testMol->addConformer(conf);
  conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0.0));
  conf->setAtomPos(1, RDGeom::Point3D(1.2, 0, 0.0));
  conf->setAtomPos(2, RDGeom::Point3D(0, 1.5, 0.0));

  featSPtrs = factory->getFeaturesForMol(*testMol);
  TEST_ASSERT(featSPtrs.size() == 2);
  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "HBondDonor");
  TEST_ASSERT(featSPtr->getType() == "HDonor1");
  TEST_ASSERT(feq(featSPtr->getPos().x, 0.0));
  TEST_ASSERT(feq(featSPtr->getPos().y, 1.5));
  TEST_ASSERT(feq(featSPtr->getPos().z, 0.0));

  featSPtr = *(++featSPtrs.begin());
  TEST_ASSERT(featSPtr->getFamily() == "ZnBinder");
  TEST_ASSERT(featSPtr->getType() == "Carboxyl1");
  TEST_ASSERT(feq(featSPtr->getPos().x, 0.4));
  TEST_ASSERT(feq(featSPtr->getPos().y, 0.5));
  TEST_ASSERT(feq(featSPtr->getPos().z, 0.0));
  delete testMol;

  delete factory;

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void test7() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Test building a FeatureFactory from a file"
                        << std::endl;

  std::string rdbase = getenv("RDBASE");
  std::string fName =
      rdbase + "/Code/GraphMol/MolChemicalFeatures/test_data/featDef.txt";

  MolChemicalFeatureFactory *factory;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;

  std::ifstream inStream(fName.c_str());
  TEST_ASSERT(inStream.is_open());

  auto &instrm = static_cast<std::istream &>(inStream);
  factory = buildFeatureFactory(instrm);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);
  featDefIt = factory->beginFeatureDefs();
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getFamily() == "HBondDonor");

  featDefIt++;
  featDef = *featDefIt;
  TEST_ASSERT(featDefIt != factory->endFeatureDefs());
  TEST_ASSERT(featDef->getFamily() == "HBondAcceptor");

  featDefIt++;
  TEST_ASSERT(featDefIt == factory->endFeatureDefs());

  delete (factory);

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void testIssue224() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Testing Issue 224." << std::endl;

  std::string inText, inLine;
  std::istringstream ss;
  MolChemicalFeatureDef *featDef;

  std::map<std::string, std::string> atomTypeDefs;
  atomTypeDefs["{donor}"] = "$([O,N])";

  inText =
      "\n"
      "DefineFeature HDonor1 [O,N]\n"
      "  Family HDONOR\n"
      "  Weights 1.0\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  unsigned int tmpLine = 2;
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonor1");
  TEST_ASSERT(featDef->getSmarts() == "[O,N]");
  TEST_ASSERT(featDef->getNumWeights() == 1);
  TEST_ASSERT(feq(*featDef->beginWeights(), 1.0));
  delete featDef;

  inText =
      "\n"
      "# comment, ignore me\n"
      "DefineFeature HDonor1 [O,N]\n"
      " \n"
      "  Family HDONOR\n"
      " \n"
      "# comment, ignore me\n"
      "  Weights 1.0\n"
      "# comment, ignore me\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonor1");
  TEST_ASSERT(featDef->getSmarts() == "[O,N]");
  TEST_ASSERT(featDef->getNumWeights() == 1);
  TEST_ASSERT(feq(*featDef->beginWeights(), 1.0));
  delete featDef;

  // repeat those tests using some continuation lines
  // (Issue 346):
  inText =
      "\n"
      "# comment, ignore me\n"
      "DefineFeature HDonor1 \\\n"
      "  [O,N]\n"
      "  Family HDONOR\n"
      " \n"
      "# comment, ignore me\n"
      "  Weights 1.0\n"
      "# comment, ignore me\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonor1");
  TEST_ASSERT(featDef->getSmarts() == "[O,N]");
  TEST_ASSERT(featDef->getNumWeights() == 1);
  TEST_ASSERT(feq(*featDef->beginWeights(), 1.0));
  delete featDef;

  inText =
      "\n"
      "# comment, ignore me\n"
      "DefineFeature HDonor1 \\\n"
      "# comment, ignore me\n"
      "  [O,N]\n"
      "  Family \\\n"
      "\tHDONOR\n"
      "# comment, ignore me\n"
      "  Weights \\\n"
      "1.0\n"
      "# comment, ignore me\n"
      "EndFeature\n";
  ss.clear();
  ss.str(inText);
  inLine = RDKit::getLine(ss);
  featDef = Local::parseFeatureDef(ss, inLine, tmpLine, atomTypeDefs);
  TEST_ASSERT(featDef);
  TEST_ASSERT(featDef->getFamily() == "HDONOR");
  TEST_ASSERT(featDef->getType() == "HDonor1");
  TEST_ASSERT(featDef->getSmarts() == "[O,N]");
  TEST_ASSERT(featDef->getNumWeights() == 1);
  TEST_ASSERT(feq(*featDef->beginWeights(), 1.0));
  delete featDef;

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void testIssue225() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Testing Issue 225." << std::endl;

  MatchVectType mv;
  std::string inText;
  int res;
  MolChemicalFeatureDef::CollectionType featureDefs;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "DefineFeature HDonor1 [N,O;!H0]\n"
      "  Family HBondDonor\n"
      "  Weights 1.0\n"
      "EndFeature\n"
      "\n"
      "DefineFeature HAcceptor1 [N,O]\n"
      "  Family HAcceptor\n"
      "  Weights 1.0\n"
      "EndFeature\n";
  res = parseFeatureData(inText, featureDefs);
  TEST_ASSERT(!res);
  TEST_ASSERT(featureDefs.size() == 2);
  featDefIt = featureDefs.begin();
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getSmarts() == "[N,O;!H0]");
  TEST_ASSERT(featDef->getPattern());
  TEST_ASSERT(featDef->getPattern()->getNumAtoms() == 1);
  TEST_ASSERT(featDef->getNumWeights() == 1);

  featDef = *(++featureDefs.begin());
  TEST_ASSERT(featDef->getSmarts() == "[N,O]");
  TEST_ASSERT(featDef->getPattern());
  TEST_ASSERT(featDef->getPattern()->getNumAtoms() == 1);
  TEST_ASSERT(featDef->getNumWeights() == 1);

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void testIssue346() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Test Issue346" << std::endl;

  std::string rdbase = getenv("RDBASE");
  std::string fName =
      rdbase + "/Code/GraphMol/MolChemicalFeatures/test_data/featDef2.txt";

  MolChemicalFeatureFactory *factory;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;

  std::ifstream inStream(fName.c_str());
  TEST_ASSERT(inStream.is_open());

  auto &instrm = static_cast<std::istream &>(inStream);
  factory = buildFeatureFactory(instrm);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);
  featDefIt = factory->beginFeatureDefs();
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getFamily() == "HBondDonor");

  featDefIt++;
  featDef = *featDefIt;
  TEST_ASSERT(featDefIt != factory->endFeatureDefs());
  TEST_ASSERT(featDef->getFamily() == "HBondAcceptor");

  featDefIt++;
  TEST_ASSERT(featDefIt == factory->endFeatureDefs());

  delete (factory);

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void testIssue347() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Test Issue347" << std::endl;

  ROMol *testMol;
  MolChemicalFeatureFactory *factory;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;
  FeatSPtrList featSPtrs;
  boost::shared_ptr<MolChemicalFeature> featSPtr;

  std::string fdef;

  fdef =
      "DefineFeature CTriplet [C][C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0,1.0\n"
      "EndFeature\n"
      "DefineFeature CPair [C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0\n"
      "EndFeature\n";
  factory = buildFeatureFactory(fdef);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);
  featDefIt = factory->beginFeatureDefs();
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getFamily() == "LumpedHydrophobe");

  featDefIt++;
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getFamily() == "LumpedHydrophobe");

  featDefIt++;
  TEST_ASSERT(featDefIt == factory->endFeatureDefs());

  testMol = SmilesToMol("CCC");
  TEST_ASSERT(testMol);
  featSPtrs = factory->getFeaturesForMol(*testMol);
  TEST_ASSERT(featSPtrs.size() == 1);
  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "LumpedHydrophobe");
  TEST_ASSERT(featSPtr->getType() == "CTriplet");

  delete factory;
  // now reverse the order and we should get two matches:
  fdef =
      "DefineFeature CPair [C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0\n"
      "EndFeature\n"
      "DefineFeature CTriplet [C][C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0,1.0\n"
      "EndFeature\n";
  factory = buildFeatureFactory(fdef);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);
  featDefIt = factory->beginFeatureDefs();
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getFamily() == "LumpedHydrophobe");

  featDefIt++;
  featDef = *featDefIt;
  TEST_ASSERT(featDef->getFamily() == "LumpedHydrophobe");

  featDefIt++;
  TEST_ASSERT(featDefIt == factory->endFeatureDefs());
  featSPtrs = factory->getFeaturesForMol(*testMol);
  TEST_ASSERT(featSPtrs.size() == 3);

  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "LumpedHydrophobe");
  TEST_ASSERT(featSPtr->getType() == "CPair");

  featSPtr = *(++featSPtrs.begin());
  TEST_ASSERT(featSPtr->getFamily() == "LumpedHydrophobe");
  TEST_ASSERT(featSPtr->getType() == "CPair");

  featSPtr = *(++++featSPtrs.begin());
  TEST_ASSERT(featSPtr->getFamily() == "LumpedHydrophobe");
  TEST_ASSERT(featSPtr->getType() == "CTriplet");

  delete factory;
  delete testMol;

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void testIssue348() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Testing Issue 348." << std::endl;

  MatchVectType mv;
  std::string inText;
  int res;
  (void)res;  // unused in any test
  MolChemicalFeatureDef::CollectionType featureDefs;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "DefineFeature CTriplet \\\n"
      "[C][C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0,1.0\n"
      "EndFeature\n"
      "DefineFeature CTriplet [C[C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0\n"
      "EndFeature\n";

  bool ok;
  try {
    res = parseFeatureData(inText, featureDefs);
    ok = false;
  } catch (FeatureFileParseException &fpe) {
    ok = true;
    TEST_ASSERT(fpe.lineNo() == 6);
  }
  TEST_ASSERT(ok);
}

void testNestedAtomTypes() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Testing Nested AtomType definitions." << std::endl;

  MatchVectType mv;
  std::string inText;
  int res;
  MolChemicalFeatureDef::CollectionType featureDefs;
  MolChemicalFeatureDef::CollectionType::const_iterator featDefIt;
  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "AtomType Carbon_NonPolar [C;!$(C=[O,N,P,S]);!$(C#N)]\n"
      "AtomType Hphobe [{Carbon_NonPolar},c]\n"
      "AtomType RingHphobe [{Hphobe};R]\n"
      "DefineFeature HP1 [{RingHphobe}]\n"
      "  Family Hydrophobe\n"
      "  Weights 1.0\n"
      "EndFeature\n";

  res = parseFeatureData(inText, featureDefs);
  TEST_ASSERT(!res);
  TEST_ASSERT(featureDefs.size() == 1);
  std::cerr << (*featureDefs.begin())->getSmarts() << std::endl;
}

void testGithub252() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Testing Github Issue #252: crash when calling "
                           "getPos() with no conformer."
                        << std::endl;
  BOOST_LOG(rdErrorLog) << "     expect a precondition failure message below"
                        << std::endl;

  ROMol *testMol;
  MolChemicalFeatureFactory *factory;
  FeatSPtrList featSPtrs;
  boost::shared_ptr<MolChemicalFeature> featSPtr;

  std::string fdef =
      "DefineFeature CTriplet [C][C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0,1.0\n"
      "EndFeature\n"
      "DefineFeature CPair [C][C]\n"
      "  Family LumpedHydrophobe\n"
      "  Weights 1.0,1.0\n"
      "EndFeature\n";
  factory = buildFeatureFactory(fdef);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);
  testMol = SmilesToMol("CCC");
  TEST_ASSERT(testMol);
  featSPtrs = factory->getFeaturesForMol(*testMol);
  TEST_ASSERT(featSPtrs.size() == 1);
  featSPtr = *featSPtrs.begin();
  TEST_ASSERT(featSPtr->getFamily() == "LumpedHydrophobe");
  bool ok = false;
  try {
    featSPtr->getPos();
  } catch (const Invar::Invariant &) {
    ok = true;
  }

  delete factory;
  delete testMol;

  TEST_ASSERT(ok);
  BOOST_LOG(rdErrorLog) << "   Done" << std::endl;
}

void testGithub2077() {
  BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
  BOOST_LOG(rdErrorLog) << "Testing github 2077: add confId argument to "
                           "MolChemicalFeatureFactor::getFeaturesForMol()"
                        << std::endl;

  ROMol *testMol;
  Conformer *conf;
  std::string inText;
  FeatSPtrList featSPtrs;
  boost::shared_ptr<MolChemicalFeature> featSPtr;

  MolChemicalFeatureFactory *factory;

  MolChemicalFeatureDef::CollectionType::value_type featDef;

  inText =
      "DefineFeature HDonor1 [N,O;!H0]\n"
      "  Family HBondDonor\n"
      "  Weights 1.0\n"
      "EndFeature\n"
      "DefineFeature Carboxyl1 C(=O)[O;H1,-]\n"
      "  Family ZnBinder\n"
      "  Weights 1.0,1.0,1.0\n"
      "EndFeature\n";

  factory = buildFeatureFactory(inText);
  TEST_ASSERT(factory);
  TEST_ASSERT(factory->getNumFeatureDefs() == 2);

  testMol = SmilesToMol("C(=O)O");
  TEST_ASSERT(testMol);
  conf = new Conformer(3);
  testMol->addConformer(conf, true);
  conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0.0));
  conf->setAtomPos(1, RDGeom::Point3D(1.2, 0, 0.0));
  conf->setAtomPos(2, RDGeom::Point3D(0, 1.5, 0.0));

  conf = new Conformer(3);
  testMol->addConformer(conf, true);
  conf->setAtomPos(0, RDGeom::Point3D(1, 0, 0.0));
  conf->setAtomPos(1, RDGeom::Point3D(2.2, 0, 0.0));
  conf->setAtomPos(2, RDGeom::Point3D(1, 1.5, 0.0));

  {
    featSPtrs = factory->getFeaturesForMol(*testMol);
    TEST_ASSERT(featSPtrs.size() == 2);
    featSPtr = *featSPtrs.begin();
    TEST_ASSERT(featSPtr->getActiveConformer() == -1);
    TEST_ASSERT(featSPtr->getFamily() == "HBondDonor");
    TEST_ASSERT(featSPtr->getType() == "HDonor1");
    TEST_ASSERT(feq(featSPtr->getPos().x, 0.0));
    TEST_ASSERT(feq(featSPtr->getPos().y, 1.5));
    TEST_ASSERT(feq(featSPtr->getPos().z, 0.0));
  }
  {
    featSPtrs = factory->getFeaturesForMol(*testMol, "", 1);
    TEST_ASSERT(featSPtrs.size() == 2);
    featSPtr = *featSPtrs.begin();
    TEST_ASSERT(featSPtr->getActiveConformer() == 1);
    TEST_ASSERT(featSPtr->getFamily() == "HBondDonor");
    TEST_ASSERT(featSPtr->getType() == "HDonor1");
    TEST_ASSERT(feq(featSPtr->getPos().x, 1.0));
    TEST_ASSERT(feq(featSPtr->getPos().y, 1.5));
    TEST_ASSERT(feq(featSPtr->getPos().z, 0.0));
  }
  delete testMol;

  delete factory;

  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
//
//-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
int main() {
  RDLog::InitLogs();
#if 1
  test1();
  test2();
  test3();
  test4();
  test5();
  test6();
  test7();
  testIssue224();
  testIssue225();
  testIssue346();
  testIssue347();
  testIssue348();
#endif
  testNestedAtomTypes();
  testGithub252();
  testGithub2077();
}
