// $Id$
//
//  Copyright (C) 2001-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 "AlignMolecules.h"
#include "O3AAlignMolecules.h"
#include <GraphMol/FileParsers/MolSupplier.h>
#include <GraphMol/FileParsers/MolWriters.h>
#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/Descriptors/Crippen.h>
#include <GraphMol/ROMol.h>
#include <GraphMol/Conformer.h>
#include <GraphMol/Substruct/SubstructMatch.h>
#include <Numerics/Vector.h>
#include <ForceField/ForceField.h>
#include <GraphMol/ForceFieldHelpers/UFF/Builder.h>
#include <GraphMol/ForceFieldHelpers/MMFF/Builder.h>
#include <GraphMol/MolPickler.h>
#include <GraphMol/DistGeomHelpers/Embedder.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/MolTransforms/MolTransforms.h>

using namespace RDKit;

void test1MolAlign() {
  std::string rdbase = getenv("RDBASE");
  std::string fname1 = rdbase + "/Code/GraphMol/MolAlign/test_data/1oir.mol";
  ROMol *m1 = MolFileToMol(fname1);
  std::string fname2 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/1oir_conf.mol";
  ROMol *m2 = MolFileToMol(fname2);

  double rmsd = MolAlign::alignMol(*m2, *m1);
  TEST_ASSERT(RDKit::feq(rmsd, 0.6578));

  std::string fname3 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/1oir_trans.mol";
  ROMol *m3 = MolFileToMol(fname3);
  const Conformer &conf1 = m2->getConformer(0);
  const Conformer &conf2 = m3->getConformer(0);
  unsigned int i, nat = m3->getNumAtoms();
  for (i = 0; i < nat; i++) {
    RDGeom::Point3D pt1 = conf1.getAtomPos(i);
    RDGeom::Point3D pt2 = conf2.getAtomPos(i);
    TEST_ASSERT(RDKit::feq(pt1.x, pt2.x, 0.001));
    TEST_ASSERT(RDKit::feq(pt1.y, pt2.y, 0.001));
    TEST_ASSERT(RDKit::feq(pt1.z, pt2.z, 0.001));
  }
  RDGeom::Transform3D trans;
  rmsd = MolAlign::getAlignmentTransform(*m1, *m2, trans);
  TEST_ASSERT(RDKit::feq(rmsd, 0.6578));

  // specify conformations
  rmsd = MolAlign::alignMol(*m1, *m2, 0, 0);
  TEST_ASSERT(RDKit::feq(rmsd, 0.6578));

  // provide an atom mapping
  delete m1;
  delete m2;
  delete m3;
}

void test1GetBestRMS() {
  std::string rdbase = getenv("RDBASE");
  std::string fname =
    rdbase + "/Code/GraphMol/MolAlign/test_data/probe_mol.sdf";
  SDMolSupplier supplier(fname, true, false);
  ROMol *m1 = supplier[1];
  ROMol *m2 = supplier[2];

  // alignMol() would return this for the rms: 2.50561
  // But the best rms is: 2.43449
  double rmsd = MolAlign::getBestRMS(*m1, *m2);

  TEST_ASSERT(RDKit::feq(rmsd, 2.43449));
  delete m1;
  delete m2;
}

void test1MolWithQueryAlign() {
  // identical to test1MolAlign except we replace one atom with a QueryAtom instead

  std::string rdbase = getenv("RDBASE");
  std::string fname1 = rdbase + "/Code/GraphMol/MolAlign/test_data/1oir.mol";
  RWMol *m1 = new RWMol(*MolFileToMol(fname1));
  QueryAtom *a1 = new QueryAtom(6);
  std::string fname2 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/1oir_conf.mol";
  RWMol *m2 = new RWMol(*MolFileToMol(fname2));
  QueryAtom *a2 = new QueryAtom(6);

  // we replace the same nitrogen instead with a null
  // query  28 and 19 are the "same" atoms
  m1->replaceAtom(28, a1);
  m2->replaceAtom(19, a2);

  double rmsd = MolAlign::alignMol(*m2, *m1);
  TEST_ASSERT(RDKit::feq(rmsd, 0.6578));

  std::string fname3 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/1oir_trans.mol";

  RWMol *m3 = new RWMol(*MolFileToMol(fname3));
  m3->replaceAtom(0, new QueryAtom(5));

  const Conformer &conf1 = m2->getConformer(0);
  const Conformer &conf2 = m3->getConformer(0);
  unsigned int i, nat = m3->getNumAtoms();
  for (i = 0; i < nat; i++) {
    RDGeom::Point3D pt1 = conf1.getAtomPos(i);
    RDGeom::Point3D pt2 = conf2.getAtomPos(i);
    TEST_ASSERT(RDKit::feq(pt1.x, pt2.x, 0.001));
    TEST_ASSERT(RDKit::feq(pt1.y, pt2.y, 0.001));
    TEST_ASSERT(RDKit::feq(pt1.z, pt2.z, 0.001));
  }

  RDGeom::Transform3D trans;
  rmsd = MolAlign::getAlignmentTransform(*m1, *m2, trans);
  TEST_ASSERT(RDKit::feq(rmsd, 0.6578));

  // specify conformations
  rmsd = MolAlign::alignMol(*m1, *m2, 0, 0);
  TEST_ASSERT(RDKit::feq(rmsd, 0.6578));

  // provide an atom mapping
  delete m1;
  delete m2;
  delete m3;

}

void test2AtomMap() {
  std::string rdbase = getenv("RDBASE");
  std::string fname1 = rdbase + "/Code/GraphMol/MolAlign/test_data/1oir.mol";
  ROMol *m1 = MolFileToMol(fname1);
  std::string fname2 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/1oir_conf.mol";
  ROMol *m2 = MolFileToMol(fname2);
  MatchVectType atomMap;
  atomMap.push_back(std::pair<int, int>(18, 27));
  atomMap.push_back(std::pair<int, int>(13, 23));
  atomMap.push_back(std::pair<int, int>(21, 14));
  atomMap.push_back(std::pair<int, int>(24, 7));
  atomMap.push_back(std::pair<int, int>(9, 19));
  atomMap.push_back(std::pair<int, int>(16, 30));
  double rmsd = MolAlign::alignMol(*m2, *m1, 0, 0, &atomMap);
  TEST_ASSERT(RDKit::feq(rmsd, 0.8525));
  delete m1;
  delete m2;
}

void test3Weights() {
  std::string rdbase = getenv("RDBASE");
  std::string fname1 = rdbase + "/Code/GraphMol/MolAlign/test_data/1oir.mol";
  ROMol *m1 = MolFileToMol(fname1);
  std::string fname2 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/1oir_conf.mol";
  ROMol *m2 = MolFileToMol(fname2);
  MatchVectType atomMap;
  atomMap.push_back(std::pair<int, int>(18, 27));
  atomMap.push_back(std::pair<int, int>(13, 23));
  atomMap.push_back(std::pair<int, int>(21, 14));
  atomMap.push_back(std::pair<int, int>(24, 7));
  atomMap.push_back(std::pair<int, int>(9, 19));
  atomMap.push_back(std::pair<int, int>(16, 30));

  RDNumeric::DoubleVector wts(6);
  wts.setVal(0, 1.0);
  wts.setVal(1, 1.0);
  wts.setVal(2, 1.0);
  wts.setVal(3, 1.0);
  wts.setVal(4, 1.0);
  wts.setVal(5, 2.0);
  double rmsd = MolAlign::alignMol(*m2, *m1, 0, 0, &atomMap, &wts);
  TEST_ASSERT(RDKit::feq(rmsd, 0.9513));
  delete m1;
  delete m2;
}

void testIssue241() {
  std::string rdbase = getenv("RDBASE");
  std::string fname1 =
      rdbase + "/Code/GraphMol/MolAlign/test_data/Issue241.mol";
  ROMol *m1 = MolFileToMol(fname1);
  std::string res;
  MolPickler::pickleMol(*m1, res);
  ROMol *ref = new ROMol(res);
  DGeomHelpers::EmbedMolecule(*ref, 30, 239 * 10);
  ForceFields::ForceField *ff1 = UFF::constructForceField(*ref);
  ff1->initialize();
  ff1->minimize(200, 1e-8, 1e-6);

  std::string pkl2;
  MolPickler::pickleMol(*m1, pkl2);
  ROMol *probe = new ROMol(pkl2);
  DGeomHelpers::EmbedMolecule(*probe, 30, 239 * 10);
  ForceFields::ForceField *ff2 = UFF::constructForceField(*probe);
  ff2->initialize();
  ff2->minimize(200, 1e-8, 1e-6);

  double rmsd = MolAlign::alignMol(*ref, *probe);
  TEST_ASSERT(RDKit::feq(rmsd, 0.0));
}

void testMMFFO3A() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2";
  std::string newSdf = sdf + "_MMFFO3A.sdf";
  sdf += ".sdf";
  SDMolSupplier supplier(sdf, true, false);
  int nMol = supplier.length();
  const int refNum = 48;
  // SDWriter *newMol = new SDWriter(newSdf);
  ROMol *refMol = supplier[refNum];
  MMFF::MMFFMolProperties refMP(*refMol);
  double cumScore = 0.0;
  double cumMsd = 0.0;
  for (int prbNum = 0; prbNum < nMol; ++prbNum) {
    ROMol *prbMol = supplier[prbNum];
    MMFF::MMFFMolProperties prbMP(*prbMol);
    MolAlign::O3A o3a(*prbMol, *refMol, &prbMP, &refMP);
    double rmsd = o3a.align();
    cumScore += o3a.score();
    cumMsd += rmsd * rmsd;
    // newMol->write(prbMol);
    delete prbMol;
  }
  cumMsd /= (double)nMol;
  delete refMol;
  // newMol->close();
  // std::cerr<<cumScore<<","<<sqrt(cumMsd)<<std::endl;
  TEST_ASSERT(RDKit::feq(cumScore, 6941.8, 1));
  TEST_ASSERT(RDKit::feq(sqrt(cumMsd), .345, .001));
}

void testCrippenO3A() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2";
  std::string newSdf = sdf + "_CrippenO3A.sdf";
  sdf += ".sdf";
  SDMolSupplier supplier(sdf, true, false);
  int nMol = supplier.length();
  const int refNum = 48;
  // SDWriter *newMol = new SDWriter(newSdf);
  ROMol *refMol = supplier[refNum];
  unsigned int refNAtoms = refMol->getNumAtoms();
  std::vector<double> refLogpContribs(refNAtoms);
  std::vector<double> refMRContribs(refNAtoms);
  std::vector<unsigned int> refAtomTypes(refNAtoms);
  std::vector<std::string> refAtomTypeLabels(refNAtoms);
  Descriptors::getCrippenAtomContribs(*refMol, refLogpContribs, refMRContribs,
                                      true, &refAtomTypes, &refAtomTypeLabels);
  double cumScore = 0.0;
  double cumMsd = 0.0;
  for (int prbNum = 0; prbNum < nMol; ++prbNum) {
    ROMol *prbMol = supplier[prbNum];
    unsigned int prbNAtoms = prbMol->getNumAtoms();
    std::vector<double> prbLogpContribs(prbNAtoms);
    std::vector<double> prbMRContribs(prbNAtoms);
    std::vector<unsigned int> prbAtomTypes(prbNAtoms);
    std::vector<std::string> prbAtomTypeLabels(prbNAtoms);
    Descriptors::getCrippenAtomContribs(*prbMol, prbLogpContribs, prbMRContribs,
                                        true, &prbAtomTypes,
                                        &prbAtomTypeLabels);
    MolAlign::O3A o3a(*prbMol, *refMol, &prbLogpContribs, &refLogpContribs,
                      MolAlign::O3A::CRIPPEN);
    double rmsd = o3a.align();
    cumScore += o3a.score();
    cumMsd += rmsd * rmsd;
    // newMol->write(prbMol);
    delete prbMol;
  }
  cumMsd /= (double)nMol;
  delete refMol;
  // newMol->close();
  // std::cerr<<cumScore<<","<<sqrt(cumMsd)<<std::endl;
  TEST_ASSERT(RDKit::feq(cumScore, 4918.1, 1));
  TEST_ASSERT(RDKit::feq(sqrt(cumMsd), .304, .001));
}

