// $Id$
//
//  Copyright (C) 2002-2008 Greg Landrum and 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 "SmartsWrite.h"
#include <sstream>
#include "SmilesWrite.h"
#include <GraphMol/RDKitBase.h>
#include <GraphMol/RDKitQueries.h>
#include <GraphMol/Canon.h>
#include <GraphMol/new_canon.h>
#include <RDGeneral/RDLog.h>

namespace RDKit {
using namespace Canon;

// local utility namespace
namespace {

std::string _recurseGetSmarts(const QueryAtom::QUERYATOM_QUERY *node,
                              bool negate);
std::string _recurseBondSmarts(const QueryBond::QUERYBOND_QUERY *node,
                               bool negate);

bool _checkForOrAndLowAnd(std::string smarts) {
  int orLoc, andLoc;
  // if we're a pure recursive smarts, we don't need to worry about this
  if (smarts[0] == '$' && smarts[smarts.size() - 1] == ')') return false;
  orLoc = smarts.find(",");
  andLoc = smarts.find(";");
  if ((orLoc > 0) && (andLoc > 0)) {
    return true;
  } else {
    return false;
  }
}

std::string _combineChildSmarts(std::string cs1, std::string cs2,
                                std::string descrip) {
  std::string res = "";
  if ((descrip.find("Or") > 0) && (descrip.find("Or") < descrip.length())) {
    // if either of child smarts already have a "," and ";" we can't have one
    // more OR here
    if (_checkForOrAndLowAnd(cs1) || _checkForOrAndLowAnd(cs2)) {
      throw "This is a non-smartable query - OR above and below AND in the binary tree";
    }
    res += cs1;
    res += ",";
    res += cs2;
  } else if ((descrip.find("And") > 0) &&
             (descrip.find("And") < descrip.length())) {
    int orLoc1, orLoc2;
    std::string symb;
    orLoc1 = cs1.find(',');
    orLoc2 = cs2.find(',');
    if ((orLoc1 > 0) || (orLoc2 > 0)) {
      symb = ";";
    } else {
      symb = "&";
    }
    res += cs1;
    res += symb;
    res += cs2;
  } else {
    std::stringstream err;
    err << "Don't know how to combine using " << descrip;
    throw err.str();
  }
  return res;
}

template <typename T>
void describeQuery(const T *query, std::string leader = "\t") {
  // BOOST_LOG(rdInfoLog) << leader << query->getDescription() << std::endl;
  typename T::CHILD_VECT_CI iter;
  for (iter = query->beginChildren(); iter != query->endChildren(); ++iter) {
    describeQuery(iter->get(), leader + "\t");
  }
}

// called with the children of AND queries:
std::string smartsOrganicAtom(const QueryAtom::QUERYATOM_QUERY *child1,
                              const QueryAtom::QUERYATOM_QUERY *child2) {
  PRECONDITION(child1 && child2, "bad query");

  std::string desc1 = child1->getDescription();
  std::string desc2 = child2->getDescription();
  const QueryAtom::QUERYATOM_QUERY *origA, *otherA;
  if (desc1 == "AtomAtomicNum") {
    origA = child1;
    otherA = child2;
  } else {
    origA = child2;
    otherA = child1;
  }

  std::string odsc = otherA->getDescription();
  CHECK_INVARIANT(((odsc == "AtomIsAliphatic") || (odsc == "AtomIsAromatic")),
                  "Should be either an aromatic or Aliphatic atom");

  const ATOM_EQUALS_QUERY *torig =
      static_cast<const ATOM_EQUALS_QUERY *>(origA);
  int val = torig->getVal();
  std::string res = PeriodicTable::getTable()->getElementSymbol(val);
  if (odsc == "AtomIsAromatic") {
    // if aromatic convert the first leter to a small letter
    res[0] += ('a' - 'A');
    if (res.length() > 1) {
      res = "[" + res + "]";
    }
  }
  if (torig->getNegation()) {
    res = "!" + res;
  }
  return res;
}

std::string getAtomSmartsSimple(const ATOM_EQUALS_QUERY *query,
                                bool &needParen) {
  PRECONDITION(query, "bad query");

  std::string descrip = query->getDescription();
  std::stringstream res;
  if (descrip == "AtomImplicitHCount") {
    res << "h" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomHasImplicitH") {
    res << "h";
    needParen = true;
  } else if (descrip == "AtomTotalValence") {
    res << "v" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomAtomicNum") {
    res << "#" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomExplicitDegree") {
    res << "D" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomTotalDegree") {
    res << "X" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomHasRingBond") {
    res << "x";
    needParen = true;
  } else if (descrip == "AtomHCount") {
    res << "H" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomIsAliphatic") {
    res << "A";
    needParen = false;
  } else if (descrip == "AtomIsAromatic") {
    res << "a";
    needParen = false;
  } else if (descrip == "AtomNull") {
    res << "*";
    needParen = false;
  } else if (descrip == "AtomInRing") {
    res << "R";
    needParen = true;
  } else if (descrip == "AtomMinRingSize") {
    res << "r" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomInNRings") {
    res << "R";
    if (query->getVal() >= 0) {
      res << query->getVal();
    }
    needParen = true;
  } else if (descrip == "AtomFormalCharge") {
    int val = query->getVal();
    if (val < 0) {
      res << "-";
    } else {
      res << "+";
    }
    if (abs(val) != 1) {
      res << abs(val);
    }
    needParen = true;
  } else if (descrip == "AtomHybridization") {
    res << "^";
    switch (query->getVal()) {
      case Atom::S:
        res << "0";
        break;
      case Atom::SP:
        res << "1";
        break;
      case Atom::SP2:
        res << "2";
        break;
      case Atom::SP3:
        res << "3";
        break;
    }
  } else if (descrip == "AtomMass") {
    res << query->getVal() / massIntegerConversionFactor << "*";
    needParen = true;
  } else if (descrip == "AtomIsotope") {
    res << query->getVal() << "*";
    needParen = true;
  } else if (descrip == "AtomHasRingBond") {
    res << "x";
    needParen = true;
  } else if (descrip == "AtomRingBondCount") {
    res << "x" << query->getVal();
    needParen = true;
  } else if (descrip == "AtomUnsaturated") {
    res << "$(*=,:,#*)";
    needParen = true;
  } else {
    BOOST_LOG(rdWarningLog)
        << "Cannot write SMARTS for query type : " << descrip
        << ". Ignoring it." << std::endl;
    res << "*";
  }
  return res.str();
}

std::string getRecursiveStructureQuerySmarts(
    const QueryAtom::QUERYATOM_QUERY *query) {
  PRECONDITION(query, "bad query");
  PRECONDITION(query->getDescription() == "RecursiveStructure", "bad query");
  const RecursiveStructureQuery *rquery =
      static_cast<const RecursiveStructureQuery *>(query);
  ROMol *qmol = const_cast<ROMol *>(rquery->getQueryMol());
  std::string res = MolToSmarts(*qmol);
  res = "$(" + res + ")";
  if (rquery->getNegation()) {
    res = "!" + res;
  }
  return res;
}

std::string getBondSmartsSimple(const BOND_EQUALS_QUERY *bquery) {
  PRECONDITION(bquery, "bad query");
  std::string descrip = bquery->getDescription();
  std::string res = "";
  if (descrip == "BondNull") {
    res += "~";
  } else if (descrip == "BondInRing") {
    res += "@";
  } else if (descrip == "BondDir") {
    int val = bquery->getVal();
    if (val == static_cast<int>(Bond::ENDDOWNRIGHT)) {
      res += "\\";
    } else if (val == static_cast<int>(Bond::ENDUPRIGHT)) {
      res += "/";
    } else {
      throw "Can't write smarts for this bond dir type";
    }
  } else if (descrip == "BondOrder") {
    int val = bquery->getVal();
    if (val == static_cast<int>(Bond::SINGLE)) {
      res += "-";
    } else if (val == static_cast<int>(Bond::DOUBLE)) {
      res += "=";
    } else if (val == static_cast<int>(Bond::TRIPLE)) {
      res += "#";
    } else if (val == static_cast<int>(Bond::AROMATIC)) {
      res += ":";
    }
  } else {
    std::stringstream msg;
    msg << "Canot write smarts for this query bond type : " << descrip;
    throw msg.str().c_str();
  }
  return res;
}

std::string _recurseGetSmarts(const QueryAtom::QUERYATOM_QUERY *node,
                              bool negate) {
  PRECONDITION(node, "bad node");
  // the algorithm goes something like this
  // - recursively get the smarts for the child queries
  // - combine the child smarts using the following rules:
  //      - if we are currently at an OR query, combine the subqueries with a
  //      ",",
  //        but only if neither of child smarts do not contain "," and ";"
  //        This situation leads to a no smartable situation and throw an error
  //      - if we are currently at an and query, combine the child smarts with
  //      "&"
  //        if neither of the child smarts contain a "," - otherwise combine
  //        them
  //        the child smarts with a ";"
  //
  // There is an additional complication with composite nodes that carry a
  // negation - in this
  // case we will propogate the neagtion to the child nodes using the following
  // rules
  //   NOT (a AND b) = ( NOT (a)) AND ( NOT (b))
  //   NOT (a OR b) = ( NOT (a)) OR ( NOT (b))

  std::string descrip = node->getDescription();
  std::string res = "";

  const QueryAtom::QUERYATOM_QUERY *child1;
  const QueryAtom::QUERYATOM_QUERY *child2;
  QueryAtom::QUERYATOM_QUERY::CHILD_VECT_CI chi;
  chi = node->beginChildren();
  child1 = chi->get();
  chi++;
  child2 = chi->get();
  chi++;
  // OK we should be at the end of vector by now - since we can have only two
  // children,
  // well - at least in this case
  CHECK_INVARIANT(chi == node->endChildren(), "Too many children on the query");

  std::string dsc1, dsc2;
  dsc1 = child1->getDescription();
  dsc2 = child2->getDescription();
  std::string csmarts1, csmarts2;

  bool needParen;

  // deal with any special AND cases
  //  1. This "node" is an AtomAnd between a AliphaticAtom (or AromaticAtom) and
  //      an organic atom e.g. "C"
  if (descrip == "AtomAnd") {
    bool specialCase = false;
    // case 1
    if (((dsc1 == "AtomAtomicNum") &&
         ((dsc2 == "AtomIsAliphatic") || (dsc2 == "AtomIsAromatic"))) ||
        ((dsc2 == "AtomAtomicNum") &&
         ((dsc1 == "AtomIsAliphatic") || (dsc1 == "AtomIsAromatic")))) {
      // we trap this one because it's nicer to see
      //   "CC" in the output than "[#6&A][#6&A]"
      res = smartsOrganicAtom(child1, child2);
      specialCase = true;
    }
    if (specialCase) {
      if (negate) {
        res = "!" + res;
      }
      return res;
    }
  }

  // deal with the first child
  if (dsc1 == "RecursiveStructure") {
    csmarts1 = getRecursiveStructureQuerySmarts(child1);
  } else if ((dsc1 != "AtomOr") && (dsc1 != "AtomAnd")) {
    // child 1 is a simple node
    const ATOM_EQUALS_QUERY *tchild =
        static_cast<const ATOM_EQUALS_QUERY *>(child1);
    csmarts1 = getAtomSmartsSimple(tchild, needParen);
    bool nneg = (negate) ^ (tchild->getNegation());
    if (nneg) {
      csmarts1 = "!" + csmarts1;
    }
  } else {
    // child 1 is composite node - recurse
    bool nneg = (negate) ^ (child1->getNegation());
    csmarts1 = _recurseGetSmarts(child1, nneg);
  }

  // deal with the second child
  if (dsc2 == "RecursiveStructure") {
    csmarts2 = getRecursiveStructureQuerySmarts(child2);
  } else if ((dsc2 != "AtomOr") && (dsc2 != "AtomAnd")) {
    // child 2 is a simple node
    const ATOM_EQUALS_QUERY *tchild =
        static_cast<const ATOM_EQUALS_QUERY *>(child2);
    csmarts2 = getAtomSmartsSimple(tchild, needParen);
    bool nneg = (negate) ^ (tchild->getNegation());
    if (nneg) {
      csmarts2 = "!" + csmarts2;
    }
  } else {
    bool nneg = (negate) ^ (child2->getNegation());
    csmarts2 = _recurseGetSmarts(child2, nneg);
  }

  // ok if we have a negation and we have an OR , we have to change to
  // an AND since we propogated the negation
  // i.e NOT (A OR B) = (NOT (A)) AND (NOT(B))
  if (negate) {
    if (descrip == "AtomOr") {
      descrip = "AtomAnd";
    } else if (descrip == "AtomAnd") {
      descrip = "AtomOr";
    }
  }

  res += _combineChildSmarts(csmarts1, csmarts2, descrip);
  return res;
}

std::string _recurseBondSmarts(const QueryBond::QUERYBOND_QUERY *node,
                               bool negate) {
  // the algorithm goes something like this
  // - recursively get the smarts for the child query bonds
  // - combine the child smarts using the following rules:
  //      - if we are currently at an OR query, combine the subqueries with a
  //      ",",
  //        but only if neither of child smarts do not contain "," and ";"
  //        This situation leads to a no smartable situation and throw an error
  //      - if we are currently at an and query, combine the child smarts with
  //      "&"
  //        if neither of the child smarts contain a "," - otherwise combine
  //        them
  //        the child smarts with a ";"
  //
  // There is an additional complication with composite nodes that carry a
  // negation - in this
  // case we will propogate the neagtion to the child nodes using the following
  // rules
  //   NOT (a AND b) = ( NOT (a)) AND ( NOT (b))
  //   NOT (a OR b) = ( NOT (a)) OR ( NOT (b))
  PRECONDITION(node, "bad node");
  std::string descrip = node->getDescription();
  std::string res = "";

  const QueryBond::QUERYBOND_QUERY *child1;
  const QueryBond::QUERYBOND_QUERY *child2;
  QueryBond::QUERYBOND_QUERY::CHILD_VECT_CI chi;

  chi = node->beginChildren();
  child1 = chi->get();
  chi++;
  child2 = chi->get();
  chi++;
  // OK we should be at the end of vector by now - since we can have only two
  // children,
  // well - atleat in this case
  CHECK_INVARIANT(chi == node->endChildren(), "Too many children on the query");

  std::string dsc1, dsc2;
  dsc1 = child1->getDescription();
  dsc2 = child2->getDescription();
  std::string csmarts1, csmarts2;

  if ((dsc1 != "BondOr") && (dsc1 != "BondAnd")) {
    // child1 is  simple node get the smarts directly
    const BOND_EQUALS_QUERY *tchild =
        static_cast<const BOND_EQUALS_QUERY *>(child1);
    csmarts1 = getBondSmartsSimple(tchild);
    bool nneg = (negate) ^ (tchild->getNegation());
    if (nneg) {
      csmarts1 = "!" + csmarts1;
    }
  } else {
    // child1 is a composite node recurse further
    bool nneg = (negate) ^ (child1->getNegation());
    csmarts1 = _recurseBondSmarts(child1, nneg);
  }

  // now deal with the second child
  if ((dsc2 != "BondOr") && (dsc2 != "BondAnd")) {
    // child 2 is a simple node
    const BOND_EQUALS_QUERY *tchild =
        static_cast<const BOND_EQUALS_QUERY *>(child2);
    csmarts2 = getBondSmartsSimple(tchild);
    bool nneg = (negate) ^ (tchild->getNegation());
    if (nneg) {
      csmarts2 = "!" + csmarts2;
    }
  } else {
    // child two is a composite node - recurse
    bool nneg = (negate) ^ (child2->getNegation());
    csmarts1 = _recurseBondSmarts(child2, nneg);
  }

  // ok if we have a negation and we have to change the underlying logic, since
  // we propogated the negation
  // i.e NOT (A OR B) = (NOT (A)) AND (NOT(B))
  if (negate) {
    if (descrip == "BondOr") {
      descrip = "BondAnd";
    } else if (descrip == "BondAnd") {
      descrip = "BondOr";
    }
  }
  res += _combineChildSmarts(csmarts1, csmarts2, descrip);
  return res;
}

std::string FragmentSmartsConstruct(ROMol &mol, unsigned int atomIdx,
                                    std::vector<Canon::AtomColors> &colors,
                                    UINT_VECT &ranks) {
  Canon::MolStack molStack;
  molStack.reserve(mol.getNumAtoms() + mol.getNumBonds());
  std::stringstream res;

  // this is dirty trick get around the fact that canonicalizeFragment
  // thinks we already called findSSSR - to do some atom ranking
  // but for smarts we are going to ignore that part. We will artificially
  // set the "SSSR" property to an empty property
  VECT_INT_VECT rings;
  mol.getRingInfo()->reset();
  mol.getRingInfo()->initialize();
  Canon::canonicalizeFragment(mol, atomIdx, colors, ranks, molStack);

  // now clear the "SSSR" property
  mol.getRingInfo()->reset();
  Canon::MolStack::const_iterator msCI;
  for (msCI = molStack.begin(); msCI != molStack.end(); msCI++) {
    switch (msCI->type) {
      case Canon::MOL_STACK_ATOM: {
        QueryAtom *qatm = static_cast<QueryAtom *>(msCI->obj.atom);
        res << SmartsWrite::GetAtomSmarts(qatm);
        break;
      }
      case Canon::MOL_STACK_BOND: {
        QueryBond *qbnd = static_cast<QueryBond *>(msCI->obj.bond);
        res << SmartsWrite::GetBondSmarts(qbnd);
        break;
      }
      case Canon::MOL_STACK_RING: {
        if (msCI->number < 10)
          res << msCI->number;
        else
          res << "%" << msCI->number;
        break;
      }
      case Canon::MOL_STACK_BRANCH_OPEN: {
        res << "(";
        break;
      }
      case Canon::MOL_STACK_BRANCH_CLOSE: {
        res << ")";
        break;
      }
      default:
        break;
    }
  }
  return res.str();
}

// this is the used when converting a SMILES or
// non-query atom from a mol file into SMARTS.
std::string getNonQueryAtomSmarts(const QueryAtom *qatom) {
  PRECONDITION(qatom, "bad atom");
  PRECONDITION(!qatom->hasQuery(), "atom should not have query");
  std::stringstream res;
  res << "[";
  if (SmilesWrite::inOrganicSubset(qatom->getAtomicNum())) {
    res << "#" << qatom->getAtomicNum();
  } else {
    res << qatom->getSymbol();
  }
  int hs = qatom->getNumExplicitHs();
  // FIX: probably should be smarter about Hs:
  if (hs) {
    res << "H";
    if (hs > 1) res << hs;
  }
  int chg = qatom->getFormalCharge();
  if (chg) {
    if (chg == -1) {
      res << "-";
    } else if (chg == 1) {
      res << "+";
    } else if (chg < 0) {
      res << qatom->getFormalCharge();
    } else {
      res << "+" << qatom->getFormalCharge();
    }
  }
  res << "]";
  return res.str();
}

// this is the used when converting a SMILES or
// non-query bond from a mol file into SMARTS.
std::string getNonQueryBondSmarts(const QueryBond *qbond) {
  PRECONDITION(qbond, "bad bond");
  PRECONDITION(!qbond->hasQuery(), "bond should not have query");
  std::string res;

  if (qbond->getIsAromatic()) {
    res = ":";
  } else {
    switch (qbond->getBondType()) {
      case Bond::SINGLE:
        res = "-";
        break;
      case Bond::DOUBLE:
        res = "=";
        break;
      case Bond::TRIPLE:
        res = "#";
        break;
      case Bond::AROMATIC:
        res = ":";
        break;
      default:
        // do nothing (i.e. match anything)
        res = "";
        break;
    }
  }
  return res;
}

}  // end of local utility namespace

namespace SmartsWrite {
std::string GetAtomSmarts(const QueryAtom *qatom) {
  PRECONDITION(qatom, "bad atom");
  std::string res;
  bool needParen = false;

  // BOOST_LOG(rdInfoLog)<<"Atom: " <<qatom->getIdx()<<std::endl;
  if (!qatom->hasQuery()) {
    res = getNonQueryAtomSmarts(qatom);
    // BOOST_LOG(rdInfoLog)<<"\tno query:" <<res;
    return res;
  }
  QueryAtom::QUERYATOM_QUERY *query = qatom->getQuery();
  // describeQuery(query);

  PRECONDITION(query, "atom has no query");
  std::string descrip = qatom->getQuery()->getDescription();
  if (descrip == "") {
    // we have simple atom - just generate the smiles and return
    res = SmilesWrite::GetAtomSmiles(qatom);
    if (res[0] == '[') {
      // chop the brackets off, we'll put them back on later:
      needParen = true;
      res = res.substr(1, res.size() - 2);
    }
  } else if ((descrip == "AtomOr") || (descrip == "AtomAnd")) {
    // we have a composite query
    needParen = true;
    res = _recurseGetSmarts(query, query->getNegation());
    if (res.length() == 1) {  // single atom symbol we don't need parens
      needParen = false;
    }
  } else if (descrip == "RecursiveStructure") {
    // it's a bare recursive structure query:
    res = getRecursiveStructureQuerySmarts(query);
    needParen = true;
  } else {  // we have a simple smarts
    ATOM_EQUALS_QUERY *tquery =
        static_cast<ATOM_EQUALS_QUERY *>(qatom->getQuery());
    res = getAtomSmartsSimple(tquery, needParen);
    if (tquery->getNegation()) {
      res = "!" + res;
    }
  }
  std::string mapNum;
  if (qatom->getPropIfPresent(common_properties::molAtomMapNumber, mapNum)) {
    needParen = true;
    res += ":" + mapNum;
  }
  if (needParen) {
    res = "[" + res + "]";
  }
  return res;
}

std::string GetBondSmarts(const QueryBond *bond) {
  PRECONDITION(bond, "bad bond");
  std::string res = "";

  // BOOST_LOG(rdInfoLog)<<"bond: " <<bond->getIdx()<<std::endl;;
  // it is possible that we are regular single bond and we don't need to write
  // anything
  if (!bond->hasQuery()) {
    res = getNonQueryBondSmarts(bond);
    // BOOST_LOG(rdInfoLog)<<"\tno query:" <<res;
    return res;
  }
  // describeQuery(bond->getQuery());
  if ((typeid(*bond) == typeid(Bond)) &&
      ((bond->getBondType() == Bond::SINGLE) ||
       (bond->getBondType() == Bond::AROMATIC))) {
    return res;
  }
  const QueryBond::QUERYBOND_QUERY *query = bond->getQuery();
  PRECONDITION(query, "bond has no query");
  std::string descrip = query->getDescription();

  if ((descrip == "BondAnd") || (descrip == "BondOr")) {
    // composite query
    res = _recurseBondSmarts(query, query->getNegation());
  } else {
    // simple query
    if (query->getNegation()) {
      res = "!";
    }
    const BOND_EQUALS_QUERY *tquery =
        static_cast<const BOND_EQUALS_QUERY *>(query);
    res += getBondSmartsSimple(tquery);
  }
  return res;
}
}  // end of namespace SmartsWrite

std::string MolToSmarts(ROMol &inmol, bool doIsomericSmiles) {
  RDUNUSED_PARAM(doIsomericSmiles);  // does this parameter even make sense?
  std::string res;
  unsigned int nAtoms = inmol.getNumAtoms();
  if (!nAtoms) return "";

  ROMol mol(inmol);
  UINT_VECT ranks;
  ranks.resize(nAtoms);
  // For smiles writing we would be canonicalizing but we will not do that here.
  // We will simple use the atom indices as the rank
  for (ROMol::AtomIterator atIt = mol.beginAtoms(); atIt != mol.endAtoms();
       atIt++) {
    ranks.push_back((*atIt)->getIdx());
  }

  std::vector<AtomColors> colors;
  colors.resize(nAtoms);
  std::vector<AtomColors>::iterator colorIt;
  for (colorIt = colors.begin(); colorIt != colors.end(); colorIt++)
    *colorIt = Canon::WHITE_NODE;

  colorIt = std::find(colors.begin(), colors.end(), Canon::WHITE_NODE);
  while (colorIt != colors.end()) {
    unsigned int nextAtomIdx = 0;
    unsigned int nextRank;
    std::string subSmi;
    nextRank = nAtoms + 1;
    for (unsigned int i = 0; i < nAtoms; i++) {
      if (colors[i] == Canon::WHITE_NODE && ranks[i] < nextRank) {
        nextRank = ranks[i];
        nextAtomIdx = i;
      }
    }

    subSmi = FragmentSmartsConstruct(mol, nextAtomIdx, colors, ranks);
    res += subSmi;

    colorIt = std::find(colors.begin(), colors.end(), Canon::WHITE_NODE);
    if (colorIt != colors.end()) {
      res += ".";
    }
  }
  return res;
}
}
