import sys
import unittest

from rdkit import Chem
from rdkit.Chem import rdFMCS


class BondMatchOrderMatrix:

  def __init__(self, ignoreAromatization):
    self.MatchMatrix = [[False] * (Chem.BondType.ZERO + 1) for i in range(Chem.BondType.ZERO + 1)]
    for i in range(Chem.BondType.ZERO + 1):
      # fill cells of the same and unspecified type
      self.MatchMatrix[i][i] = True
      self.MatchMatrix[Chem.BondType.UNSPECIFIED][i] = \
          self.MatchMatrix[i][Chem.BondType.UNSPECIFIED] = True
      self.MatchMatrix[Chem.BondType.ZERO][i] = \
          self.MatchMatrix[i][Chem.BondType.ZERO] = True
    if ignoreAromatization:
      self.MatchMatrix[Chem.BondType.SINGLE][Chem.BondType.AROMATIC] = \
          self.MatchMatrix[Chem.BondType.AROMATIC][Chem.BondType.SINGLE] = True
      self.MatchMatrix[Chem.BondType.DOUBLE][Chem.BondType.AROMATIC] = \
          self.MatchMatrix[Chem.BondType.AROMATIC][Chem.BondType.DOUBLE] = True
    self.MatchMatrix[Chem.BondType.SINGLE][Chem.BondType.ONEANDAHALF] = \
        self.MatchMatrix[Chem.BondType.ONEANDAHALF][Chem.BondType.SINGLE] = True
    self.MatchMatrix[Chem.BondType.DOUBLE][Chem.BondType.TWOANDAHALF] = \
        self.MatchMatrix[Chem.BondType.TWOANDAHALF][Chem.BondType.DOUBLE] = True
    self.MatchMatrix[Chem.BondType.TRIPLE][Chem.BondType.THREEANDAHALF] = \
        self.MatchMatrix[Chem.BondType.THREEANDAHALF][Chem.BondType.TRIPLE] = True
    self.MatchMatrix[Chem.BondType.QUADRUPLE][Chem.BondType.FOURANDAHALF] = \
        self.MatchMatrix[Chem.BondType.FOURANDAHALF][Chem.BondType.QUADRUPLE] = True
    self.MatchMatrix[Chem.BondType.QUINTUPLE][Chem.BondType.FIVEANDAHALF] = \
        self.MatchMatrix[Chem.BondType.FIVEANDAHALF][Chem.BondType.QUINTUPLE] = True

  def isEqual(self, i, j):
    return self.MatchMatrix[i][j]


class CompareAny(rdFMCS.MCSAtomCompare):

  def __call__(self, p, mol1, atom1, mol2, atom2):
    if (p.MatchChiralTag and not self.CheckAtomChirality(p, mol1, atom1, mol2, atom2)):
      return False
    if (p.MatchFormalCharge and not self.CheckAtomCharge(p, mol1, atom1, mol2, atom2)):
      return False
    if (p.RingMatchesRingOnly):
      return self.CheckAtomRingMatch(p, mol1, atom1, mol2, atom2)
    return True


class CompareAnyHeavyAtom(CompareAny):

  def __call__(self, p, mol1, atom1, mol2, atom2):
    a1 = mol1.GetAtomWithIdx(atom1)
    a2 = mol2.GetAtomWithIdx(atom2)
    # Any atom, including H, matches another atom of the same type,  according to
    # the other flags
    if (a1.GetAtomicNum() == a2.GetAtomicNum()
        or (a1.GetAtomicNum() > 1 and a2.GetAtomicNum() > 1)):
      return CompareAny.__call__(self, p, mol1, atom1, mol2, atom2)
    return False


class CompareElements(rdFMCS.MCSAtomCompare):

  def __call__(self, p, mol1, atom1, mol2, atom2):
    a1 = mol1.GetAtomWithIdx(atom1)
    a2 = mol2.GetAtomWithIdx(atom2)
    if (a1.GetAtomicNum() != a2.GetAtomicNum()):
      return False
    if (p.MatchValences and a1.GetTotalValence() != a2.GetTotalValence()):
      return False
    if (p.MatchChiralTag and not self.CheckAtomChirality(p, mol1, atom1, mol2, atom2)):
      return False
    if (p.MatchFormalCharge and not self.CheckAtomCharge(p, mol1, atom1, mol2, atom2)):
      return False
    if p.RingMatchesRingOnly:
      return self.CheckAtomRingMatch(p, mol1, atom1, mol2, atom2)
    return True


class CompareIsotopes(rdFMCS.MCSAtomCompare):

  def __call__(self, p, mol1, atom1, mol2, atom2):
    a1 = mol1.GetAtomWithIdx(atom1)
    a2 = mol2.GetAtomWithIdx(atom2)
    if (a1.GetIsotope() != a2.GetIsotope()):
      return False
    if (p.MatchChiralTag and not self.CheckAtomChirality(p, mol1, atom1, mol2, atom2)):
      return False
    if (p.MatchFormalCharge and not self.CheckAtomCharge(p, mol1, atom1, mol2, atom2)):
      return False
    if p.RingMatchesRingOnly:
      return self.CheckAtomRingMatch(p, mol1, atom1, mol2, atom2)
    return True


class CompareOrder(rdFMCS.MCSBondCompare):
  match = BondMatchOrderMatrix(True)  # ignore Aromatization

  def __call__(self, p, mol1, bond1, mol2, bond2):
    b1 = mol1.GetBondWithIdx(bond1)
    b2 = mol2.GetBondWithIdx(bond2)
    t1 = b1.GetBondType()
    t2 = b2.GetBondType()
    if self.match.isEqual(t1, t2):
      if (p.MatchStereo and not self.CheckBondStereo(p, mol1, bond1, mol2, bond2)):
        return False
      if p.RingMatchesRingOnly:
        return self.CheckBondRingMatch(p, mol1, bond1, mol2, bond2)
      return True
    return False


class AtomCompareCompareIsInt(rdFMCS.MCSAtomCompare):
  __call__ = 1


class AtomCompareNoCompare(rdFMCS.MCSAtomCompare):
  pass


class AtomCompareUserData(rdFMCS.MCSAtomCompare):

  def __init__(self):
    super().__init__()
    self._matchAnyHet = False

  def setMatchAnyHet(self, v):
    self._matchAnyHet = v

  def __call__(self, p, mol1, atom1, mol2, atom2):
    a1 = mol1.GetAtomWithIdx(atom1)
    a2 = mol2.GetAtomWithIdx(atom2)
    if (a1.GetAtomicNum() != a2.GetAtomicNum()
        and ((not self._matchAnyHet) or a1.GetAtomicNum() == 6 or a2.GetAtomicNum() == 6)):
      return False
    if (p.MatchValences and a1.GetTotalValence() != a2.GetTotalValence()):
      return False
    if (p.MatchChiralTag and not self.CheckAtomChirality(p, mol1, atom1, mol2, atom2)):
      return False
    if (p.MatchFormalCharge and not self.CheckAtomCharge(p, mol1, atom1, mol2, atom2)):
      return False
    if p.RingMatchesRingOnly:
      return self.CheckAtomRingMatch(p, mol1, atom1, mol2, atom2)
    return True


class BondCompareCompareIsInt(rdFMCS.MCSBondCompare):
  __call__ = 1


