//
//  Copyright (C) 2014 Novartis Institutes for BioMedical Research
//
//   @@ 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 <map>
#include <RDGeneral/utils.h>
#include <RDGeneral/Invariant.h>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/RDKitQueries.h>
#include "SubstructMatchCustom.h"

#include "../Substruct/vf2.hpp"

namespace RDKit {
namespace FMCS {

class MolMatchFinalCheckFunctor {
  const FMCS::Graph &QueryTopology;
  const FMCS::Graph &TargetTopology;
  const ROMol &d_query;
  const ROMol &d_mol;
  const MCSParameters *Parameters;

 public:
  MolMatchFinalCheckFunctor(const FMCS::Graph &query, const FMCS::Graph &target,
                            const ROMol &querySrc, const ROMol &mol  // target
                            ,
                            const MCSParameters *parameters)
      : QueryTopology(query),
        TargetTopology(target),
        d_query(querySrc),
        d_mol(mol),
        Parameters(parameters) {};

  bool operator()(const boost::detail::node_id c1[],
                  const boost::detail::node_id c2[]) const {
    if (static_cast<unsigned int>(c1[0]) >=
        boost::num_vertices(QueryTopology)) {
      return false;  // invalid index - match failed, see v2f implementation
    }
    auto compare = Parameters ? Parameters->FinalMatchChecker : nullptr;
    return compare ? compare(c1, c2, d_query, QueryTopology, d_mol,
                             TargetTopology, Parameters)
                   : true;
  }
};
//=================================================================================================
// PRECOMPUTED_TABLES_MATCH much faster in overall even with very simple compare
// functions

class AtomTableCompareFunctor {
  const FMCS::Graph &QueryTopology;
  const FMCS::Graph &TargetTopology;
  const FMCS::MatchTable &MatchTable;

 public:
  AtomTableCompareFunctor(const FMCS::Graph &query, const FMCS::Graph &target,
                          const FMCS::MatchTable &targetMatch)
      : QueryTopology(query),
        TargetTopology(target),
        MatchTable(targetMatch) {};
  bool operator()(unsigned int i, unsigned int j) const {
    return MatchTable.at(QueryTopology[i], TargetTopology[j]);
  }
};

class BondTableCompareFunctor {
  const FMCS::Graph &QueryTopology;
  const FMCS::Graph &TargetTopology;
  const FMCS::MatchTable &MatchTable;

 public:
  BondTableCompareFunctor(const FMCS::Graph &query, const FMCS::Graph &target,
                          const FMCS::MatchTable &targetMatch)
      : QueryTopology(query),
        TargetTopology(target),
        MatchTable(targetMatch) {};
  bool operator()(FMCS::Graph::edge_descriptor i,
                  FMCS::Graph::edge_descriptor j) const {
    return MatchTable.at(QueryTopology[i], TargetTopology[j]);
  }
};

bool SubstructMatchCustomTable(const FMCS::Graph &target, const ROMol &mol,
                               const FMCS::Graph &query, const ROMol &querySrc,
                               const MatchTable &atomMatchTable,
                               const MatchTable &bondMatchTable,
                               const MCSParameters *p, match_V_t *match) {
  if (query.m_vertices.size() > target.m_vertices.size()  // query > target
      || query.m_edges.size() > target.m_edges.size()) {
    return false;
  }

  MolMatchFinalCheckFunctor mc(query, target, querySrc, mol, p);

  AtomTableCompareFunctor ac(query, target, atomMatchTable);
  BondTableCompareFunctor bc(query, target, bondMatchTable);

  match_V_t dummy_match;
  if (!match) {
    match = &dummy_match;
  }
  return boost::vf2(query, target, ac, bc, mc, *match);
}

//=========================================================================
// slow implementation with absolutely the same functionality
//=========================================================================

class AtomLabelFunctor {
  const FMCS::Graph &QueryTopology;
  const FMCS::Graph &TargetTopology;
  const ROMol &d_query;
  const ROMol &d_mol;
  MCSAtomCompareFunction AtomCompare;
  const MCSAtomCompareParameters &Parameters;
  void *UserData;

 public:
  AtomLabelFunctor(const FMCS::Graph &query, const FMCS::Graph &target,
                   const ROMol &querySrc, const ROMol &mol  // target
                   ,
                   MCSAtomCompareFunction atomCompare,
                   const MCSAtomCompareParameters &p, void *ud)
      : QueryTopology(query),
        TargetTopology(target),
        d_query(querySrc),
        d_mol(mol),
        AtomCompare(atomCompare),
        Parameters(p),
        UserData(ud) {};
  bool operator()(unsigned int i, unsigned int j) const {
    return AtomCompare(Parameters, d_query, QueryTopology[i], d_mol,
                       TargetTopology[j], UserData);
  }
};

class BondLabelFunctor {
  const FMCS::Graph &QueryTopology;
  const FMCS::Graph &TargetTopology;
  const ROMol &d_query;
  const ROMol &d_mol;
  MCSBondCompareFunction BondCompare;
  const MCSBondCompareParameters &Parameters;
  void *UserData;

 public:
  BondLabelFunctor(const FMCS::Graph &query, const FMCS::Graph &target,
                   const ROMol &querySrc, const ROMol &mol,
                   MCSBondCompareFunction bondCompare,
                   const MCSBondCompareParameters &p, void *ud)
      : QueryTopology(query),
        TargetTopology(target),
        d_query(querySrc),
        d_mol(mol),
        BondCompare(bondCompare),
        Parameters(p),
        UserData(ud) {};

  bool operator()(FMCS::Graph::edge_descriptor i,
                  FMCS::Graph::edge_descriptor j) const {
    unsigned int ii =
        QueryTopology[i];  // take actual Idx value for full source
                           // query molecule from index list
    unsigned int jj = TargetTopology[j];  // the same Idx
    return BondCompare(Parameters, d_query, ii, d_mol, jj, UserData);
  }
};

bool SubstructMatchCustom(
    const FMCS::Graph &target, const ROMol &mol, const FMCS::Graph &query,
    const ROMol &querySrc  // seed and full source query molecule
    ,
    MCSAtomCompareFunction atomCompare, MCSBondCompareFunction bondCompare,
    MCSFinalMatchCheckFunction, const MCSAtomCompareParameters &acp,
    const MCSBondCompareParameters &bcp, void *ud, match_V_t *match) {
  MolMatchFinalCheckFunctor matchChecker(query, target, querySrc, mol, nullptr);
  AtomLabelFunctor atomLabeler(query, target, querySrc, mol, atomCompare, acp,
                               ud);
  BondLabelFunctor bondLabeler(query, target, querySrc, mol, bondCompare, bcp,
                               ud);

  match_V_t dummy_match;
  if (!match) {
    match = &dummy_match;
  }
  return boost::vf2(query, target, atomLabeler, bondLabeler, matchChecker,
                    *match);
}
}  // namespace FMCS
}  // namespace RDKit