void testMMFFO3AMolHist() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2";
  std::string newSdf = sdf + "_MMFFO3A.sdf";
  sdf += ".sdf";
  SDMolSupplier supplier(sdf, true, false);
  int nMol = supplier.length();
  const int refNum = 48;
  // SDWriter *newMol = new SDWriter(newSdf);
  ROMol *refMol = supplier[refNum];
  MMFF::MMFFMolProperties refMP(*refMol);
  double *refDmat = MolOps::get3DDistanceMat(*refMol);
  MolAlign::MolHistogram refHist(*refMol, refDmat);
  double cumScore = 0.0;
  double cumMsd = 0.0;
  for (int prbNum = 0; prbNum < nMol; ++prbNum) {
    ROMol *prbMol = supplier[prbNum];
    MMFF::MMFFMolProperties prbMP(*prbMol);
    double *prbDmat = MolOps::get3DDistanceMat(*prbMol);
    MolAlign::MolHistogram prbHist(*prbMol, prbDmat);

    MolAlign::O3A o3a(*prbMol, *refMol, &prbMP, &refMP, MolAlign::O3A::MMFF94,
                      -1, -1, false, 50, 0, NULL, NULL, NULL, &prbHist,
                      &refHist);
    double rmsd = o3a.align();
    cumScore += o3a.score();
    cumMsd += rmsd * rmsd;
    // newMol->write(prbMol);
    delete prbMol;
  }
  cumMsd /= (double)nMol;
  delete refMol;
  // newMol->close();
  // std::cerr<<cumScore<<","<<sqrt(cumMsd)<<std::endl;
  TEST_ASSERT(RDKit::feq(cumScore, 6941.8, 1));
  TEST_ASSERT(RDKit::feq(sqrt(cumMsd), .345, .001));
}

void testCrippenO3AMolHist() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2";
  std::string newSdf = sdf + "_CrippenO3A.sdf";
  sdf += ".sdf";
  SDMolSupplier supplier(sdf, true, false);
  int nMol = supplier.length();
  const int refNum = 48;
  // SDWriter *newMol = new SDWriter(newSdf);
  ROMol *refMol = supplier[refNum];
  unsigned int refNAtoms = refMol->getNumAtoms();
  std::vector<double> refLogpContribs(refNAtoms);
  std::vector<double> refMRContribs(refNAtoms);
  std::vector<unsigned int> refAtomTypes(refNAtoms);
  std::vector<std::string> refAtomTypeLabels(refNAtoms);
  Descriptors::getCrippenAtomContribs(*refMol, refLogpContribs, refMRContribs,
                                      true, &refAtomTypes, &refAtomTypeLabels);
  double *refDmat = MolOps::get3DDistanceMat(*refMol);
  MolAlign::MolHistogram refHist(*refMol, refDmat);
  double cumScore = 0.0;
  double cumMsd = 0.0;
  for (int prbNum = 0; prbNum < nMol; ++prbNum) {
    ROMol *prbMol = supplier[prbNum];
    unsigned int prbNAtoms = prbMol->getNumAtoms();
    std::vector<double> prbLogpContribs(prbNAtoms);
    std::vector<double> prbMRContribs(prbNAtoms);
    std::vector<unsigned int> prbAtomTypes(prbNAtoms);
    std::vector<std::string> prbAtomTypeLabels(prbNAtoms);
    Descriptors::getCrippenAtomContribs(*prbMol, prbLogpContribs, prbMRContribs,
                                        true, &prbAtomTypes,
                                        &prbAtomTypeLabels);
    double *prbDmat = MolOps::get3DDistanceMat(*prbMol);
    MolAlign::MolHistogram prbHist(*prbMol, prbDmat);

    MolAlign::O3A o3a(*prbMol, *refMol, &prbLogpContribs, &refLogpContribs,
                      MolAlign::O3A::CRIPPEN, -1, -1, false, 50, 0, NULL, NULL,
                      NULL, &prbHist, &refHist);
    double rmsd = o3a.align();
    cumScore += o3a.score();
    cumMsd += rmsd * rmsd;
    // newMol->write(prbMol);
    delete prbMol;
  }
  cumMsd /= (double)nMol;
  delete refMol;
  // newMol->close();
  // std::cerr<<cumScore<<","<<sqrt(cumMsd)<<std::endl;
  TEST_ASSERT(RDKit::feq(cumScore, 4918.1, 1));
  TEST_ASSERT(RDKit::feq(sqrt(cumMsd), .304, .001));
}

void testMMFFO3AConstraints() {
  ROMol *m = SmilesToMol("n1ccc(cc1)-c1ccccc1");
  TEST_ASSERT(m);
  ROMol *m1 = MolOps::addHs(*m);
  delete m;
  TEST_ASSERT(m1);
  DGeomHelpers::EmbedMolecule(*m1);
  MMFF::sanitizeMMFFMol((RWMol &)(*m1));
  MMFF::MMFFMolProperties mp(*m1);
  TEST_ASSERT(mp.isValid());
  ForceFields::ForceField *field = MMFF::constructForceField(*m1, &mp);
  field->initialize();
  field->minimize();
  RWMol *patt = SmartsToMol("nccc-cccc");
  MatchVectType matchVect;
  TEST_ASSERT(SubstructMatch(*m1, (ROMol &)*patt, matchVect));
  unsigned int nIdx = matchVect[0].second;
  unsigned int cIdx = matchVect[matchVect.size() - 1].second;
  MolTransforms::setDihedralDeg(m1->getConformer(), matchVect[2].second,
                                matchVect[3].second, matchVect[4].second,
                                matchVect[5].second, 0.0);
  ROMol m2(*m1);
  MolAlign::randomTransform(m2);
  ROMol m3(m2);
  MolAlign::O3A *o3a = new MolAlign::O3A(m2, *m1, &mp, &mp);
  TEST_ASSERT(o3a);
  o3a->align();
  delete o3a;
  double d = (m2.getConformer().getAtomPos(cIdx) -
              m1->getConformer().getAtomPos(cIdx)).length();
  TEST_ASSERT(feq(d, 0.0, 1));
  MatchVectType constraintMap;
  constraintMap.push_back(std::make_pair(cIdx, nIdx));
  o3a = new MolAlign::O3A(m3, *m1, &mp, &mp, MolAlign::O3A::MMFF94, -1, -1,
                          false, 50, 0, &constraintMap);
  TEST_ASSERT(o3a);
  o3a->align();
  delete o3a;
  d = (m3.getConformer().getAtomPos(cIdx) - m1->getConformer().getAtomPos(cIdx))
          .length();
  TEST_ASSERT(feq(d, 7.0, 1.0));
  delete m1;
}

void testCrippenO3AConstraints() {
  ROMol *m = SmilesToMol("n1ccc(cc1)-c1ccccc1");
  TEST_ASSERT(m);
  ROMol *m1 = MolOps::addHs(*m);
  delete m;
  TEST_ASSERT(m1);
  DGeomHelpers::EmbedMolecule(*m1);
  MMFF::sanitizeMMFFMol((RWMol &)(*m1));
  MMFF::MMFFMolProperties mp(*m1);
  TEST_ASSERT(mp.isValid());
  ForceFields::ForceField *field = MMFF::constructForceField(*m1, &mp);
  field->initialize();
  field->minimize();
  RWMol *patt = SmartsToMol("nccc-cccc");
  MatchVectType matchVect;
  TEST_ASSERT(SubstructMatch(*m1, (ROMol &)*patt, matchVect));
  unsigned int nIdx = matchVect[0].second;
  unsigned int cIdx = matchVect[matchVect.size() - 1].second;
  MolTransforms::setDihedralDeg(m1->getConformer(), matchVect[2].second,
                                matchVect[3].second, matchVect[4].second,
                                matchVect[5].second, 0.0);
  ROMol m2(*m1);
  MolAlign::randomTransform(m2);
  ROMol m3(m2);
  unsigned int prbNAtoms = m2.getNumAtoms();
  std::vector<double> prbLogpContribs(prbNAtoms);
  std::vector<double> prbMRContribs(prbNAtoms);
  std::vector<unsigned int> prbAtomTypes(prbNAtoms);
  std::vector<std::string> prbAtomTypeLabels(prbNAtoms);
  Descriptors::getCrippenAtomContribs(m2, prbLogpContribs, prbMRContribs, true,
                                      &prbAtomTypes, &prbAtomTypeLabels);
  MolAlign::O3A *o3a = new MolAlign::O3A(
      m2, *m1, &prbLogpContribs, &prbLogpContribs, MolAlign::O3A::CRIPPEN);
  TEST_ASSERT(o3a);
  o3a->align();
  delete o3a;
  double d = (m2.getConformer().getAtomPos(cIdx) -
              m1->getConformer().getAtomPos(cIdx)).length();
  TEST_ASSERT(feq(d, 0.0, 1));
  MatchVectType constraintMap;
  constraintMap.push_back(std::make_pair(cIdx, nIdx));
  o3a = new MolAlign::O3A(m3, *m1, &prbLogpContribs, &prbLogpContribs,
                          MolAlign::O3A::CRIPPEN, -1, -1, false, 50, 0,
                          &constraintMap);
  TEST_ASSERT(o3a);
  o3a->align();
  delete o3a;
  d = (m3.getConformer().getAtomPos(cIdx) - m1->getConformer().getAtomPos(cIdx))
          .length();
  TEST_ASSERT(feq(d, 7.0, 1.0));
  delete m1;
}