class BondCompareNoCompare(rdFMCS.MCSBondCompare):
  pass


class BondCompareUserData(rdFMCS.MCSBondCompare):

  def __init__(self):
    super().__init__()
    self.match = None

  def setIgnoreAromatization(self, v):
    self.match = BondMatchOrderMatrix(v)

  def __call__(self, p, mol1, bond1, mol2, bond2):
    b1 = mol1.GetBondWithIdx(bond1)
    b2 = mol2.GetBondWithIdx(bond2)
    t1 = b1.GetBondType()
    t2 = b2.GetBondType()
    if self.match.isEqual(t1, t2):
      if (p.MatchStereo and not self.CheckBondStereo(p, mol1, bond1, mol2, bond2)):
        return False
      if p.RingMatchesRingOnly:
        return self.CheckBondRingMatch(p, mol1, bond1, mol2, bond2)
      return True
    return False


class ProgressCallbackCallbackIsInt(rdFMCS.MCSProgress):
  __call__ = 1


class ProgressCallbackNoCallback(rdFMCS.MCSProgress):
  pass


class ProgressCallback(rdFMCS.MCSProgress):

  def __init__(self, parent):
    super().__init__()
    self.parent = parent
    self.callCount = 0

  def __call__(self, stat, params):
    self.callCount += 1
    self.parent.assertTrue(isinstance(stat, rdFMCS.MCSProgressData))
    self.parent.assertTrue(hasattr(stat, "numAtoms"))
    self.parent.assertTrue(isinstance(stat.numAtoms, int))
    self.parent.assertTrue(hasattr(stat, "numBonds"))
    self.parent.assertTrue(isinstance(stat.numBonds, int))
    self.parent.assertTrue(hasattr(stat, "seedProcessed"))
    self.parent.assertTrue(isinstance(stat.seedProcessed, int))
    self.parent.assertTrue(isinstance(params, rdFMCS.MCSParameters))
    self.parent.assertTrue(isinstance(params.AtomTyper, rdFMCS.MCSAtomCompare))
    self.parent.assertTrue(isinstance(params.BondTyper, rdFMCS.BondCompare))
    self.parent.assertEqual(params.ProgressCallback, self)
    return (self.callCount < 3)


class Test21AcceptanceCallbackCallbackIsInt(rdFMCS.MCSAcceptance):
  __call__ = 1


class Test21AcceptanceCallbackNoCallback(rdFMCS.MCSAcceptance):
  pass


class Test21AcceptanceCallback(rdFMCS.MCSAcceptance):

  def __init__(self, parent):
    super().__init__()
    self.parent = parent

  def __call__(self, query, target, atom_idx_match, bond_idx_match, params):
    self.parent.assertTrue(isinstance(query, Chem.Mol))
    self.parent.assertTrue(isinstance(target, Chem.Mol))
    self.parent.assertGreater(len(atom_idx_match), 0)
    self.parent.assertTrue(len(atom_idx_match) == 1 or len(bond_idx_match) > 0)
    self.parent.assertTrue(isinstance(params, rdFMCS.MCSParameters))
    self.parent.assertTrue(isinstance(params.AtomTyper, rdFMCS.MCSAtomCompare))
    self.parent.assertTrue(isinstance(params.BondTyper, rdFMCS.BondCompare))
    self.parent.assertEqual(params.ShouldAcceptMCS, self)
    for query_atom_idx, target_atom_idx in atom_idx_match:
      if query.GetAtomWithIdx(query_atom_idx).GetAtomicNum() == 0:
        return True 
      if target.GetAtomWithIdx(target_atom_idx).GetAtomicNum() == 0:
        return True
    for query_bond_idx, target_bond_idx in bond_idx_match:
      self.parent.assertIsNotNone(query.GetBondWithIdx(query_bond_idx))
      self.parent.assertIsNotNone(target.GetBondWithIdx(target_bond_idx))
    return False


class Test21AtomCompare(rdFMCS.MCSAtomCompare):

  def __init__(self):
    super().__init__()

  def __call__(self, p, mol1, atom1, mol2, atom2):
    a1 = mol1.GetAtomWithIdx(atom1)
    a2 = mol2.GetAtomWithIdx(atom2)
    a1_is_dummy = (a1.GetAtomicNum() == 0)
    a2_is_dummy = (a2.GetAtomicNum() == 0)
    if a1_is_dummy ^ a2_is_dummy:
      atoms = (a1, a2)
      dummy_atom_idx = 0 if a1_is_dummy else 1
      other_atom_idx = 1 - dummy_atom_idx
      return atoms[dummy_atom_idx].GetDegree() == 1 and atoms[other_atom_idx].GetDegree() > 1
    if a1.GetAtomicNum() != a2.GetAtomicNum():
      return False
    return self.CheckAtomRingMatch(p, mol1, atom1, mol2, atom2)


class Test21ProgressCallback(rdFMCS.MCSProgress):

  def __init__(self, parent):
    super().__init__()
    self.parent = parent
    self.callCount = 0

  def __call__(self, stat, params):
    self.callCount += 1
    self.parent.assertTrue(isinstance(stat, rdFMCS.MCSProgressData))
    self.parent.assertTrue(hasattr(stat, "numAtoms"))
    self.parent.assertTrue(isinstance(stat.numAtoms, int))
    self.parent.assertTrue(hasattr(stat, "numBonds"))
    self.parent.assertTrue(isinstance(stat.numBonds, int))
    self.parent.assertTrue(hasattr(stat, "seedProcessed"))
    self.parent.assertTrue(isinstance(stat.seedProcessed, int))
    self.parent.assertTrue(isinstance(params, rdFMCS.MCSParameters))
    self.parent.assertTrue(isinstance(params.AtomTyper, rdFMCS.MCSAtomCompare))
    self.parent.assertTrue(isinstance(params.BondTyper, rdFMCS.BondCompare))
    self.parent.assertEqual(params.ProgressCallback, self)
    return True


class Test21FinalMatchCheckCallbackCallbackIsInt(rdFMCS.MCSFinalMatchCheck):
  __call__ = 1


class Test21FinalMatchCheckCallbackNoCallback(rdFMCS.MCSFinalMatchCheck):
  pass


class Test21FinalMatchCheckCallback(rdFMCS.MCSFinalMatchCheck):

  def __init__(self, parent):
    super().__init__()
    self.parent = parent
    self.callCount = 0

  def __call__(self, mol1, mol2, atom_idx_match, bond_idx_match, params):
    self.callCount += 1
    self.parent.assertTrue(isinstance(mol1, Chem.Mol))
    self.parent.assertTrue(isinstance(mol2, Chem.Mol))
    self.parent.assertGreater(len(atom_idx_match), 0)
    self.parent.assertGreater(len(bond_idx_match), 0)
    self.parent.assertTrue(isinstance(params, rdFMCS.MCSParameters))
    self.parent.assertTrue(isinstance(params.AtomTyper, rdFMCS.MCSAtomCompare))
    self.parent.assertTrue(isinstance(params.BondTyper, rdFMCS.BondCompare))
    self.parent.assertEqual(params.FinalMatchChecker, self)
    return True