void testMMFFO3AConstraintsAndLocalOnly() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2.sdf";
  SDMolSupplier supplier(sdf, true, false);
  const int refNum = 23;
  const int prbNum = 32;
  ROMol *refMol = supplier[refNum];
  ROMol *prbMol = supplier[prbNum];
  unsigned int refNAtoms = refMol->getNumAtoms();
  std::vector<double> refLogpContribs(refNAtoms);
  std::vector<double> refMRContribs(refNAtoms);
  std::vector<unsigned int> refAtomTypes(refNAtoms);
  std::vector<std::string> refAtomTypeLabels(refNAtoms);
  Descriptors::getCrippenAtomContribs(*refMol, refLogpContribs, refMRContribs,
                                      true, &refAtomTypes, &refAtomTypeLabels);
  unsigned int prbNAtoms = prbMol->getNumAtoms();
  std::vector<double> prbLogpContribs(prbNAtoms);
  std::vector<double> prbMRContribs(prbNAtoms);
  std::vector<unsigned int> prbAtomTypes(prbNAtoms);
  std::vector<std::string> prbAtomTypeLabels(prbNAtoms);
  Descriptors::getCrippenAtomContribs(*prbMol, prbLogpContribs, prbMRContribs,
                                      true, &prbAtomTypes, &prbAtomTypeLabels);
  RWMol *patt = SmartsToMol("S");
  MatchVectType matchVect;
  TEST_ASSERT(SubstructMatch(*refMol, (ROMol &)*patt, matchVect));
  delete patt;
  unsigned int refSIdx = matchVect[0].second;
  matchVect.clear();
  patt = SmartsToMol("O");
  TEST_ASSERT(SubstructMatch(*prbMol, (ROMol &)*patt, matchVect));
  delete patt;
  unsigned int prbOIdx = matchVect[0].second;
  std::vector<double> distOS(2);
  distOS[0] = 2.7;
  distOS[1] = 0.4;
  std::vector<double> weights(2);
  weights[0] = 0.1;
  weights[1] = 100.0;
  for (unsigned int i = 0; i < 2; ++i) {
    MatchVectType constraintMap;
    constraintMap.push_back(std::make_pair(prbOIdx, refSIdx));
    RDNumeric::DoubleVector constraintWeights(1);
    constraintWeights[0] = weights[i];
    MolAlign::O3A *o3a =
        new MolAlign::O3A(*prbMol, *refMol, &prbLogpContribs, &refLogpContribs,
                          MolAlign::O3A::CRIPPEN, -1, -1, false, 50, 0,
                          &constraintMap, &constraintWeights);
    TEST_ASSERT(o3a);
    o3a->align();
    delete o3a;
    o3a = new MolAlign::O3A(*prbMol, *refMol, &prbLogpContribs,
                            &refLogpContribs, MolAlign::O3A::CRIPPEN, -1, -1,
                            false, 50, MolAlign::O3_LOCAL_ONLY);
    TEST_ASSERT(o3a);
    o3a->align();
    delete o3a;
    double d = (prbMol->getConformer().getAtomPos(prbOIdx) -
                refMol->getConformer().getAtomPos(refSIdx)).length();
    TEST_ASSERT(feq(d, distOS[i], 0.1));
  }
}

void testCrippenO3AConstraintsAndLocalOnly() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2.sdf";
  SDMolSupplier supplier(sdf, true, false);
  const int refNum = 23;
  const int prbNum = 32;
  ROMol *refMol = supplier[refNum];
  ROMol *prbMol = supplier[prbNum];
  MMFF::MMFFMolProperties refMP(*refMol);
  TEST_ASSERT(refMP.isValid());
  MMFF::MMFFMolProperties prbMP(*prbMol);
  TEST_ASSERT(prbMP.isValid());
  RWMol *patt = SmartsToMol("S");
  MatchVectType matchVect;
  TEST_ASSERT(SubstructMatch(*refMol, (ROMol &)*patt, matchVect));
  delete patt;
  unsigned int refSIdx = matchVect[0].second;
  matchVect.clear();
  patt = SmartsToMol("O");
  TEST_ASSERT(SubstructMatch(*prbMol, (ROMol &)*patt, matchVect));
  delete patt;
  unsigned int prbOIdx = matchVect[0].second;
  std::vector<double> distOS(2);
  distOS[0] = 3.2;
  distOS[1] = 0.3;
  std::vector<double> weights(2);
  weights[0] = 10.0;
  weights[1] = 100.0;
  for (unsigned int i = 0; i < 2; ++i) {
    MatchVectType constraintMap;
    constraintMap.push_back(std::make_pair(prbOIdx, refSIdx));
    RDNumeric::DoubleVector constraintWeights(1);
    constraintWeights[0] = weights[i];
    MolAlign::O3A *o3a = new MolAlign::O3A(
        *prbMol, *refMol, &prbMP, &refMP, MolAlign::O3A::MMFF94, -1, -1, false,
        50, 0, &constraintMap, &constraintWeights);
    TEST_ASSERT(o3a);
    o3a->align();
    delete o3a;
    o3a = new MolAlign::O3A(*prbMol, *refMol, &prbMP, &refMP,
                            MolAlign::O3A::MMFF94, -1, -1, false, 50,
                            MolAlign::O3_LOCAL_ONLY);
    TEST_ASSERT(o3a);
    o3a->align();
    delete o3a;
    double d = (prbMol->getConformer().getAtomPos(prbOIdx) -
                refMol->getConformer().getAtomPos(refSIdx)).length();
    TEST_ASSERT(feq(d, distOS[i], 0.1));
  }
}

#ifdef RDK_TEST_MULTITHREADED
namespace {
void runblock_o3a_mmff(ROMol *refMol, const std::vector<ROMol *> &mols,
                       const std::vector<double> &rmsds,
                       const std::vector<double> &scores, unsigned int count,
                       unsigned int idx) {
  for (unsigned int rep = 0; rep < 30; ++rep) {
    MMFF::MMFFMolProperties refMP(*refMol);
    for (unsigned int i = 0; i < mols.size(); ++i) {
      if (i % count != idx) continue;
      if (!(rep % 10)) {
        BOOST_LOG(rdErrorLog) << "Rep: " << rep << " Mol:" << i << std::endl;
      }
      ROMol prbMol(*mols[i]);
      MMFF::MMFFMolProperties prbMP(prbMol);
      MolAlign::O3A o3a(prbMol, *refMol, &prbMP, &refMP);
      double rmsd = o3a.align();
      double score = o3a.score();
      TEST_ASSERT(feq(rmsd, rmsds[i]));
      TEST_ASSERT(feq(score, scores[i]));
    }
  }
}
void runblock_o3a_crippen(ROMol *refMol, const std::vector<ROMol *> &mols,
                          const std::vector<double> &rmsds,
                          const std::vector<double> &scores, unsigned int count,
                          unsigned int idx) {
  ROMol refMolCopy(*refMol);
  for (unsigned int rep = 0; rep < 30; ++rep) {
    unsigned int refNAtoms = refMolCopy.getNumAtoms();
    std::vector<double> refLogpContribs(refNAtoms);
    std::vector<double> refMRContribs(refNAtoms);
    std::vector<unsigned int> refAtomTypes(refNAtoms);
    std::vector<std::string> refAtomTypeLabels(refNAtoms);
    Descriptors::getCrippenAtomContribs(refMolCopy, refLogpContribs,
                                        refMRContribs, true, &refAtomTypes,
                                        &refAtomTypeLabels);
    for (unsigned int i = 0; i < mols.size(); ++i) {
      if (i % count != idx) continue;
      if (!(rep % 10)) {
        BOOST_LOG(rdErrorLog) << "Rep: " << rep << " Mol:" << i << std::endl;
      }
      ROMol prbMol(*mols[i]);
      unsigned int prbNAtoms = prbMol.getNumAtoms();
      std::vector<double> prbLogpContribs(prbNAtoms);
      std::vector<double> prbMRContribs(prbNAtoms);
      std::vector<unsigned int> prbAtomTypes(prbNAtoms);
      std::vector<std::string> prbAtomTypeLabels(prbNAtoms);
      Descriptors::getCrippenAtomContribs(prbMol, prbLogpContribs,
                                          prbMRContribs, true, &prbAtomTypes,
                                          &prbAtomTypeLabels);
      MolAlign::O3A o3a(prbMol, refMolCopy, &prbLogpContribs, &refLogpContribs,
                        MolAlign::O3A::CRIPPEN);
      double rmsd = o3a.align();
      double score = o3a.score();
      TEST_ASSERT(feq(rmsd, rmsds[i]));
      TEST_ASSERT(feq(score, scores[i]));
    }
  }
}
}
#include <RDGeneral/BoostStartInclude.h>
#include <boost/thread.hpp>
#include <RDGeneral/BoostEndInclude.h>

void testMMFFO3AMultiThread() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2.sdf";

  SDMolSupplier suppl(sdf, true, false);

  std::vector<ROMol *> mols;
  while (!suppl.atEnd() && mols.size() < 100) {
    ROMol *mol = 0;
    try {
      mol = suppl.next();
    } catch (...) {
      continue;
    }
    if (!mol) continue;
    mols.push_back(mol);
  }

  std::cerr << "generating reference data" << std::endl;
  std::vector<double> rmsds(mols.size(), 0.0);
  std::vector<double> scores(mols.size(), 0.0);
  const int refNum = 48;
  ROMol *refMol = mols[refNum];
  MMFF::MMFFMolProperties refMP(*refMol);

  for (unsigned int i = 0; i < mols.size(); ++i) {
    ROMol prbMol(*mols[i]);
    MMFF::MMFFMolProperties prbMP(prbMol);
    MolAlign::O3A o3a(prbMol, *refMol, &prbMP, &refMP);
    rmsds[i] = o3a.align();
    scores[i] = o3a.score();
  }

  boost::thread_group tg;

  std::cerr << "processing" << std::endl;
  unsigned int count = 4;
  for (unsigned int i = 0; i < count; ++i) {
    std::cerr << " launch :" << i << std::endl;
    std::cerr.flush();
    tg.add_thread(new boost::thread(runblock_o3a_mmff, refMol, mols, rmsds,
                                    scores, count, i));
  }
  tg.join_all();

  BOOST_FOREACH (ROMol *mol, mols) { delete mol; }
  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}

void testCrippenO3AMultiThread() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2.sdf";

  SDMolSupplier suppl(sdf, true, false);

  std::vector<ROMol *> mols;
  while (!suppl.atEnd() && mols.size() < 100) {
    ROMol *mol = 0;
    try {
      mol = suppl.next();
    } catch (...) {
      continue;
    }
    if (!mol) continue;
    mols.push_back(mol);
  }

  std::cerr << "generating reference data" << std::endl;
  std::vector<double> rmsds(mols.size(), 0.0);
  std::vector<double> scores(mols.size(), 0.0);
  const int refNum = 48;
  ROMol *refMol = mols[refNum];
  unsigned int refNAtoms = refMol->getNumAtoms();
  std::vector<double> refLogpContribs(refNAtoms);
  std::vector<double> refMRContribs(refNAtoms);
  std::vector<unsigned int> refAtomTypes(refNAtoms);
  std::vector<std::string> refAtomTypeLabels(refNAtoms);
  Descriptors::getCrippenAtomContribs(*refMol, refLogpContribs, refMRContribs,
                                      true, &refAtomTypes, &refAtomTypeLabels);

  for (unsigned int i = 0; i < mols.size(); ++i) {
    ROMol prbMol(*mols[i]);
    unsigned int prbNAtoms = prbMol.getNumAtoms();
    std::vector<double> prbLogpContribs(prbNAtoms);
    std::vector<double> prbMRContribs(prbNAtoms);
    std::vector<unsigned int> prbAtomTypes(prbNAtoms);
    std::vector<std::string> prbAtomTypeLabels(prbNAtoms);
    Descriptors::getCrippenAtomContribs(prbMol, prbLogpContribs, prbMRContribs,
                                        true, &prbAtomTypes,
                                        &prbAtomTypeLabels);
    MolAlign::O3A o3a(prbMol, *refMol, &prbLogpContribs, &refLogpContribs,
                      MolAlign::O3A::CRIPPEN);
    rmsds[i] = o3a.align();
    scores[i] = o3a.score();
  }

  boost::thread_group tg;

  std::cerr << "processing" << std::endl;
  unsigned int count = 4;
  for (unsigned int i = 0; i < count; ++i) {
    std::cerr << " launch :" << i << std::endl;
    std::cerr.flush();
    tg.add_thread(new boost::thread(runblock_o3a_crippen, refMol, mols, rmsds,
                                    scores, count, i));
  }
  tg.join_all();

  BOOST_FOREACH (ROMol *mol, mols) { delete mol; }
  BOOST_LOG(rdErrorLog) << "  done" << std::endl;
}
#endif

void testGetO3AForProbeConfs() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/ref_e2.sdf";

  SDMolSupplier suppl(sdf, true, false);
  ROMol *refMol = suppl[13];
  TEST_ASSERT(refMol);

  sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/probe_mol.sdf";
  SDMolSupplier psuppl(sdf, true, false);
  ROMol *prbMol = psuppl.next();
  TEST_ASSERT(prbMol);
  while (!psuppl.atEnd()) {
    ROMol *mol = psuppl.next();
    if (!mol) continue;
    Conformer *conf = new Conformer(mol->getConformer());
    prbMol->addConformer(conf, true);
    delete mol;
  }
  TEST_ASSERT(prbMol->getNumConformers() == 50);

  MMFF::MMFFMolProperties refMP(*refMol);
  MMFF::MMFFMolProperties prbMP(*prbMol);

  std::vector<std::pair<double, double> > oscores;
  for (unsigned int i = 0; i < prbMol->getNumConformers(); ++i) {
    MolAlign::O3A o3a(*prbMol, *refMol, &prbMP, &refMP, MolAlign::O3A::MMFF94,
                      i);
    double rmsd = o3a.align();
    double score = o3a.score();
    oscores.push_back(std::make_pair(rmsd, score));
  }

  {
    std::vector<boost::shared_ptr<MolAlign::O3A> > o3s;
    MolAlign::getO3AForProbeConfs(*prbMol, *refMol, &prbMP, &refMP, o3s);
    TEST_ASSERT(o3s.size() == prbMol->getNumConformers());
    for (unsigned int i = 0; i < prbMol->getNumConformers(); ++i) {
      TEST_ASSERT(feq(oscores[i].first, o3s[i]->align()));
      TEST_ASSERT(feq(oscores[i].second, o3s[i]->score()));
    }
  }
#ifdef RDK_TEST_MULTITHREADED
  {
    ROMol prbMol2(*prbMol);
    unsigned int nDups = 10;
    for (unsigned int j = 0; j < nDups; ++j) {
      for (unsigned int i = 0; i < prbMol->getNumConformers(); ++i) {
        prbMol2.addConformer(new Conformer(prbMol->getConformer(i)), true);
      }
    }

    std::vector<boost::shared_ptr<MolAlign::O3A> > o3s;
    MolAlign::getO3AForProbeConfs(prbMol2, *refMol, &prbMP, &refMP, o3s, 4);
    TEST_ASSERT(o3s.size() == prbMol2.getNumConformers());
    for (unsigned int i = 0; i < prbMol2.getNumConformers(); ++i) {
      TEST_ASSERT(
          feq(oscores[i % prbMol->getNumConformers()].first, o3s[i]->align()));
      TEST_ASSERT(
          feq(oscores[i % prbMol->getNumConformers()].second, o3s[i]->score()));
    }
  }

#endif
  delete refMol;
  delete prbMol;

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