class Common:

  @staticmethod
  def getParams(**kwargs):
    params = rdFMCS.MCSParameters()
    for kw in ("AtomTyper", "BondTyper"):
      v = kwargs.get(kw, None)
      if v is not None:
        v_instance = v()
        setattr(params, kw, v_instance)
    return params

  @staticmethod
  def test1(self, **kwargs):
    smis = (
      "Cc1nc(CN(C(C)c2ncccc2)CCCCN)ccc1 CHEMBL1682991",  # -- QUERY
      "Cc1ccc(CN(C(C)c2ccccn2)CCCCN)nc1 CHEMBL1682990",
      "Cc1cccnc1CN(C(C)c1ccccn1)CCCCN CHEMBL1682998",
      "CC(N(CCCCN)Cc1c(N)cccn1)c1ccccn1 CHEMBL1682987",
      "Cc1cc(C)c(CN(C(C)c2ccccn2)CCCCN)nc1 CHEMBL1682992",
      "Cc1cc(C(C)N(CCCCN)Cc2c(C)cccn2)ncc1 CHEMBL1682993",
      "Cc1nc(C(C)N(CCCCN)Cc2nc3c([nH]2)cccc3)ccc1 CHEMBL1682878",
      "CC(c1ncccc1)N(CCCCN)Cc1nc2c([nH]1)cccc2 CHEMBL1682867",
      "CC(N(CCCCN)Cc1c(C(C)(C)C)cccn1)c1ccccn1 CHEMBL1682989",
      "CC(N(CCCCN)Cc1c(C(F)(F)F)cccn1)c1ccccn1 CHEMBL1682988",
    )
    ms = [Chem.MolFromSmiles(x.split()[0]) for x in smis]
    qm = ms[0]
    ms = ms[1:]
    if kwargs:
      params = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numBonds, 21)
    self.assertEqual(mcs.numAtoms, 21)
    self.assertEqual(
      mcs.smartsString,
      '[#6](:[#6]:[#6]):[#6]:[#7]:[#6]-[#6]-[#7](-[#6](-[#6])-[#6]1:[#6]:[#6]:[#6]:[#6]:[#7]:1)-[#6]-[#6]-[#6]-[#6]-[#7]'
    )
    qm = Chem.MolFromSmarts(mcs.smartsString)
    self.assertTrue(qm is not None)
    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))

    if kwargs:
      params = Common.getParams(**kwargs)
      params.Threshold = 0.8
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, threshold=0.8)
    self.assertEqual(mcs.numBonds, 21)
    self.assertEqual(mcs.numAtoms, 21)
    self.assertEqual(
      mcs.smartsString,
      '[#6](:[#6]:[#6]):[#6]:[#7]:[#6]-[#6]-[#7](-[#6](-[#6])-[#6]1:[#6]:[#6]:[#6]:[#6]:[#7]:1)-[#6]-[#6]-[#6]-[#6]-[#7]'
    )
    qm = Chem.MolFromSmarts(mcs.smartsString)
    self.assertTrue(qm is not None)
    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))

  def test2(self, **kwargs):
    smis = (
      "CHEMBL122452 CN(CCCN(C)CCc1ccccc1)CCOC(c1ccccc1)c1ccccc1",
      "CHEMBL123252 CN(CCCc1ccccc1)CCCN(C)CCOC(c1ccccc1)c1ccccc1",
      "CHEMBL121611 Fc1ccc(C(OCCNCCCNCCc2ccccc2)c2ccc(F)cc2)cc1",
      "CHEMBL121050 O=C(Cc1ccccc1)NCCCCNCCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL333667 O=C(Cc1ccccc1)NCCNCCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL121486 O=C(Cc1ccc(Br)cc1)NC=CNCCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL123830 O=C(Cc1ccc(F)cc1)NCCNCCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL420900 O=C(Cc1ccccc1)NCCCNCCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL121460 CN(CCOC(c1ccc(F)cc1)c1ccc(F)cc1)CCN(C)CCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL120901 COC(=O)C1C2CCC(CC1C(=O)Oc1ccccc1)N2C",
      "CHEMBL122859 O=C1CN(CCc2ccccc2)CCN1CCOC(c1ccc(F)cc1)c1ccc(F)cc1",
      "CHEMBL121027 CN(CCOC(c1ccccc1)c1ccccc1)CCN(C)CCc1ccc(F)cc1",
    )

    ms = [Chem.MolFromSmiles(x.split()[1]) for x in smis]
    qm = ms[0]
    ms = ms[1:]
    if kwargs:
      params = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numBonds, 9)
    self.assertEqual(mcs.numAtoms, 10)
    qm = Chem.MolFromSmarts(mcs.smartsString)
    self.assertTrue(qm is not None)
    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))
    # smarts too hard to canonicalize this
    # self.assertEqual(mcs.smartsString,'[#6]-,:[#6]-,:[#6]-,:[#6]-,:[#6](-[#6]-[#8]-[#6]:,-[#6])-,:[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.Threshold = 0.8
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, threshold=0.8)
    self.assertEqual(mcs.numBonds, 20)
    self.assertEqual(mcs.numAtoms, 19)
    qm = Chem.MolFromSmarts(mcs.smartsString)
    self.assertTrue(qm is not None)
    nHits = 0
    for m in ms:
      if m.HasSubstructMatch(qm):
        nHits += 1
    self.assertTrue(nHits >= int(0.8 * len(smis)))
    # smarts too hard to canonicalize this
    # self.assertEqual(mcs.smartsString,'[#6]1:[#6]:[#6]:[#6](:[#6]:[#6]:1)-[#6](-[#8]-[#6]-[#6]-[#7]-[#6]-[#6])-[#6]2:[#6]:[#6]:[#6]:[#6]:[#6]:2')

  def test3IsotopeMatch(self, **kwargs):
    smis = (
      "CC[14NH2]",
      "CC[14CH3]",
    )

    ms = [Chem.MolFromSmiles(x) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numBonds, 1)
    self.assertEqual(mcs.numAtoms, 2)
    qm = Chem.MolFromSmarts(mcs.smartsString)

    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomTyper = CompareIsotopes()
      params.AtomCompareParameters.MatchIsotope = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, atomCompare=rdFMCS.AtomCompare.CompareIsotopes)
    self.assertEqual(mcs.numBonds, 2)
    self.assertEqual(mcs.numAtoms, 3)
    qm = Chem.MolFromSmarts(mcs.smartsString)

    self.assertTrue(Chem.MolFromSmiles('CC[14CH3]').HasSubstructMatch(qm))
    self.assertFalse(Chem.MolFromSmiles('CC[13CH3]').HasSubstructMatch(qm))
    self.assertTrue(Chem.MolFromSmiles('OO[14CH3]').HasSubstructMatch(qm))
    self.assertFalse(Chem.MolFromSmiles('O[13CH2][14CH3]').HasSubstructMatch(qm))

  def test4RingMatches(self, **kwargs):
    smis = ['CCCCC', 'CCC1CCCCC1']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numBonds, 4)
    self.assertEqual(mcs.numAtoms, 5)
    self.assertEqual(mcs.smartsString, '[#6]-[#6]-[#6]-[#6]-[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.BondCompareParameters.CompleteRingsOnly = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, completeRingsOnly=True)
    self.assertEqual(mcs.numBonds, 2)
    self.assertEqual(mcs.numAtoms, 3)
    self.assertEqual(mcs.smartsString, '[#6]-&!@[#6]-&!@[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.BondCompareParameters.CompleteRingsOnly = True
      params.BondCompareParameters.MatchFusedRings = True
      params.BondCompareParameters.MatchFusedRingsStrict = False
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, completeRingsOnly=True,
                           ringCompare=rdFMCS.RingCompare.PermissiveRingFusion)
    self.assertEqual(mcs.numBonds, 2)
    self.assertEqual(mcs.numAtoms, 3)
    self.assertEqual(mcs.smartsString, '[#6]-&!@[#6]-&!@[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.BondCompareParameters.CompleteRingsOnly = True
      params.BondCompareParameters.MatchFusedRings = True
      params.BondCompareParameters.MatchFusedRingsStrict = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, completeRingsOnly=True,
                           ringCompare=rdFMCS.RingCompare.StrictRingFusion)
    self.assertEqual(mcs.numBonds, 2)
    self.assertEqual(mcs.numAtoms, 3)
    self.assertEqual(mcs.smartsString, '[#6]-&!@[#6]-&!@[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.RingMatchesRingOnly = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, ringMatchesRingOnly=True)
    self.assertEqual(mcs.numBonds, 1)
    self.assertEqual(mcs.numAtoms, 2)
    self.assertEqual(mcs.smartsString, '[#6&!R]-&!@[#6&!R]')

    smis = ['CC1CCC1', 'CCC1CCCCC1']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numBonds, 4)
    self.assertEqual(mcs.numAtoms, 5)
    self.assertEqual(mcs.smartsString, '[#6]-[#6](-[#6]-[#6])-[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.BondCompareParameters.CompleteRingsOnly = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, completeRingsOnly=True)
    self.assertEqual(mcs.numBonds, 1)
    self.assertEqual(mcs.numAtoms, 2)
    self.assertEqual(mcs.smartsString, '[#6]-&!@[#6]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.CompleteRingsOnly = True
      params.BondCompareParameters.RingMatchesRingOnly = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, ringMatchesRingOnly=True, completeRingsOnly=True)
    self.assertEqual(mcs.numBonds, 1)
    self.assertEqual(mcs.numAtoms, 2)
    self.assertEqual(mcs.smartsString, '[#6&!R]-&!@[#6&R]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.CompleteRingsOnly = True
      params.BondCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.MatchFusedRings = True
      params.BondCompareParameters.MatchFusedRingsStrict = False
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, ringMatchesRingOnly=True, completeRingsOnly=True,
                           ringCompare=rdFMCS.RingCompare.PermissiveRingFusion)
    self.assertEqual(mcs.numBonds, 1)
    self.assertEqual(mcs.numAtoms, 2)
    self.assertEqual(mcs.smartsString, '[#6&!R]-&!@[#6&R]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.CompleteRingsOnly = True
      params.BondCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.MatchFusedRings = True
      params.BondCompareParameters.MatchFusedRingsStrict = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, ringMatchesRingOnly=True, completeRingsOnly=True,
                           ringCompare=rdFMCS.RingCompare.StrictRingFusion)
    self.assertEqual(mcs.numBonds, 1)
    self.assertEqual(mcs.numAtoms, 2)
    self.assertEqual(mcs.smartsString, '[#6&!R]-&!@[#6&R]')

    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.RingMatchesRingOnly = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, ringMatchesRingOnly=True)
    self.assertEqual(mcs.numBonds, 4)
    self.assertEqual(mcs.numAtoms, 5)
    self.assertEqual(mcs.smartsString, '[#6&!R]-&!@[#6&R](-&@[#6&R]-&@[#6&R])-&@[#6&R]')

  def test5AnyMatch(self, **kwargs):
    smis = ('c1ccccc1C', 'c1ccccc1O', 'c1ccccc1Cl')
    ms = [Chem.MolFromSmiles(x) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomTyper = CompareAny()
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, atomCompare=rdFMCS.AtomCompare.CompareAny)
    self.assertEqual(mcs.numBonds, 7)
    self.assertEqual(mcs.numAtoms, 7)
    qm = Chem.MolFromSmarts(mcs.smartsString)

    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))

    smis = ('c1cccnc1C', 'c1cnncc1O', 'c1cccnc1Cl')
    ms = [Chem.MolFromSmiles(x) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomTyper = CompareAny()
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, atomCompare=rdFMCS.AtomCompare.CompareAny)
    self.assertEqual(mcs.numBonds, 7)
    self.assertEqual(mcs.numAtoms, 7)
    qm = Chem.MolFromSmarts(mcs.smartsString)

    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))

  def testAtomCompareAnyHeavyAtom(self, **kwargs):
    # H matches H, O matches C
    smis = ('[H]c1ccccc1C', '[H]c1ccccc1O')
    ms = [Chem.MolFromSmiles(x, sanitize=False) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomTyper = CompareAnyHeavyAtom()
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, atomCompare=rdFMCS.AtomCompare.CompareAnyHeavyAtom)
    self.assertEqual(mcs.numBonds, 8)
    self.assertEqual(mcs.numAtoms, 8)
    qm = Chem.MolFromSmarts(mcs.smartsString)

    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))

  def testAtomCompareAnyHeavyAtom1(self, **kwargs):
    # O matches C, H does not match O
    smis = ('[H]c1ccccc1C', 'Oc1ccccc1O')
    ms = [Chem.MolFromSmiles(x, sanitize=False) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomTyper = CompareAnyHeavyAtom()
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, atomCompare=rdFMCS.AtomCompare.CompareAnyHeavyAtom)
    self.assertEqual(mcs.numBonds, 7)
    self.assertEqual(mcs.numAtoms, 7)
    qm = Chem.MolFromSmarts(mcs.smartsString)

    for m in ms:
      self.assertTrue(m.HasSubstructMatch(qm))

  def test6MatchValences(self, **kwargs):
    ms = (Chem.MolFromSmiles('NC1OC1'), Chem.MolFromSmiles('C1OC1[N+](=O)[O-]'))
    if kwargs:
      params = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numBonds, 4)
    self.assertEqual(mcs.numAtoms, 4)
    if kwargs:
      params = Common.getParams(**kwargs)
      params.AtomCompareParameters.MatchValences = True
      mcs = rdFMCS.FindMCS(ms, params)
    else:
      mcs = rdFMCS.FindMCS(ms, matchValences=True)
    self.assertEqual(mcs.numBonds, 3)
    self.assertEqual(mcs.numAtoms, 3)

  def test7Seed(self, **kwargs):
    smis = ['C1CCC1CC1CC1', 'C1CCC1OC1CC1', 'C1CCC1NC1CC1', 'C1CCC1SC1CC1']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    if kwargs:
      params = Common.getParams(**kwargs)
      r = rdFMCS.FindMCS(ms, params)
    else:
      r = rdFMCS.FindMCS(ms)
    self.assertEqual(r.smartsString, "[#6]1-[#6]-[#6]-[#6]-1")
    if kwargs:
      params = Common.getParams(**kwargs)
      params.InitialSeed = 'C1CC1'
      r = rdFMCS.FindMCS(ms, params)
    else:
      r = rdFMCS.FindMCS(ms, seedSmarts='C1CC1')
    self.assertEqual(r.smartsString, "[#6]1-[#6]-[#6]-1")
    if kwargs:
      params = Common.getParams(**kwargs)
      params.InitialSeed = 'C1OC1'
      r = rdFMCS.FindMCS(ms, params)
    else:
      r = rdFMCS.FindMCS(ms, seedSmarts='C1OC1')
    self.assertEqual(r.smartsString, "[#6]1-[#6]-[#6]-[#6]-1")
    self.assertEqual(r.numAtoms, 4)
    self.assertEqual(r.numBonds, 4)
    if kwargs:
      params = Common.getParams(**kwargs)
      params.InitialSeed = 'C1OC1'
      params.AtomCompareParameters.RingMatchesRingOnly = True
      params.BondCompareParameters.RingMatchesRingOnly = True
      r = rdFMCS.FindMCS(ms, params)
    else:
      r = rdFMCS.FindMCS(ms, seedSmarts='C1OC1', ringMatchesRingOnly=True)
    self.assertEqual(r.smartsString, "[#6]1-&@[#6]-&@[#6]-&@[#6]-&@1")
    self.assertEqual(r.numAtoms, 4)
    self.assertEqual(r.numBonds, 4)
    if kwargs:
      params = Common.getParams(**kwargs)
      params.InitialSeed = 'C1OC1'
      params.BondCompareParameters.CompleteRingsOnly = True
      r = rdFMCS.FindMCS(ms, params)
    else:
      r = rdFMCS.FindMCS(ms, seedSmarts='C1OC1', completeRingsOnly=True)
    self.assertEqual(r.smartsString, "[#6]1-&@[#6]-&@[#6]-&@[#6]-&@1")
    self.assertEqual(r.numAtoms, 4)
    self.assertEqual(r.numBonds, 4)

  def test8MatchParams(self, **kwargs):
    smis = ("CCC1NC1", "CCC1N(C)C1", "CCC1OC1")
    ms = [Chem.MolFromSmiles(x) for x in smis]

    if kwargs:
      ps = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, ps)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numAtoms, 4)

    ps = Common.getParams(**kwargs)
    ps.BondCompareParameters.CompleteRingsOnly = True
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 3)

    ps = Common.getParams(**kwargs)
    ps.BondCompareParameters.CompleteRingsOnly = True
    ps.BondCompareParameters.MatchFusedRings = True
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 3)

    ps = Common.getParams(**kwargs)
    ps.BondCompareParameters.CompleteRingsOnly = True
    ps.BondCompareParameters.MatchFusedRingsStrict = True
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 3)

    ps = Common.getParams(**kwargs)
    if kwargs:
      ps.AtomTyper = CompareAny()
    else:
      ps.AtomTyper = rdFMCS.AtomCompare.CompareAny
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 5)

  def test9MatchCharge(self, **kwargs):
    smis = ("CCNC", "CCN(C)C", "CC[N+](C)C")
    ms = [Chem.MolFromSmiles(x) for x in smis]

    if kwargs:
      ps = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, ps)
    else:
      mcs = rdFMCS.FindMCS(ms)

    self.assertEqual(mcs.numAtoms, 4)

    ps = Common.getParams(**kwargs)
    ps.AtomCompareParameters.MatchFormalCharge = True
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 2)

  def test10MatchChargeAndParams(self, **kwargs):
    smis = ("CCNC", "CCN(C)C", "CC[N+](C)C", "CC[C+](C)C")
    ms = [Chem.MolFromSmiles(x) for x in smis]

    if kwargs:
      ps = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, ps)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numAtoms, 2)

    ps = Common.getParams(**kwargs)
    if kwargs:
      ps.AtomTyper = CompareAny()
    else:
      ps.AtomTyper = rdFMCS.AtomCompare.CompareAny
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 4)

    ps = Common.getParams(**kwargs)
    ps.AtomCompareParameters.MatchFormalCharge = True
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 2)

  def test11Github2034(self, **kwargs):
    smis = ("C1CC1N2CC2", "C1CC1N")
    ms = [Chem.MolFromSmiles(x) for x in smis]

    if kwargs:
      ps = Common.getParams(**kwargs)
      mcs = rdFMCS.FindMCS(ms, ps)
    else:
      mcs = rdFMCS.FindMCS(ms)
    self.assertEqual(mcs.numAtoms, 4)
    self.assertEqual(mcs.numBonds, 4)

    if kwargs:
      ps = Common.getParams(**kwargs)
      ps.AtomCompareParameters.RingMatchesRingOnly = True
      ps.BondCompareParameters.RingMatchesRingOnly = True
      mcs = rdFMCS.FindMCS(ms, ps)
    else:
      mcs = rdFMCS.FindMCS(ms, ringMatchesRingOnly=True)
    self.assertEqual(mcs.numAtoms, 3)
    self.assertEqual(mcs.numBonds, 3)

    ps = Common.getParams(**kwargs)
    ps.AtomCompareParameters.RingMatchesRingOnly = True
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 3)
    self.assertEqual(mcs.numBonds, 3)

  def test19MCS3d(self, **kwargs):
    block1 = """
     RDKit          3D

 17 17  0  0  0  0  0  0  0  0999 V2000
    0.1592    0.8577    0.8639 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.9090    0.9385   -0.2218 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.1866   -0.4482   -0.7339 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.0705   -1.1345   -1.1348 O   0  0  0  0  0  0  0  0  0  0  0  0
    0.8460   -1.2511   -0.0884 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.3508    0.1733    0.1788 C   0  0  1  0  0  0  0  0  0  0  0  0
    1.6294    0.7269   -1.0491 O   0  0  0  0  0  0  0  0  0  0  0  0
   -0.2088    0.1739    1.6399 H   0  0  0  0  0  0  0  0  0  0  0  0
    0.4334    1.8588    1.2198 H   0  0  0  0  0  0  0  0  0  0  0  0
   -1.8657    1.3295    0.1967 H   0  0  0  0  0  0  0  0  0  0  0  0
   -0.5185    1.5822   -1.0340 H   0  0  0  0  0  0  0  0  0  0  0  0
   -1.7155   -1.0290    0.0396 H   0  0  0  0  0  0  0  0  0  0  0  0
   -1.8809   -0.3833   -1.6049 H   0  0  0  0  0  0  0  0  0  0  0  0
    1.6828   -1.9159   -0.3875 H   0  0  0  0  0  0  0  0  0  0  0  0
    0.3276   -1.6370    0.8016 H   0  0  0  0  0  0  0  0  0  0  0  0
    2.2158    0.0912    0.8546 H   0  0  0  0  0  0  0  0  0  0  0  0
    1.6002    1.6926   -1.1014 H   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  3  4  1  0
  4  5  1  0
  5  6  1  0
  6  7  1  0
  6  1  1  0
  1  8  1  0
  1  9  1  0
  2 10  1  0
  2 11  1  0
  3 12  1  0
  3 13  1  0
  5 14  1  0
  5 15  1  0
  6 16  1  1
  7 17  1  0
M  END


"""
    block2 = """
     RDKit          3D

 17 17  0  0  0  0  0  0  0  0999 V2000
    0.1592    0.8577    0.8639 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.9090    0.9385   -0.2218 C   0  0  0  0  0  0  0  0  0  0  0  0
   -1.1866   -0.4482   -0.7339 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.0705   -1.1345   -1.1348 O   0  0  0  0  0  0  0  0  0  0  0  0
    0.8460   -1.2511   -0.0884 C   0  0  0  0  0  0  0  0  0  0  0  0
    1.3508    0.1733    0.1788 C   0  0  2  0  0  0  0  0  0  0  0  0
    2.4771    0.1924    0.9509 O   0  0  0  0  0  0  0  0  0  0  0  0
   -0.1638    0.2755    1.7318 H   0  0  0  0  0  0  0  0  0  0  0  0
    0.4849    1.8825    1.0902 H   0  0  0  0  0  0  0  0  0  0  0  0
   -0.4598    1.5686   -1.0290 H   0  0  0  0  0  0  0  0  0  0  0  0
   -1.8337    1.3595    0.1902 H   0  0  0  0  0  0  0  0  0  0  0  0
   -1.8991   -0.3820   -1.5700 H   0  0  0  0  0  0  0  0  0  0  0  0
   -1.6394   -1.0265    0.1178 H   0  0  0  0  0  0  0  0  0  0  0  0
    1.6816   -1.9167   -0.3664 H   0  0  0  0  0  0  0  0  0  0  0  0
    0.3727   -1.5979    0.8445 H   0  0  0  0  0  0  0  0  0  0  0  0
    1.4834    0.6899   -0.7827 H   0  0  0  0  0  0  0  0  0  0  0  0
    2.2370    0.0336    1.8861 H   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0
  2  3  1  0
  3  4  1  0
  4  5  1  0
  5  6  1  0
  6  7  1  0
  6  1  1  0
  1  8  1  0
  1  9  1  0
  2 10  1  0
  2 11  1  0
  3 12  1  0
  3 13  1  0
  5 14  1  0
  5 15  1  0
  6 16  1  6
  7 17  1  0
M  END


"""
    m1 = Chem.MolFromMolBlock(block1, removeHs=False)
    m2 = Chem.MolFromMolBlock(block2, removeHs=False)
    ps = Common.getParams(**kwargs)
    ps.AtomCompareParameters.MaxDistance = 1.0
    mcs = rdFMCS.FindMCS([m1, m2], ps)
    self.assertEqual(mcs.numAtoms, 14)
    self.assertEqual(mcs.numBonds, 14)