void testO3AMultiThreadBug() {
  std::string rdbase = getenv("RDBASE");
  std::string sdf = rdbase + "/Code/GraphMol/MolAlign/test_data/bzr_data.sdf";

  SDMolSupplier suppl(sdf, true, false);

  std::vector<ROMol *> mols;
  while (!suppl.atEnd()) {
    ROMol *mol = suppl.next();
    if (!mol) continue;

    while (mol->getNumConformers() < 50) {
      Conformer *conf = new Conformer(mol->getConformer(0));
      mol->addConformer(conf, true);
    }
    mols.push_back(mol);
  }
  TEST_ASSERT(mols.size() == 10);

  ROMol *refMol = new ROMol(*mols[0]);
  TEST_ASSERT(refMol);

  MMFF::MMFFMolProperties refMP(*refMol);

#ifdef RDK_TEST_MULTITHREADED
  {
    for (unsigned int j = 0; j < mols.size(); ++j) {
      ROMol prbMol = *(mols[j]);
      TEST_ASSERT(prbMol.getNumConformers() == 50);

      MMFF::MMFFMolProperties prbMP(prbMol);

      std::vector<std::pair<double, double> > oscores;
      for (unsigned int i = 0; i < prbMol.getNumConformers(); ++i) {
        MolAlign::O3A o3a(prbMol, *refMol, &prbMP, &refMP,
                          MolAlign::O3A::MMFF94, i);
        double rmsd = o3a.align();
        double score = o3a.score();
        oscores.push_back(std::make_pair(rmsd, score));
      }

      ROMol prbMol2 = *(mols[j]);
      std::vector<boost::shared_ptr<MolAlign::O3A> > o3s;
      MolAlign::getO3AForProbeConfs(prbMol2, *refMol, &prbMP, &refMP, o3s, 0);
      TEST_ASSERT(o3s.size() == prbMol2.getNumConformers());
      for (unsigned int i = 0; i < prbMol2.getNumConformers(); ++i) {
        TEST_ASSERT(
            feq(oscores[i % prbMol.getNumConformers()].first, o3s[i]->align()));
        TEST_ASSERT(feq(oscores[i % prbMol.getNumConformers()].second,
                        o3s[i]->score()));
      }
    }
  }

#endif
  delete refMol;
  for (unsigned int j = 0; j < mols.size(); ++j) delete mols[j];

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

int main() {
  std::cout << "***********************************************************\n";
  std::cout << "Testing MolAlign\n";

#if 1
  std::cout << "\t---------------------------------\n";
  std::cout << "\t test1MolAlign \n\n";
  test1MolAlign();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t test1GetBestRMS \n\n";
  test1GetBestRMS();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t test1MolWithQueryAlign \n\n";
  test1MolWithQueryAlign();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t test2AtomMap \n\n";
  test2AtomMap();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t test3Weights \n\n";
  test3Weights();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testIssue241 \n\n";
  testIssue241();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testMMFFO3A \n\n";
  testMMFFO3A();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testMMFFO3A with pre-computed dmat and MolHistogram\n\n";
  testMMFFO3AMolHist();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testMMFFO3A with constraints\n\n";
  testMMFFO3AConstraints();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testMMFFO3A with variable weight constraints followed by "
               "local-only optimization\n\n";
  testMMFFO3AConstraintsAndLocalOnly();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testCrippenO3A \n\n";
  testCrippenO3A();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testCrippenO3A with pre-computed dmat and MolHistogram\n\n";
  testCrippenO3AMolHist();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testCrippenO3A with constraints\n\n";
  testCrippenO3AConstraints();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t testCrippenO3A with variable weight constraints followed by "
               "local-only optimization\n\n";
  testCrippenO3AConstraintsAndLocalOnly();

#ifdef RDK_TEST_MULTITHREADED
  std::cout << "\t---------------------------------\n";
  std::cout << "\t testMMFFO3A multithreading\n\n";
  testMMFFO3AMultiThread();

  std::cout << "\t---------------------------------\n";
  std::cout << "\t test O3A multithreading bug\n\n";
  testO3AMultiThreadBug();
#endif

#ifdef RDK_TEST_MULTITHREADED
  std::cout << "\t---------------------------------\n";
  std::cout << "\t testCrippenO3A multithreading\n\n";
  testCrippenO3AMultiThread();
#endif

  std::cout << "\t---------------------------------\n";
  std::cout << "\t test getO3AForProbeConfs\n\n";
  testGetO3AForProbeConfs();
#endif

#ifdef RDK_TEST_MULTITHREADED
  std::cout << "\t---------------------------------\n";
  std::cout << "\t test O3A multithreading bug\n\n";
  testO3AMultiThreadBug();
#endif

  std::cout << "***********************************************************\n";
}


#pragma once
#include "detection/Category.hpp"
#include "detection/Classification.hpp"
#include <Aquila/core/detail/Export.hpp>

#include <MetaObject/types/small_vec.hpp>

#include <ct/reflect.hpp>

#include <Eigen/Geometry>
#include <opencv2/core/types.hpp>

#include <map>
#include <string>
#include <vector>

namespace aq
{

    AQUILA_EXPORTS void boundingBoxToPixels(cv::Rect2f& bb, cv::Size size);
    AQUILA_EXPORTS void normalizeBoundingBox(cv::Rect2f& bb, cv::Size size);
    AQUILA_EXPORTS void clipNormalizedBoundingBox(cv::Rect2f& bb);

    //////////////////////////////////////////
    /// DetectedObject
    //////////////////////////////////////////
    struct AQUILA_EXPORTS DetectedObject
    {
        DetectedObject(const cv::Rect2f& rect = cv::Rect2f(),
                       const mo::SmallVec<Classification, 5>& cls = mo::SmallVec<Classification, 5>(),
                       unsigned int id = 0,
                       float confidence = 0.0);

        void classify(Classification&& cls);

        cv::Rect2f bounding_box;
        mo::SmallVec<Classification, 5> classifications;
        unsigned int id = 0;
        float confidence = 0.0f;
    };

    bool operator==(const DetectedObject& lhs, const DetectedObject& rhs);

    using DetectedObject2d = DetectedObject;

    //////////////////////////////////////////
    /// DetectedObject3d
    //////////////////////////////////////////
    struct AQUILA_EXPORTS DetectedObject3d
    {
        Eigen::Vector3f size;
        Eigen::Affine3f pose;
        mo::SmallVec<Classification, 5> classifications;
        unsigned int id = 0;
        float confidence = 0.0f;
    };

    //////////////////////////////////////////
    /// TDetectedObjectSet
    //////////////////////////////////////////
    template <class DetType>
    struct AQUILA_EXPORTS TDetectedObjectSet : public std::vector<DetType>
    {
        template <class... Args>
        TDetectedObjectSet(const CategorySet::ConstPtr& cats = CategorySet::ConstPtr())
            : cat_set(cats)
        {
        }

        template <class... Args>
        TDetectedObjectSet(CategorySet::ConstPtr& cats, Args&&... args)
            : std::vector<DetType>(std::forward<Args>(args)...)
            , cat_set(cats)
        {
        }

        template <class... Args>
        TDetectedObjectSet(Args&&... args)
            : std::vector<DetType>(std::forward<Args>(args)...)
        {
        }

        void setCatSet(const CategorySet::ConstPtr& cats)
        {
            cat_set = cats;
            std::vector<DetType>::clear();
        }

        /*template <class AR>
        void serialize(AR& ar)
        {
            ar(cereal::make_nvp("detections", static_cast<std::vector<DetType>&>(*this)));
            ar(CEREAL_NVP(cat_set));
        }*/

        CategorySet::ConstPtr getCatSet() const
        {
            return cat_set;
        }

        CategorySet::ConstPtr cat_set;
    };
    using DetectedObjectSet = TDetectedObjectSet<DetectedObject>;
}

namespace ct
{

    REFLECT_BEGIN(aq::DetectedObject)
        PUBLIC_ACCESS(bounding_box)
        PUBLIC_ACCESS(classifications)
        PUBLIC_ACCESS(id)
        PUBLIC_ACCESS(confidence)
    REFLECT_END;

    REFLECT_BEGIN(aq::DetectedObject3d)
        PUBLIC_ACCESS(size)
        PUBLIC_ACCESS(pose)
        PUBLIC_ACCESS(classifications)
        PUBLIC_ACCESS(id)
        PUBLIC_ACCESS(confidence)
    REFLECT_END;
}


/*
 * Copyright (c) 2021 The Foundation for Research on Information Technologies in Society (IT'IS).
 *
 * This file is part of iSEG
 * (see https://github.com/ITISFoundation/osparc-iseg).
 *
 * This software is released under the MIT License.
 *  https://opensource.org/licenses/MIT
 */
#include "TraceTubesWidget.h"

#include "itkWeightedDijkstraImageFilter.h"

#include "Data/BrushInteraction.h"
#include "Data/ItkUtils.h"
#include "Data/LogApi.h"
#include "Data/SlicesHandlerITKInterface.h"

#include <itkBinaryThresholdImageFilter.h>
#include <itkDanielssonDistanceMapImageFilter.h>
#include <itkHessianRecursiveGaussianImageFilter.h>
#include <itkHessianToObjectnessMeasureImageFilter.h>
#include <itkMinimumMaximumImageCalculator.h>
#include <itkRegionOfInterestImageFilter.h>
#include <itkSignedDanielssonDistanceMapImageFilter.h>
#include <itkSignedMaurerDistanceMapImageFilter.h>
#include <itkSliceBySliceImageFilter.h>

#include <QCheckBox>
#include <QComboBox>
#include <QCompleter>
#include <QDoubleValidator>
#include <QFileSystemModel>
#include <QFormLayout>
#include <QKeyEvent>
#include <QLineEdit>
#include <QProgressDialog>
#include <QPushButton>
#include <QScrollArea>
#include <QStackedWidget>

#include <boost/format.hpp>

using iseg::Point;

namespace {
class FileSystemModel : public QFileSystemModel
{
public:
	FileSystemModel(QObject* parent = nullptr) : QFileSystemModel(parent) {}

	QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
	{
		if (role == Qt::DisplayRole && index.column() == 0)
		{
			QString path = QDir::toNativeSeparators(filePath(index));
			if (path.endsWith(QDir::separator()))
				path.chop(1);
			return path;
		}

		return QFileSystemModel::data(index, role);
	}
};

void add_completer(QLineEdit* file_path, QWidget* parent)
{
	auto completer = new QCompleter(parent);
	completer->setMaxVisibleItems(10);

	FileSystemModel* fs_model = new FileSystemModel(completer);
	completer->setModel(fs_model);
	fs_model->setRootPath("");

	file_path->setCompleter(completer);
}
} // namespace

TraceTubesWidget::TraceTubesWidget(iseg::SlicesHandlerInterface* hand3D)
	: m_Handler(hand3D)
{
	setToolTip(Format("Trace elongated structures"));

	m_MainOptions = new QWidget;
	{
		m_Metric = new QComboBox;
		m_Metric->insertItem(kIntensity, QString("Intensity"));
		m_Metric->insertItem(kHessian2D, QString("Vesselness 2D"));
		m_Metric->insertItem(kHessian3D, QString("Vesselness 3D"));
		m_Metric->insertItem(kTarget, QString("Target"));
		m_Metric->setCurrentIndex(kHessian2D);
		m_Metric->setToolTip(Format("Intensity sticks to pixels with similar intensity as the start/end points. Vesselness computes a tubular structure measure and sticks to vesselness values similar to start/end."));

		m_IntensityValue = new QLineEdit;
		m_IntensityValue->setValidator(new QDoubleValidator);
		m_IntensityValue->setToolTip(Format("Typical intensity value along path. Call estimate intensity to guess a meaningful value. If empty, the value will be guessed from the selected points."));

		m_IntensityWeight = new QLineEdit(QString::number(1.0 / 255.0));
		m_IntensityWeight->setValidator(new QDoubleValidator(0, 1e6, 6));
		m_IntensityWeight->setToolTip(Format("Weight assigned to intensity differences wrt start/end pixel value."));

		m_AngleWeight = new QLineEdit(QString::number(1));
		m_AngleWeight->setValidator(new QDoubleValidator(0, 1e6, 6));
		m_AngleWeight->setToolTip(Format("Penalty weight for non-smooth paths. If values are too large, piece-wise straight paths will be created."));

		m_LineRadius = new QLineEdit(QString::number(0));
		m_LineRadius->setValidator(new QDoubleValidator(0, 100, 4));

		m_ActiveRegionPadding = new QLineEdit(QString::number(2));
		m_ActiveRegionPadding->setValidator(new QIntValidator(1, 1000, m_ActiveRegionPadding));
		m_ActiveRegionPadding->setToolTip(Format("The region for computing the path is defined by the bounding box of the seed points, including a padding."));

		m_DebugMetricFilePath = new QLineEdit;
		add_completer(m_DebugMetricFilePath, this);
		m_DebugMetricFilePath->setToolTip(Format("In order to debug the metric parameters, you can set a file path to export the metric to."));

		m_ClearPoints = new QPushButton("Clear points");

		m_EstimateIntensity = new QPushButton("Estimate intensity");

		m_ExecuteButton = new QPushButton("Execute");

		auto layout = new QFormLayout();
		layout->addRow(QString("Metric"), m_Metric);
		layout->addRow(QString("Intensity"), m_IntensityValue);
		layout->addRow(QString("Intensity weight"), m_IntensityWeight);
		layout->addRow(QString("Angle weight"), m_AngleWeight);
		layout->addRow(QString("Line radius"), m_LineRadius);
		layout->addRow(QString("Region padding"), m_ActiveRegionPadding);
		layout->addRow(QString("Metric debug file"), m_DebugMetricFilePath);
		layout->addRow(m_ClearPoints);
		layout->addRow(m_EstimateIntensity);
		layout->addRow(m_ExecuteButton);

		m_MainOptions->setLayout(layout);
	}

	m_VesselnessOptions = new QWidget;
	{
		m_Sigma = new QLineEdit(QString::number(0.5));
		m_Sigma->setToolTip(Format("Sigma is the width of a Gaussian smoothing operator used to remove noise. Large values might remove important features."));

		m_DarkObjects = new QCheckBox;
		m_DarkObjects->setChecked(true);
		m_DarkObjects->setToolTip(Format("Enhance dark structures on a bright background if true."));

		m_Alpha = new QLineEdit(QString::number(0.5));
		m_Alpha->setValidator(new QDoubleValidator);
		m_Alpha->setToolTip(Format("Ratio of the smallest eigenvalue that has to be large to the larger ones. "
															 "Smaller values lead to increased sensitivity to the object dimensionality."));

		m_Beta = new QLineEdit(QString::number(0.5));
		m_Beta->setValidator(new QDoubleValidator);
		m_Beta->setToolTip(Format("Ratio of the largest eigenvalue that has to be small to the larger ones. "
															"Smaller values lead to increased sensitivity to the object dimensionality."));

		m_Gamma = new QLineEdit(QString::number(5.0));
		m_Gamma->setValidator(new QDoubleValidator);

		auto layout = new QFormLayout();
		layout->addRow(QString("Sigma"), m_Sigma);
		layout->addRow(QString("Dark objects"), m_DarkObjects);
		layout->addRow(QString("Alpha"), m_Alpha);
		layout->addRow(QString("Beta"), m_Beta);
		layout->addRow(QString("Gamma"), m_Gamma);

		m_VesselnessOptions->setLayout(layout);
	}

	m_TargetOptions = new QWidget;
	{
		m_PathFileName = new QLineEdit;
		add_completer(m_PathFileName, this);

		auto layout = new QFormLayout();
		layout->addRow(QString("Path file name"), m_PathFileName);

		m_TargetOptions->setLayout(layout);
	}

	m_OptionsStack = new QStackedWidget;
	m_OptionsStack->addWidget(m_EmptyOptions = new QWidget);
	m_OptionsStack->addWidget(m_VesselnessOptions);
	m_OptionsStack->addWidget(m_TargetOptions);

	auto scroll_area = new QScrollArea(this);
	scroll_area->setWidget(m_MainOptions);

	auto top_level_layout = new QHBoxLayout;
	top_level_layout->addWidget(scroll_area);
	top_level_layout->addWidget(m_OptionsStack);

	setLayout(top_level_layout);
	setMinimumWidth(std::max(300, top_level_layout->sizeHint().width()));

	// update advanced options
	OnMetricChanged();

	QObject_connect(m_Metric, SIGNAL(currentIndexChanged(int)), this, SLOT(OnMetricChanged()));
	QObject_connect(m_ClearPoints, SIGNAL(clicked()), this, SLOT(ClearPoints()));
	QObject_connect(m_EstimateIntensity, SIGNAL(clicked()), this, SLOT(EstimateIntensity()));
	QObject_connect(m_ExecuteButton, SIGNAL(clicked()), this, SLOT(DoWork()));
}

void TraceTubesWidget::OnMetricChanged()
{
	if (m_Metric->currentIndex() == kIntensity)
	{
		m_OptionsStack->setCurrentWidget(m_EmptyOptions);
	}
	else if (m_Metric->currentIndex() == kTarget)
	{
		m_OptionsStack->setCurrentWidget(m_TargetOptions);
	}
	else // kHessian2D/kHessian3D
	{
		m_OptionsStack->setCurrentWidget(m_VesselnessOptions);
	}

	m_EstimateIntensity->setEnabled(m_Metric->currentIndex() != kTarget);
}

void TraceTubesWidget::OnSlicenrChanged()
{
	UpdatePoints();
}

void TraceTubesWidget::Init()
{
	OnSlicenrChanged();
	HideParamsChanged();
	OnMetricChanged();
}

void TraceTubesWidget::NewLoaded()
{
	OnSlicenrChanged();
}

void TraceTubesWidget::Cleanup()
{
	m_Points.clear();
}

std::string TraceTubesWidget::GetName()
{
	return std::string("Trace Tubes");
}

QIcon TraceTubesWidget::GetIcon(QDir picdir)
{
	return QIcon(picdir.absFilePath(QString("Bias.png")).ascii());
}

void TraceTubesWidget::OnMouseReleased(Point x)
{
	auto active_slice = m_Handler->ActiveSlice();
	if (active_slice >= m_Handler->StartSlice() && active_slice < m_Handler->EndSlice())
	{
		iseg::Point3D p;
		p.p = x;
		p.pz = m_Handler->ActiveSlice();
		m_Points.push_back(p);

		UpdatePoints();
	}
	else
	{
		iseg::Log::Warning("no points selected, because slice is not in active slices");
	}
}

void TraceTubesWidget::UpdatePoints()
{
	std::vector<iseg::Point> vp;
	for (auto v : m_Points)
	{
		if (v.pz == m_Handler->ActiveSlice())
			vp.push_back(v.p);
	}
	emit VpdynChanged(&vp);
}

int TraceTubesWidget::GetPadding() const
{
	int pad = m_ActiveRegionPadding->text().toInt();
	auto const line_radius = m_LineRadius->text().toDouble();
	if (line_radius > 0.0)
	{
		auto const spacing = m_Handler->Spacing();
		pad = std::max(pad, static_cast<int>(std::ceil(line_radius / spacing[0])));
		pad = std::max(pad, static_cast<int>(std::ceil(line_radius / spacing[1])));
		pad = std::max(pad, static_cast<int>(std::ceil(line_radius / spacing[2])));
	}
	return pad;
}

itk::Image<float, 3>::Pointer TraceTubesWidget::ComputeVesselness(const itk::ImageBase<3>::RegionType& requested_region) const
{
	using input_type = itk::SliceContiguousImage<float>;
	using hessian_filter_type = itk::HessianRecursiveGaussianImageFilter<input_type>;
	using hessian_pixel_type = itk::SymmetricSecondRankTensor<double, 3>;
	using hessian_image_type = itk::Image<hessian_pixel_type, 3>;
	using image_type = itk::Image<float, 3>;
	using objectness_filter_type = itk::HessianToObjectnessMeasureImageFilter<hessian_image_type, image_type>;

	double sigma = m_Sigma->text().toDouble();
	double alpha = m_Alpha->text().toDouble();
	double beta = m_Beta->text().toDouble();
	double gamma = m_Gamma->text().toDouble();

	iseg::SlicesHandlerITKInterface itk_handler(m_Handler);
	auto source = itk_handler.GetSource(true);

	auto hessian_filter = hessian_filter_type::New();
	hessian_filter->SetInput(source);
	hessian_filter->SetSigma(sigma);

	auto objectness_filter = objectness_filter_type::New();
	objectness_filter->SetInput(hessian_filter->GetOutput());
	objectness_filter->SetBrightObject(!m_DarkObjects->isChecked());
	objectness_filter->SetObjectDimension(1);
	objectness_filter->SetScaleObjectnessMeasure(true);
	objectness_filter->SetAlpha(alpha);
	objectness_filter->SetBeta(beta);
	objectness_filter->SetGamma(gamma);
	objectness_filter->GetOutput()->SetRequestedRegion(requested_region);
	objectness_filter->Update();

	return objectness_filter->GetOutput();
}

itk::Image<float, 3>::Pointer TraceTubesWidget::ComputeBlobiness(const itk::ImageBase<3>::RegionType& requested_region) const
{
	using input_type = itk::SliceContiguousImage<float>;
	using output_type = itk::Image<float, 3>;
	using image_type = itk::Image<float, 2>;

	using hessian_filter_type = itk::HessianRecursiveGaussianImageFilter<image_type>;
	using hessian_pixel_type = itk::SymmetricSecondRankTensor<double, 2>;
	using hessian_image_type = itk::Image<hessian_pixel_type, 2>;

	using objectness_filter_type = itk::HessianToObjectnessMeasureImageFilter<hessian_image_type, image_type>;
	using slice_by_slice_filter_type = itk::SliceBySliceImageFilter<input_type, output_type, hessian_filter_type, objectness_filter_type>;

	double sigma = m_Sigma->text().toDouble();
	double alpha = m_Alpha->text().toDouble();
	double beta = m_Beta->text().toDouble();
	double gamma = m_Gamma->text().toDouble();

	iseg::SlicesHandlerITKInterface itk_handler(m_Handler);
	auto source = itk_handler.GetSource(true);

	auto hessian_filter = hessian_filter_type::New();
	hessian_filter->SetSigma(sigma);

	auto objectness_filter = objectness_filter_type::New();
	objectness_filter->SetInput(hessian_filter->GetOutput());
	objectness_filter->SetBrightObject(!m_DarkObjects->isChecked());
	objectness_filter->SetObjectDimension(0);
	objectness_filter->SetScaleObjectnessMeasure(true);
	objectness_filter->SetAlpha(alpha);
	objectness_filter->SetBeta(beta);
	objectness_filter->SetGamma(gamma);

	auto slice_filter = slice_by_slice_filter_type::New();
	slice_filter->SetInput(source);
	slice_filter->SetInputFilter(hessian_filter);
	slice_filter->SetOutputFilter(objectness_filter);
	slice_filter->GetOutput()->SetRequestedRegion(requested_region);
	slice_filter->Update();

	return slice_filter->GetOutput();
}

itk::Image<float, 3>::Pointer TraceTubesWidget::ComputeObjectSdf(const itk::ImageBase<3>::RegionType& requested_region) const
{
	iseg::SlicesHandlerITKInterface itk_handler(m_Handler);
	auto target = itk_handler.GetTarget(true);

	using input_type = itk::SliceContiguousImage<float>;
	using mask_type = itk::Image<unsigned char, 3>;
	using real_type = itk::Image<float, 3>;
	using roi_filter_type = itk::RegionOfInterestImageFilter<input_type, mask_type>;
	using threshold_filter_type = itk::BinaryThresholdImageFilter<mask_type, mask_type>;
	using distance_filter_type = itk::SignedDanielssonDistanceMapImageFilter<mask_type, real_type>;

	// it seems the distance transform always runs on whole image, which is slow
	// therefore, I extract ROI and afterwards graft the output to fake a bufferedregion
	// with correct start index.
	auto roi_filter = roi_filter_type::New();
	roi_filter->SetInput(target);
	roi_filter->SetRegionOfInterest(requested_region);

	auto threshold = threshold_filter_type::New();
	threshold->SetInput(roi_filter->GetOutput());
	threshold->SetLowerThreshold(1);
	threshold->SetInsideValue(255);
	threshold->SetOutsideValue(0);

	auto distance = distance_filter_type::New();
	distance->SetInput(threshold->GetOutput());
	distance->SetUseImageSpacing(true);
	distance->SetSquaredDistance(false);
	distance->Update();

	auto output = real_type::New();
	output->Graft(distance->GetOutput());
	output->SetLargestPossibleRegion(target->GetLargestPossibleRegion());
	output->SetBufferedRegion(requested_region);
	return output;
}

void TraceTubesWidget::ClearPoints()
{
	m_Points.clear();

	UpdatePoints();
}

void TraceTubesWidget::EstimateIntensity()
{
	if (!m_Points.empty())
	{
		using image_type = itk::Image<float, 3>;

		iseg::SlicesHandlerITKInterface itk_handler(m_Handler);
		auto source = itk_handler.GetSource(true);

		double intensity = 0.0;

		for (auto p : m_Points)
		{
			image_type::IndexType idx = {p.px, p.py, p.pz};

			if (m_Metric->currentIndex() == kIntensity)
			{
				intensity += source->GetPixel(idx);
			}
			else
			{
				image_type::SizeType size = {1, 1, 1};
				image_type::RegionType region(idx, size);
				region.PadByRadius(1);
				region.Crop(source->GetLargestPossibleRegion());

				if (m_Metric->currentIndex() == kHessian2D)
				{
					auto speed_image = ComputeBlobiness(region);
					std::cerr << "Value: " << speed_image->GetPixel(idx) << "\n";
					std::cerr << "Region: " << speed_image->GetBufferedRegion() << "\n";
					intensity += speed_image->GetPixel(idx);
				}
				else if (m_Metric->currentIndex() == kHessian3D)
				{
					auto speed_image = ComputeVesselness(region);
					std::cerr << "Value: " << speed_image->GetPixel(idx) << "\n";
					std::cerr << "Region: " << speed_image->GetBufferedRegion() << "\n";
					intensity += speed_image->GetPixel(idx);
				}
			}
		}

		intensity /= m_Points.size();
		m_IntensityValue->setText(QString::number(intensity));
	}
}

void TraceTubesWidget::DoWork()
{
	if (m_Points.size() < 2)
	{
		return;
	}

	using input_type = itk::SliceContiguousImage<float>;

	int pad = GetPadding();
	auto export_file_path = m_DebugMetricFilePath->text().toStdString();

	iseg::SlicesHandlerITKInterface itk_handler(m_Handler);
	auto source = itk_handler.GetSource(true);

	input_type::IndexType idx_lo = {m_Points.front().px, m_Points.front().py, m_Points.front().pz};
	input_type::IndexType idx_hi = idx_lo;
	for (auto p : m_Points)
	{
		idx_lo[0] = std::min<input_type::IndexValueType>(idx_lo[0], p.px);
		idx_lo[1] = std::min<input_type::IndexValueType>(idx_lo[1], p.py);
		idx_lo[2] = std::min<input_type::IndexValueType>(idx_lo[2], p.pz);

		idx_hi[0] = std::max<input_type::IndexValueType>(idx_hi[0], p.px);
		idx_hi[1] = std::max<input_type::IndexValueType>(idx_hi[1], p.py);
		idx_hi[2] = std::max<input_type::IndexValueType>(idx_hi[2], p.pz);
	}
	input_type::RegionType requested_region;
	requested_region.SetIndex(idx_lo);
	requested_region.SetUpperIndex(idx_hi);
	requested_region.PadByRadius(pad);
	requested_region.Crop(source->GetLargestPossibleRegion());

	if (m_Metric->currentIndex() == kIntensity)
	{
		auto speed_image = source;
		DoWorkTemplate<input_type>(speed_image, requested_region);
	}
	else if (m_Metric->currentIndex() == kHessian2D)
	{
		auto speed_image = ComputeBlobiness(requested_region);

		iseg::dump_image<itk::Image<float, 3>>(speed_image, export_file_path);

		DoWorkTemplate<itk::Image<float, 3>>(speed_image, requested_region);
	}
	else if (m_Metric->currentIndex() == kHessian3D)
	{
		auto speed_image = ComputeVesselness(requested_region);

		iseg::dump_image<itk::Image<float, 3>>(speed_image, export_file_path);

		DoWorkTemplate<itk::Image<float, 3>>(speed_image, requested_region);
	}
	else if (m_Metric->currentIndex() == kTarget)
	{
		auto speed_image = ComputeObjectSdf(requested_region);

		iseg::dump_image<itk::Image<float, 3>>(speed_image, export_file_path);

		DoWorkTemplate<itk::Image<float, 3>>(speed_image, requested_region);
	}
}

template<class TSpeedImage>
void TraceTubesWidget::DoWorkTemplate(TSpeedImage* speed_image, const itk::ImageBase<3>::RegionType& requested_region)
{
	using speed_image_type = TSpeedImage;
	using input_type = itk::SliceContiguousImage<float>;
	using output_type = itk::Image<unsigned char, 3>;

	using path_filter_type = itk::WeightedDijkstraImageFilter<speed_image_type>;
	using metric_type = itk::MyMetric<speed_image_type>;

	iseg::SlicesHandlerITKInterface itk_handler(m_Handler);
	auto target = itk_handler.GetTarget(true);

	int pad = GetPadding();
	double intensity_weight = m_IntensityWeight->text().toDouble();
	double angle_weight = m_AngleWeight->text().toDouble();
	double length_weight = 1;
	double bone_length_penalty = 2;		// TODO
	double artery_length_penalty = 2; // TODO
	double line_radius = m_LineRadius->text().toDouble();

	bool has_intensity = false;
	double intensity_value = m_IntensityValue->text().toDouble(&has_intensity);
	if (m_Metric->currentIndex() == kTarget)
	{
		auto calculator = itk::MinimumMaximumImageCalculator<TSpeedImage>::New();
		calculator->SetImage(speed_image);
		calculator->SetRegion(requested_region);
		calculator->ComputeMinimum();
		intensity_value = calculator->GetMinimum();
		has_intensity = true;
	}

	std::vector<typename path_filter_type::PathType::VertexListPointer> paths;

	for (size_t k = 0; k + 1 < m_Points.size(); ++k)
	{
		auto a = m_Points[k];
		auto b = m_Points[k + 1];
		input_type::IndexType aidx = {a.px, a.py, a.pz};
		input_type::IndexType bidx = {b.px, b.py, b.pz};

		input_type::RegionType region;
		for (int i = 0; i < 3; i++)
		{
			region.SetIndex(i, std::min(aidx[i], bidx[i]));
			region.SetSize(i, 1 + std::max(aidx[i], bidx[i]) - region.GetIndex(i));
		}
		region.PadByRadius(pad);
		region.Crop(speed_image->GetLargestPossibleRegion());

		auto dijkstra = path_filter_type::New();
		dijkstra->SetInput(speed_image);
		dijkstra->SetStartIndex(aidx);
		dijkstra->SetEndIndex(bidx);
		dijkstra->SetRegion(region);
		dijkstra->Metric().m_IntensityWeight = intensity_weight;
		dijkstra->Metric().m_AngleWeight = angle_weight;
		dijkstra->Metric().m_LengthWeight = length_weight;
		if (has_intensity)
		{
			dijkstra->Metric().m_InitIntensity = false;
			dijkstra->Metric().m_StartValue = intensity_value;
			dijkstra->Metric().m_EndValue = intensity_value;
		}
		dijkstra->Update();

		auto path = dijkstra->GetOutput(0);
		auto verts = path->GetVertexList();
		paths.push_back(verts);
	}

	// voxelize line
	auto image_with_path = output_type::New();
	image_with_path->SetBufferedRegion(requested_region);
	image_with_path->SetLargestPossibleRegion(speed_image->GetLargestPossibleRegion());
	image_with_path->SetSpacing(speed_image->GetSpacing());
	image_with_path->Allocate();
	image_with_path->FillBuffer(0);

	for (const auto& verts : paths)
	{
		for (auto v : *verts)
		{
			output_type::IndexType idx;
			idx[0] = v[0];
			idx[1] = v[1];
			idx[2] = v[2];
			image_with_path->SetPixel(idx, 255);
		}
	}

	if (line_radius > 0)
	{
		using real_type = itk::Image<float, 3>;
		using distance_type = itk::DanielssonDistanceMapImageFilter<output_type, real_type>;
		using threshold_type = itk::BinaryThresholdImageFilter<real_type, output_type>;

		auto distance = distance_type::New();
		distance->SetInput(image_with_path);
		distance->SetInputIsBinary(true);
		distance->SetUseImageSpacing(true);
		distance->SetSquaredDistance(true);

		auto threshold = threshold_type::New();
		threshold->SetInput(distance->GetOutput());
		threshold->SetUpperThreshold(line_radius * line_radius);
		threshold->SetInsideValue(255);
		threshold->SetOutsideValue(0);
		threshold->GetOutput()->SetRequestedRegion(requested_region);
		threshold->Update();

		image_with_path = threshold->GetOutput();
	}

	if (!m_PathFileName->text().isEmpty() && m_Metric->currentIndex() == kTarget)
	{
		auto fname = m_PathFileName->text().toStdString();
		std::ofstream ofile(fname, std::ofstream::out);
		if (ofile.is_open())
		{
			for (const auto& verts : paths)
			{
				for (auto v : *verts)
				{
					itk::Point<double> p;
					target->TransformContinuousIndexToPhysicalPoint(v, p);
					ofile << p[0] << ", " << p[1] << ", " << p[2] << "\n";
				}
			}
			ofile.close();
		}
	}

	iseg::DataSelection data_selection;
	data_selection.allSlices = true;
	data_selection.work = true;
	emit BeginDatachange(data_selection, this);

	iseg::Paste<output_type, input_type>(image_with_path, target, requested_region);

	emit EndDatachange(this);
}


// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <script/sigencoding.h>

#include <pubkey.h>
#include <script/script_flags.h>

#include <boost/range/adaptor/sliced.hpp>

typedef boost::sliced_range<const valtype> slicedvaltype;

/**
 * A canonical signature exists of: <30> <total len> <02> <len R> <R> <02> <len
 * S> <S>, where R and S are not negative (their first byte has its highest bit
 * not set), and not excessively padded (do not start with a 0 byte, unless an
 * otherwise negative number follows, in which case a single 0 byte is
 * necessary and even required).
 *
 * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
 *
 * This function is consensus-critical since BIP66.
 */
static bool IsValidDERSignatureEncoding(const slicedvaltype &sig) {
    // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
    // * total-length: 1-byte length descriptor of everything that follows,
    // excluding the sighash byte.
    // * R-length: 1-byte length descriptor of the R value that follows.
    // * R: arbitrary-length big-endian encoded R value. It must use the
    // shortest possible encoding for a positive integer (which means no null
    // bytes at the start, except a single one when the next byte has its
    // highest bit set).
    // * S-length: 1-byte length descriptor of the S value that follows.
    // * S: arbitrary-length big-endian encoded S value. The same rules apply.

    // Minimum and maximum size constraints.
    if (sig.size() < 8 || sig.size() > 72) {
        return false;
    }

    //
    // Check that the signature is a compound structure of proper size.
    //

    // A signature is of type 0x30 (compound).
    if (sig[0] != 0x30) {
        return false;
    }

    // Make sure the length covers the entire signature.
    // Remove:
    // * 1 byte for the coupound type.
    // * 1 byte for the length of the signature.
    if (sig[1] != sig.size() - 2) {
        return false;
    }

    //
    // Check that R is an positive integer of sensible size.
    //

    // Check whether the R element is an integer.
    if (sig[2] != 0x02) {
        return false;
    }

    // Extract the length of the R element.
    const uint32_t lenR = sig[3];

    // Zero-length integers are not allowed for R.
    if (lenR == 0) {
        return false;
    }

    // Negative numbers are not allowed for R.
    if (sig[4] & 0x80) {
        return false;
    }

    // Make sure the length of the R element is consistent with the signature
    // size.
    // Remove:
    // * 1 byte for the coumpound type.
    // * 1 byte for the length of the signature.
    // * 2 bytes for the integer type of R and S.
    // * 2 bytes for the size of R and S.
    // * 1 byte for S itself.
    if (lenR > (sig.size() - 7)) {
        return false;
    }

    // Null bytes at the start of R are not allowed, unless R would otherwise be
    // interpreted as a negative number.
    //
    // /!\ This check can only be performed after we checked that lenR is
    //     consistent with the size of the signature or we risk to access out of
    //     bound elements.
    if (lenR > 1 && (sig[4] == 0x00) && !(sig[5] & 0x80)) {
        return false;
    }

    //
    // Check that S is an positive integer of sensible size.
    //

    // S's definition starts after R's definition:
    // * 1 byte for the coumpound type.
    // * 1 byte for the length of the signature.
    // * 1 byte for the size of R.
    // * lenR bytes for R itself.
    // * 1 byte to get to S.
    const uint32_t startS = lenR + 4;

    // Check whether the S element is an integer.
    if (sig[startS] != 0x02) {
        return false;
    }

    // Extract the length of the S element.
    const uint32_t lenS = sig[startS + 1];

    // Zero-length integers are not allowed for S.
    if (lenS == 0) {
        return false;
    }

    // Negative numbers are not allowed for S.
    if (sig[startS + 2] & 0x80) {
        return false;
    }

    // Verify that the length of S is consistent with the size of the signature
    // including metadatas:
    // * 1 byte for the integer type of S.
    // * 1 byte for the size of S.
    if (size_t(startS + lenS + 2) != sig.size()) {
        return false;
    }

    // Null bytes at the start of S are not allowed, unless S would otherwise be
    // interpreted as a negative number.
    //
    // /!\ This check can only be performed after we checked that lenR and lenS
    //     are consistent with the size of the signature or we risk to access
    //     out of bound elements.
    if (lenS > 1 && (sig[startS + 2] == 0x00) && !(sig[startS + 3] & 0x80)) {
        return false;
    }

    return true;
}

static bool IsSchnorrSig(const slicedvaltype &sig) {
    return sig.size() == 64;
}

static bool CheckRawECDSASignatureEncoding(const slicedvaltype &sig,
                                           uint32_t flags,
                                           ScriptError *serror) {
    if (IsSchnorrSig(sig)) {
        // In an ECDSA-only context, 64-byte signatures are forbidden.
        return set_error(serror, ScriptError::SIG_BADLENGTH);
    }
    // https://bitcoin.stackexchange.com/a/12556:
    if ((flags & (SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S |
                  SCRIPT_VERIFY_STRICTENC)) &&
        !IsValidDERSignatureEncoding(sig)) {
        return set_error(serror, ScriptError::SIG_DER);
    }
    // If the S value is above the order of the curve divided by two, its
    // complement modulo the order could have been used instead, which is
    // one byte shorter when encoded correctly.
    if ((flags & SCRIPT_VERIFY_LOW_S) && !CPubKey::CheckLowS(sig)) {
        return set_error(serror, ScriptError::SIG_HIGH_S);
    }

    return true;
}

static bool CheckRawSchnorrSignatureEncoding(const slicedvaltype &sig,
                                             uint32_t flags,
                                             ScriptError *serror) {
    if (IsSchnorrSig(sig)) {
        return true;
    }
    return set_error(serror, ScriptError::SIG_NONSCHNORR);
}

static bool CheckRawSignatureEncoding(const slicedvaltype &sig, uint32_t flags,
                                      ScriptError *serror) {
    if (IsSchnorrSig(sig)) {
        // In a generic-signature context, 64-byte signatures are interpreted
        // as Schnorr signatures (always correctly encoded).
        return true;
    }
    return CheckRawECDSASignatureEncoding(sig, flags, serror);
}

bool CheckDataSignatureEncoding(const valtype &vchSig, uint32_t flags,
                                ScriptError *serror) {
    // Empty signature. Not strictly DER encoded, but allowed to provide a
    // compact way to provide an invalid signature for use with CHECK(MULTI)SIG
    if (vchSig.size() == 0) {
        return true;
    }

    return CheckRawSignatureEncoding(
        vchSig | boost::adaptors::sliced(0, vchSig.size()), flags, serror);
}

static bool CheckSighashEncoding(const valtype &vchSig, uint32_t flags,
                                 ScriptError *serror) {
    if (flags & SCRIPT_VERIFY_STRICTENC) {
        if (!GetHashType(vchSig).isDefined()) {
            return set_error(serror, ScriptError::SIG_HASHTYPE);
        }

        bool usesForkId = GetHashType(vchSig).hasForkId();
        bool forkIdEnabled = flags & SCRIPT_ENABLE_SIGHASH_FORKID;
        if (!forkIdEnabled && usesForkId) {
            return set_error(serror, ScriptError::ILLEGAL_FORKID);
        }

        if (forkIdEnabled && !usesForkId) {
            return set_error(serror, ScriptError::MUST_USE_FORKID);
        }
    }

    return true;
}

template <typename F>
static bool CheckTransactionSignatureEncodingImpl(const valtype &vchSig,
                                                  uint32_t flags,
                                                  ScriptError *serror, F fun) {
    // Empty signature. Not strictly DER encoded, but allowed to provide a
    // compact way to provide an invalid signature for use with CHECK(MULTI)SIG
    if (vchSig.size() == 0) {
        return true;
    }

    if (!fun(vchSig | boost::adaptors::sliced(0, vchSig.size() - 1), flags,
             serror)) {
        // serror is set
        return false;
    }

    return CheckSighashEncoding(vchSig, flags, serror);
}

bool CheckTransactionSignatureEncoding(const valtype &vchSig, uint32_t flags,
                                       ScriptError *serror) {
    return CheckTransactionSignatureEncodingImpl(
        vchSig, flags, serror,
        [](const slicedvaltype &templateSig, uint32_t templateFlags,
           ScriptError *templateSerror) {
            return CheckRawSignatureEncoding(templateSig, templateFlags,
                                             templateSerror);
        });
}

bool CheckTransactionECDSASignatureEncoding(const valtype &vchSig,
                                            uint32_t flags,
                                            ScriptError *serror) {
    return CheckTransactionSignatureEncodingImpl(
        vchSig, flags, serror,
        [](const slicedvaltype &templateSig, uint32_t templateFlags,
           ScriptError *templateSerror) {
            return CheckRawECDSASignatureEncoding(templateSig, templateFlags,
                                                  templateSerror);
        });
}

bool CheckTransactionSchnorrSignatureEncoding(const valtype &vchSig,
                                              uint32_t flags,
                                              ScriptError *serror) {
    return CheckTransactionSignatureEncodingImpl(
        vchSig, flags, serror,
        [](const slicedvaltype &templateSig, uint32_t templateFlags,
           ScriptError *templateSerror) {
            return CheckRawSchnorrSignatureEncoding(templateSig, templateFlags,
                                                    templateSerror);
        });
}

static bool IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) {
    switch (vchPubKey.size()) {
        case CPubKey::COMPRESSED_SIZE:
            // Compressed public key: must start with 0x02 or 0x03.
            return vchPubKey[0] == 0x02 || vchPubKey[0] == 0x03;

        case CPubKey::SIZE:
            // Non-compressed public key: must start with 0x04.
            return vchPubKey[0] == 0x04;

        default:
            // Non-canonical public key: invalid size.
            return false;
    }
}

bool CheckPubKeyEncoding(const valtype &vchPubKey, uint32_t flags,
                         ScriptError *serror) {
    if ((flags & SCRIPT_VERIFY_STRICTENC) &&
        !IsCompressedOrUncompressedPubKey(vchPubKey)) {
        return set_error(serror, ScriptError::PUBKEYTYPE);
    }
    return true;
}


#include <boost/units/systems/information/nat.hpp>


//
// $Id: bucket1d.hpp 5313 2013-12-17 18:06:54Z chambm $
//
//
// Original author: Witold Wolski <wewolski@gmail.com>
//
// Copyright : ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef BUCKET1D_H
#define BUCKET1D_H

#include <vector>
#include <stdexcept>
#include <boost/cstdint.hpp>

#include "pwiz/utility/findmf/base/resample/bin1d.hpp"

namespace ralab{
  namespace base{
    namespace resample{
    /*!\given breaks and data compute frequencies in bins*/
typedef boost::uint32_t uint32_t;
struct Bucket1D
    {
    private:
      std::vector<double> breaks_; // boundaries
      std::vector<uint32_t> indicator_;//Has length length(breaks_) - 1, and indicates if bin is of interest.
      //this code is required to avoid bound checking in release.
      //Some stupid bruker compiler settings.
      double * begbreaks_;
      double * endbreaks_;
      uint32_t * indicatorptr_;
    public:
      /*!\brief CTor*/
      Bucket1D(
          std::vector<double> & breaks, // breaks
          std::vector<uint32_t> & indic // indicator
          ):breaks_(), indicator_(), begbreaks_(0), endbreaks_(0), indicatorptr_(0)
      {
        set( breaks , indic );
      }