class TestCase(unittest.TestCase):

  def setUp(self):
    pass

  def test1(self):
    Common.test1(self)

  def test1PythonImpl(self):
    Common.test1(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test2(self):
    Common.test2(self)

  def test2PythonImpl(self):
    Common.test2(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test2PythonImplAtomTyperOnly(self):
    Common.test2(self, AtomTyper=CompareElements)

  def test2PythonImplBondTyperOnly(self):
    Common.test2(self, BondTyper=CompareOrder)

  def test3IsotopeMatch(self):
    Common.test3IsotopeMatch(self)

  def test3IsotopeMatchPythonImpl(self):
    Common.test3IsotopeMatch(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test3IsotopeMatchPythonImplAtomTyperOnly(self):
    Common.test3IsotopeMatch(self, AtomTyper=CompareElements)

  def test3IsotopeMatchPythonImplBondTyperOnly(self):
    Common.test3IsotopeMatch(self, BondTyper=CompareOrder)

  def test4RingMatches(self):
    Common.test4RingMatches(self)

  def test4RingMatchesPythonImpl(self):
    Common.test4RingMatches(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test4RingMatchesPythonImplAtomTyperOnly(self):
    Common.test4RingMatches(self, AtomTyper=CompareElements)

  def test4RingMatchesPythonImplBondTyperOnly(self):
    Common.test4RingMatches(self, BondTyper=CompareOrder)

  def test5AnyMatch(self):
    Common.test5AnyMatch(self)

  def test5AnyMatchPythonImpl(self):
    Common.test5AnyMatch(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test5AnyMatchPythonImplAtomTyperOnly(self):
    Common.test5AnyMatch(self, AtomTyper=CompareElements)

  def test5AnyMatchPythonImplBondTyperOnly(self):
    Common.test5AnyMatch(self, BondTyper=CompareOrder)

  def testAtomCompareAnyHeavyAtom(self):
    Common.testAtomCompareAnyHeavyAtom(self)

  def testAtomCompareAnyHeavyAtomPythonImpl(self):
    Common.testAtomCompareAnyHeavyAtom(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def testAtomCompareAnyHeavyAtomPythonImplAtomTyperOnly(self):
    Common.testAtomCompareAnyHeavyAtom(self, AtomTyper=CompareElements)

  def testAtomCompareAnyHeavyAtomPythonImplBondTyperOnly(self):
    Common.testAtomCompareAnyHeavyAtom(self, BondTyper=CompareOrder)

  def testAtomCompareAnyHeavyAtom1(self):
    Common.testAtomCompareAnyHeavyAtom1(self)

  def testAtomCompareAnyHeavyAtom1PythonImpl(self):
    Common.testAtomCompareAnyHeavyAtom1(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def testAtomCompareAnyHeavyAtom1PythonImplAtomTyperOnly(self):
    Common.testAtomCompareAnyHeavyAtom1(self, AtomTyper=CompareElements)

  def testAtomCompareAnyHeavyAtom1PythonImplBondTyperOnly(self):
    Common.testAtomCompareAnyHeavyAtom1(self, BondTyper=CompareOrder)

  def test6MatchValences(self):
    Common.test6MatchValences(self)

  def test6MatchValencesPythonImpl(self):
    Common.test6MatchValences(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test6MatchValencesPythonImplAtomTyperOnly(self):
    Common.test6MatchValences(self, AtomTyper=CompareElements)

  def test6MatchValencesPythonImplBondTyperOnly(self):
    Common.test6MatchValences(self, BondTyper=CompareOrder)

  def test7Seed(self):
    Common.test7Seed(self)

  def test7SeedPythonImpl(self):
    Common.test7Seed(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test7SeedPythonImplAtomTyperOnly(self):
    Common.test7Seed(self, AtomTyper=CompareElements)

  def test7SeedPythonImplBondTyperOnly(self):
    Common.test7Seed(self, BondTyper=CompareOrder)

  def test8MatchParams(self):
    Common.test8MatchParams(self)

  def test8MatchParamsPythonImpl(self):
    Common.test8MatchParams(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test8MatchParamsPythonImplAtomTyperOnly(self):
    Common.test8MatchParams(self, AtomTyper=CompareElements)

  def test8MatchParamsPythonImplBondTyperOnly(self):
    Common.test8MatchParams(self, BondTyper=CompareOrder)

  def test9MatchCharge(self):
    Common.test9MatchCharge(self)

  def test9MatchChargePythonImpl(self):
    Common.test9MatchCharge(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test9MatchChargePythonImplAtomTyperOnly(self):
    Common.test9MatchCharge(self, AtomTyper=CompareElements)

  def test9MatchChargePythonImplBondTyperOnly(self):
    Common.test9MatchCharge(self, BondTyper=CompareOrder)

  def test10MatchChargeAndParams(self):
    Common.test10MatchChargeAndParams(self)

  def test10MatchChargeAndParamsPythonImpl(self):
    Common.test10MatchChargeAndParams(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test10MatchChargeAndParamsPythonImplAtomTyperOnly(self):
    Common.test10MatchChargeAndParams(self, AtomTyper=CompareElements)

  def test10MatchChargeAndParamsPythonImplBondTyperOnly(self):
    Common.test10MatchChargeAndParams(self, BondTyper=CompareOrder)

  def test11Github2034(self):
    Common.test11Github2034(self)

  def test11Github2034PythonImpl(self):
    Common.test11Github2034(self, AtomTyper=CompareElements, BondTyper=CompareOrder)

  def test11Github2034PythonImplAtomTyperOnly(self):
    Common.test11Github2034(self, AtomTyper=CompareElements)

  def test11Github2034PythonImplBondTyperOnly(self):
    Common.test11Github2034(self, BondTyper=CompareOrder)

  def test12MCSAtomCompareExceptions(self):
    ps = rdFMCS.MCSParameters()
    smis = ['CCCCC', 'CCC1CCCCC1']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    self.assertRaises(TypeError, lambda ps: setattr(ps, "AtomTyper", AtomCompareCompareIsInt()))
    self.assertRaises(TypeError, lambda ps: setattr(ps, "AtomTyper", AtomCompareNoCompare()))

  def test13MCSAtomCompareUserData(self):
    smis = ['CCOCCOC', 'CCNCCCC']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    ps = rdFMCS.MCSParameters()
    ps.AtomTyper = AtomCompareUserData()
    ps.AtomTyper.setMatchAnyHet(False)
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 2)
    ps.AtomTyper.setMatchAnyHet(True)
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 5)

  def test14MCSBondCompareExceptions(self):
    ps = rdFMCS.MCSParameters()
    smis = ['CCCCC', 'CCC1CCCCC1']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    self.assertRaises(TypeError, lambda ps: setattr(ps, "BondTyper", BondCompareCompareIsInt()))
    self.assertRaises(TypeError, lambda ps: setattr(ps, "BondTyper", BondCompareNoCompare()))

  def test15MCSBondCompareUserData(self):
    smis = ['C1CC=CCC1', 'c1ccccc1']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    ps = rdFMCS.MCSParameters()
    ps.BondTyper = BondCompareUserData()
    ps.BondCompareParameters.CompleteRingsOnly = True
    ps.BondTyper.setIgnoreAromatization(False)
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 0)
    ps.BondTyper.setIgnoreAromatization(True)
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertEqual(mcs.numAtoms, 6)

  def test16MCSProgressCallbackExceptions(self):
    ps = rdFMCS.MCSParameters()
    smis = ['CCCC(C)CC(CC)CC', 'OC(N)CC(C)CC(CC)CC']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    self.assertRaises(TypeError,
                      lambda ps: setattr(ps, "ProgressCallback", ProgressCallbackCallbackIsInt()))
    self.assertRaises(TypeError,
                      lambda ps: setattr(ps, "ProgressCallback", ProgressCallbackNoCallback()))

  def test17MCSProgressCallbackCancel(self):
    ps = rdFMCS.MCSParameters()
    smis = ['CCCC(C)CC(CC)CC', 'OC(N)CC(C)CC(CC)CC']
    ms = [Chem.MolFromSmiles(x) for x in smis]
    ps.AtomTyper = CompareElements()
    ps.ProgressCallback = ProgressCallback(self)
    mcs = rdFMCS.FindMCS(ms, ps)
    self.assertTrue(mcs.canceled)
    self.assertEqual(ps.ProgressCallback.callCount, 3)

  def test18GitHub3693(self):
    mols = [
      Chem.MolFromSmiles(smi)
      for smi in ["Nc1ccc(O)cc1c1ccc2ccccc2c1", "Oc1cnc(NC2CCC2)c(c1)c1ccc2ccccc2c1"]
    ]
    params = rdFMCS.MCSParameters()
    res = rdFMCS.FindMCS(mols, params)
    self.assertEqual(res.numAtoms, 17)
    self.assertEqual(res.numBonds, 18)
    self.assertEqual(
      res.smartsString,
      "[#7]-,:[#6]:[#6](:[#6]:[#6](:[#6])-[#8])-[#6]1:[#6]:[#6]:[#6]2:[#6](:[#6]:1):[#6]:[#6]:[#6]:[#6]:2"
    )

    params = rdFMCS.MCSParameters()
    params.BondCompareParameters.CompleteRingsOnly = True
    res = rdFMCS.FindMCS(mols, params)
    self.assertEqual(res.numAtoms, 11)
    self.assertEqual(res.numBonds, 12)
    self.assertEqual(
      res.smartsString,
      "[#6]-&!@[#6]1:&@[#6]:&@[#6]:&@[#6]2:&@[#6](:&@[#6]:&@1):&@[#6]:&@[#6]:&@[#6]:&@[#6]:&@2")

    params = rdFMCS.MCSParameters()
    params.AtomCompareParameters.CompleteRingsOnly = True
    params.BondCompareParameters.CompleteRingsOnly = True
    res = rdFMCS.FindMCS(mols, params)
    self.assertEqual(res.numAtoms, 10)
    self.assertEqual(res.numBonds, 11)
    self.assertEqual(
      res.smartsString,
      "[#6]1:&@[#6]:&@[#6]:&@[#6]2:&@[#6](:&@[#6]:&@1):&@[#6]:&@[#6]:&@[#6]:&@[#6]:&@2"
    )

    params = rdFMCS.MCSParameters()
    params.AtomCompareParameters.CompleteRingsOnly = True
    # this will automatically be set to True
    params.BondCompareParameters.CompleteRingsOnly = False
    res = rdFMCS.FindMCS(mols, params)
    self.assertEqual(res.numAtoms, 10)
    self.assertEqual(res.numBonds, 11)
    self.assertEqual(
      res.smartsString,
      "[#6]1:&@[#6]:&@[#6]:&@[#6]2:&@[#6](:&@[#6]:&@1):&@[#6]:&@[#6]:&@[#6]:&@[#6]:&@2"
    )

  def test19MCS3d(self):
    Common.test19MCS3d(self)

  def Test21AtomCompareCompleteRingsOnly(self):
    mols = [Chem.MolFromSmiles(smi) for smi in ["C1CCCC1C", "C1CCCC1C1CCCCC1"]]
    params = rdFMCS.MCSParameters()
    params.AtomCompareParameters.CompleteRingsOnly = True
    res = rdFMCS.FindMCS(mols, params)
    self.assertEqual(res.numAtoms, 5)
    self.assertEqual(res.numBonds, 5)
    self.assertEqual(res.smartsString, "[#6&R]1-&@[#6&R]-&@[#6&R]-&@[#6&R]-&@[#6&R]-&@1")

    params = rdFMCS.MCSParameters()
    params.AtomCompareParameters.CompleteRingsOnly = True
    # this will automatically be set to True
    params.BondCompareParameters.CompleteRingsOnly = False
    res = rdFMCS.FindMCS(mols, params)
    self.assertEqual(res.numAtoms, 5)
    self.assertEqual(res.numBonds, 5)
    self.assertEqual(res.smartsString, "[#6&R]1-&@[#6&R]-&@[#6&R]-&@[#6&R]-&@[#6&R]-&@1")

  def test21ShouldAcceptMCS(self):
    mols1 = [
      Chem.MolFromSmiles(smi) for smi in [
        "OC(=O)CCc1c(C)ccc2ccccc12",
        "c1(C)cc(cccc2)c2c(*)c1"
      ]
    ]
    mols2 = [
      Chem.MolFromSmiles(smi) for smi in [
        "OC(=O)C(C1CC1)Cc1c(C)ccc2ccccc12",
        "c1(C)cc(cccc2)c2c(C2CC2*)c1"
      ]
    ]
    params = rdFMCS.MCSParameters()
    params.AtomTyper = Test21AtomCompare()
    params.BondTyper = rdFMCS.BondCompare.CompareAny
    self.assertRaises(TypeError,
                      lambda params: setattr(params, "ShouldAcceptMCS", Test21AcceptanceCallbackCallbackIsInt()))
    self.assertRaises(TypeError,
                      lambda params: setattr(params, "ShouldAcceptMCS", Test21AcceptanceCallbackNoCallback()))
    self.assertRaises(TypeError,
                      lambda params: setattr(params, "FinalMatchChecker", Test21FinalMatchCheckCallbackCallbackIsInt()))
    self.assertRaises(TypeError,
                      lambda params: setattr(params, "FinalMatchChecker", Test21FinalMatchCheckCallbackNoCallback()))
    params.ShouldAcceptMCS = Test21AcceptanceCallback(self)
    params.ProgressCallback = Test21ProgressCallback(self)
    params.FinalMatchChecker = Test21FinalMatchCheckCallback(self)
    params.AtomCompareParameters.RingMatchesRingOnly = False
    params.AtomCompareParameters.CompleteRingsOnly = False
    params.BondCompareParameters.RingMatchesRingOnly = True
    params.BondCompareParameters.CompleteRingsOnly = False
    mcs = rdFMCS.FindMCS(mols1, params)
    self.assertEqual(mcs.numAtoms, 11)
    self.assertEqual(mcs.numBonds, 12)
    self.assertEqual(mcs.smartsString, "[#6]1:&@[#6]:&@[#6]2:&@[#6]:&@[#6]:&@[#6]:&@[#6]:&@[#6]:&@2:&@[#6](:&@[#6]:&@1)-&!@[#0,#6]")
    self.assertGreater(params.ProgressCallback.callCount, 0)
    self.assertGreater(params.FinalMatchChecker.callCount, 0)
    params.AtomCompareParameters.RingMatchesRingOnly = True
    mcs = rdFMCS.FindMCS(mols2, params)
    self.assertEqual(mcs.numAtoms, 4)
    self.assertEqual(mcs.numBonds, 4)
    self.assertEqual(mcs.smartsString, "[#6]1-&@[#6]-&@[#6]-&@1-&!@[#0,#6;!R]")
    self.assertGreater(params.ProgressCallback.callCount, 0)
    self.assertGreater(params.FinalMatchChecker.callCount, 0)

  def test22DegenerateMCS(self):
    para = "CC1:C:C:C(N):C:C:1"
    ortho = "CC1:C:C:C:C:C:1N"
    if 1:
      mols1 = [Chem.MolFromSmiles(smi) for smi in [
        "Nc1ccc(cc1)C-Cc1c(N)cccc1",
        "Nc1ccc(cc1)C=Cc1c(N)cccc1"]
      ]
      mols2 = [Chem.MolFromSmiles(smi) for smi in [
        "Nc1ccccc1C-Cc1ccc(N)cc1",
        "Nc1ccccc1C=Cc1ccc(N)cc1"]
      ]
      p = rdFMCS.MCSParameters()
      mcs1 = rdFMCS.FindMCS(mols1)
      self.assertEqual(mcs1.numAtoms, 8)
      self.assertEqual(mcs1.numBonds, 8)
      self.assertNotEqual(mcs1.smartsString, "")
      self.assertIsNotNone(mcs1.queryMol)
      mcs1Smiles = Chem.MolToSmiles(Chem.MolFromSmarts(mcs1.smartsString))
      self.assertEqual(mcs1Smiles, para)
      self.assertEqual(len(mcs1.degenerateSmartsQueryMolDict), 0)
      mcs2 = rdFMCS.FindMCS(mols2)
      self.assertEqual(mcs2.numAtoms, 8)
      self.assertEqual(mcs2.numBonds, 8)
      self.assertNotEqual(mcs2.smartsString, "")
      self.assertIsNotNone(mcs2.queryMol)
      mcs2Smiles = Chem.MolToSmiles(Chem.MolFromSmarts(mcs2.smartsString))
      self.assertEqual(mcs2Smiles, ortho)
      self.assertEqual(len(mcs2.degenerateSmartsQueryMolDict), 0)
      self.assertNotEqual(mcs1Smiles, mcs2Smiles)
      self.assertEqual(Chem.MolToSmiles(mols1[0]), Chem.MolToSmiles(mols2[0]))
      self.assertEqual(Chem.MolToSmiles(mols1[1]), Chem.MolToSmiles(mols2[1]))
      p.StoreAll = True
      mcs1 = rdFMCS.FindMCS(mols1, p)
      self.assertEqual(mcs1.numAtoms, 8)
      self.assertEqual(mcs1.numBonds, 8)
      self.assertEqual(mcs1.smartsString, "")
      self.assertIsNone(mcs1.queryMol)
      self.assertEqual(len(mcs1.degenerateSmartsQueryMolDict), 2)
      mcs2 = rdFMCS.FindMCS(mols2, p)
      self.assertEqual(mcs2.numAtoms, 8)
      self.assertEqual(mcs2.numBonds, 8)
      self.assertEqual(mcs2.smartsString, "")
      self.assertIsNone(mcs2.queryMol)
      self.assertEqual(len(mcs2.degenerateSmartsQueryMolDict), 2)
      degSmiles1 = sorted(Chem.MolToSmiles(Chem.MolFromSmarts(sma)) for sma in mcs1.degenerateSmartsQueryMolDict.keys())
      degSmiles2 = sorted(Chem.MolToSmiles(Chem.MolFromSmarts(sma)) for sma in mcs2.degenerateSmartsQueryMolDict.keys())
      self.assertEqual(len(degSmiles1), 2)
      self.assertEqual(degSmiles1, degSmiles2)
    if 1:
      mols = [Chem.MolFromSmiles(smi) for smi in [
        "Nc1ccc(cc1)C-C",
        "Nc1ccc(cc1)C=C"]
      ]
      p = rdFMCS.MCSParameters()
      mcs1 = rdFMCS.FindMCS(mols)
      self.assertEqual(mcs1.numAtoms, 8)
      self.assertEqual(mcs1.numBonds, 8)
      self.assertIsNotNone(mcs1.queryMol)
      self.assertNotEqual(mcs1.smartsString, "")
      self.assertEqual(Chem.MolToSmiles(Chem.MolFromSmarts(mcs1.smartsString)), para)
      self.assertEqual(len(mcs1.degenerateSmartsQueryMolDict), 0)
      p.StoreAll = True
      mcs2 = rdFMCS.FindMCS(mols, p)
      self.assertEqual(mcs2.numAtoms, 8)
      self.assertEqual(mcs2.numBonds, 8)
      self.assertIsNone(mcs2.queryMol)
      self.assertEqual(mcs2.smartsString, "")
      self.assertEqual(len(mcs2.degenerateSmartsQueryMolDict), 1)
      self.assertEqual(Chem.MolToSmiles(Chem.MolFromSmarts(tuple(mcs2.degenerateSmartsQueryMolDict.keys())[0])), para)


if __name__ == "__main__":
  unittest.main()