      /*!\brief CCTor*/
      Bucket1D(
          const Bucket1D & rhs
          ):breaks_(), indicator_(), begbreaks_(0), endbreaks_(0), indicatorptr_(0)
      {
        this->set(rhs.breaks_,rhs.indicator_);
      }

    private:
      /*!\brief  set the data*/
      void set( const std::vector<double> & breaks,
                const std::vector<uint32_t> & indic )
      {
        if(( breaks.size() - 1 ) != indic.size()){
            throw std::out_of_range( "breaks.size == inic + 1 , failed!" );
          }
        breaks_ = breaks;
        begbreaks_ = &breaks_[0];
        endbreaks_ = begbreaks_ + breaks_.size();
        indicator_ = indic;
        indicatorptr_ = &indicator_[0];
      }

    public:

      /*! \Assignment */
      Bucket1D& operator=(const Bucket1D &rhs)
      {
        if (this == &rhs) // protect against invalid self-assignment
          return *this; // See "4:"
        this->set( rhs.breaks_, rhs.indicator_);
        return *this;
      }

      /*!\brief
                    The result tells you in which bucket which input should end up.
                    */
      template<typename InputIterator>
      void operator()(
          InputIterator beg, //!< Check wich of these masses should be bucketed
          InputIterator end, //!<
          std::vector<std::pair<std::size_t, std::size_t> > & bucketPairs //indicates an successful assignment, first:  index in bucket second: index in input
          )
      {
        std::size_t index(0);
        std::pair<std::size_t, bool> res;
        for(;beg !=end; ++beg, ++index)
          {
            res = this->operator()(*beg);
            if(res.second)
              {
                bucketPairs.push_back(std::make_pair(res.first , index ));
              }
          }
      }

      /*!\brief

                    the std::size_t indicates to which bucket dat belongs too.
                    The bool indicates if a new bucket is of interest
                    */
      std::pair<std::size_t, bool> operator()(double dat)
      {
        double * it2 = std::lower_bound(begbreaks_,endbreaks_,dat);
        std::size_t ub = std::distance(begbreaks_,it2);

        if(ub > 0 && ub <= indicator_.size())
          {
            ub = ub - 1;
            if(*(indicatorptr_ + ub) > 0)
              return std::make_pair(ub, true);
            else
              return std::make_pair(ub, false);
          }
        else
          return std::make_pair(0, false);
      }

    }; //Bucket1D
    }//namespace resample
  }//namespace base
}//namespace ralab

#endif // BUCKET1D_H


//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include <boost/python/def.hpp>

#include "pxr/pxr.h"
#include "pxr/base/gf/gamma.h"
#include "pxr/base/gf/vec3f.h"
#include "pxr/base/gf/vec3d.h"
#include "pxr/base/gf/vec4f.h"
#include "pxr/base/gf/vec4d.h"

#include "pxr/base/tf/wrapTypeHelpers.h"

using namespace boost::python;

PXR_NAMESPACE_USING_DIRECTIVE

void wrapGamma()
{
    def("ApplyGamma", (GfVec3f(*)(GfVec3f const &,double))GfApplyGamma);
    def("ApplyGamma", (GfVec3d(*)(GfVec3d const &,double))GfApplyGamma);
    def("ApplyGamma", (GfVec3h(*)(GfVec3h const &,double))GfApplyGamma);
    def("ApplyGamma", (GfVec4f(*)(GfVec4f const &,double))GfApplyGamma);
    def("ApplyGamma", (GfVec4d(*)(GfVec4d const &,double))GfApplyGamma);
    def("ApplyGamma", (GfVec4h(*)(GfVec4h const &,double))GfApplyGamma);
    def("GetDisplayGamma",GfGetDisplayGamma);
    def("ConvertDisplayToLinear",(GfVec3f(*)(GfVec3f const &))GfConvertDisplayToLinear);
    def("ConvertDisplayToLinear",(GfVec3d(*)(GfVec3d const &))GfConvertDisplayToLinear);
    def("ConvertDisplayToLinear",(GfVec3h(*)(GfVec3h const &))GfConvertDisplayToLinear);
    def("ConvertDisplayToLinear",(GfVec4f(*)(GfVec4f const &))GfConvertDisplayToLinear);
    def("ConvertDisplayToLinear",(GfVec4d(*)(GfVec4d const &))GfConvertDisplayToLinear);
    def("ConvertDisplayToLinear",(GfVec4h(*)(GfVec4h const &))GfConvertDisplayToLinear);
    def("ConvertLinearToDisplay",(GfVec3f(*)(GfVec3f const &))GfConvertLinearToDisplay);
    def("ConvertLinearToDisplay",(GfVec3d(*)(GfVec3d const &))GfConvertLinearToDisplay);
    def("ConvertLinearToDisplay",(GfVec3h(*)(GfVec3h const &))GfConvertLinearToDisplay);
    def("ConvertLinearToDisplay",(GfVec4f(*)(GfVec4f const &))GfConvertLinearToDisplay);
    def("ConvertLinearToDisplay",(GfVec4d(*)(GfVec4d const &))GfConvertLinearToDisplay);
    def("ConvertLinearToDisplay",(GfVec4h(*)(GfVec4h const &))GfConvertLinearToDisplay);
}


/*    Copyright (c) 2010-2016, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */

#include <boost/make_shared.hpp>
#include <boost/lexical_cast.hpp>

#include "Tudat/Astrodynamics/Aerodynamics/exponentialAtmosphere.h"
#include "Tudat/Astrodynamics/Aerodynamics/tabulatedAtmosphere.h"
#include "Tudat/SimulationSetup/createAtmosphereModel.h"

namespace tudat
{

namespace simulation_setup
{

//! Function to create an atmosphere model.
boost::shared_ptr< aerodynamics::AtmosphereModel > createAtmosphereModel(
        const boost::shared_ptr< AtmosphereSettings > atmosphereSettings,
        const std::string& body )
{
    using namespace tudat::aerodynamics;

    // Declare return object.
    boost::shared_ptr< AtmosphereModel > atmosphereModel;

    // Check which type of atmosphere model is to be created.
    switch( atmosphereSettings->getAtmosphereType( ) )
    {
    case exponential_atmosphere:
    {
        // Check whether settings for atmosphere are consistent with its type.
        boost::shared_ptr< ExponentialAtmosphereSettings > exponentialAtmosphereSettings =
                boost::dynamic_pointer_cast< ExponentialAtmosphereSettings >( atmosphereSettings );
        if( exponentialAtmosphereSettings == NULL )
        {
            throw std::runtime_error(
                        "Error, expected exponential atmosphere settings for body " + body );
        }
        else
        {
            // Create and initialize exponential atmosphere model.
            boost::shared_ptr< ExponentialAtmosphere > exponentialAtmosphereModel =
                    boost::make_shared< ExponentialAtmosphere >(
                        exponentialAtmosphereSettings->getDensityScaleHeight( ) ,
                        exponentialAtmosphereSettings->getConstantTemperature( ),
                        exponentialAtmosphereSettings->getDensityAtZeroAltitude( ),
                        exponentialAtmosphereSettings->getSpecificGasConstant( ) );
            atmosphereModel = exponentialAtmosphereModel;
        }
        break;
    }
    case tabulated_atmosphere:
    {
        // Check whether settings for atmosphere are consistent with its type
        boost::shared_ptr< TabulatedAtmosphereSettings > tabulatedAtmosphereSettings =
                boost::dynamic_pointer_cast< TabulatedAtmosphereSettings >( atmosphereSettings );
        if( tabulatedAtmosphereSettings == NULL )
        {
            throw std::runtime_error(
                        "Error, expected tabulated atmosphere settings for body " + body );
        }
        else
        {
            // Create and initialize tabulatedl atmosphere model.
            atmosphereModel = boost::make_shared< TabulatedAtmosphere >(
                        tabulatedAtmosphereSettings->getAtmosphereFile( ) );
        }
        break;
    }
    default:
        throw std::runtime_error(
                 "Error, did not recognize atmosphere model settings type " +
                  boost::lexical_cast< std::string >( atmosphereSettings->getAtmosphereType( ) ) );
    }
    return atmosphereModel;
}

} // namespace simulation_setup

} // namespace tudat