/*
 (C) 2022 Cadence Design Systems, Inc. (Cadence)
 All rights reserved.
 TERMS FOR USE OF SAMPLE CODE The software below ("Sample Code") is
 provided to current licensees or subscribers of Cadence products or
 SaaS offerings (each a "Customer").
 Customer is hereby permitted to use, copy, and modify the Sample Code,
 subject to these terms. Cadence claims no rights to Customer's
 modifications. Modification of Sample Code is at Customer's sole and
 exclusive risk. Sample Code may require Customer to have a then
 current license or subscription to the applicable Cadence offering.
 THE SAMPLE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED. OPENEYE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
 NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall Cadence be
 liable for any damages or liability in connection with the Sample Code
 or its use.
 */

#include <cstdio>

#include <openeye.h>
#include <oeplatform.h>
#include <oesystem.h>
#include <oechem.h>
#include <oebioisostere.h>

// Demonstrates how to take curated Brood scores and inspect connection-table
// and molecule-building outcomes with OEMolCTBuilder, OEBroodMolBuilder, and
// OEBroodBuildResult.

class BroodConnectionMolBuilderOptions : public OESystem::OEOptions
{
public:
  BroodConnectionMolBuilderOptions(std::string name = "BroodConnectionMolBuilderOptions")
    : OESystem::OEOptions(name)
  {
    OESystem::OEStringParameter dbParam("-db");
    dbParam.SetRequired(true);
    dbParam.SetVisibility(OESystem::OEParamVisibility::Simple);
    dbParam.SetBrief("Brood database folder");
    m_dbParam = AddParameter(dbParam);

    OESystem::OEUIntParameter maxHitsParam("-maxScoreHits", 100);
    maxHitsParam.SetVisibility(OESystem::OEParamVisibility::Simple);
    maxHitsParam.SetBrief("Maximum number of curated scores used for CT and molecule building");
    m_maxHitsParam = AddParameter(maxHitsParam);

  }

  BroodConnectionMolBuilderOptions(const BroodConnectionMolBuilderOptions&) = default;
  BroodConnectionMolBuilderOptions& operator=(const BroodConnectionMolBuilderOptions&) = default;
  ~BroodConnectionMolBuilderOptions() override = default;
  BroodConnectionMolBuilderOptions* CreateCopy() const override { return new BroodConnectionMolBuilderOptions(*this); }

  std::string GetDataBase() const { return m_dbParam->GetStringValue(); }

  unsigned int GetMaxScoreHits() const
  {
    if (m_maxHitsParam->GetHasValue())
      return static_cast<unsigned int>(std::stoi(m_maxHitsParam->GetStringValue()));
    return static_cast<unsigned int>(std::stoi(m_maxHitsParam->GetStringDefault()));
  }

private:
  OESystem::OEParameter* m_dbParam;
  OESystem::OEParameter* m_maxHitsParam;
};

int main(int argc, char* argv[])
{
  BroodConnectionMolBuilderOptions broodOpts;
  OEChem::OESimpleAppOptions opts(
      broodOpts,
      "BroodConnectionMolBuilder",
      OEChem::OEFileStringType::Mol3D,
      OEChem::OEFileStringType::Mol3D);

  if (OESystem::OEConfigureOpts(opts, argc, argv, false) == OESystem::OEOptsConfigureStatus::Help)
    return 0;
  broodOpts.UpdateValues(opts);

  OEChem::oemolistream ifs;
  if (!ifs.open(opts.GetInFile()))
    OESystem::OEThrow.Fatal("Unable to open %s for reading", opts.GetInFile().c_str());

  OEChem::oemolostream ofs;
  if (!ofs.open(opts.GetOutFile()))
    OESystem::OEThrow.Fatal("Unable to open %s for writing", opts.GetOutFile().c_str());

  OEBioisostere::OEBroodQuery query;
  unsigned retCode = OEBioisostere::OEReadBroodQuery(ifs, query);
  if (retCode != OEBioisostere::OEBroodStatusCode::Success)
    OESystem::OEThrow.Fatal("Failed to read query: %s", OEBioisostere::OEGetBroodStatus(retCode).c_str());

  // Gather scored fragment matches for the query from the Brood database.
  OEBioisostere::OEDBReader reader;
  if (reader.Init(broodOpts.GetDataBase(), query) != OEBioisostere::OEBroodStatusCode::Success)
    OESystem::OEThrow.Fatal("Unable to open Brood database '%s'", broodOpts.GetDataBase().c_str());

  OEBioisostere::OEBroodOverlay overlay;
  overlay.SetupRef(query);

  // Curate the raw scores before running CT and molecule building.
  OEBioisostere::OEScoreHitlist scoreHitlist(broodOpts.GetMaxScoreHits());
  OEBioisostere::OEBroodDBPacket packet;
  while (reader.GetNextPacket(packet))
    scoreHitlist.AddScores(overlay.Overlay(packet));
  scoreHitlist.Build();

  if (scoreHitlist.GetHitCount() == 0)
  {
    OESystem::OEThrow.Warning("No fragments survived overlay scoring; nothing to build.");
    return 0;
  }

  // Compare connection building and molecule building on the curated scores.
  OEBioisostere::OEMolCTBuilder ctBuilder(query);
  OEBioisostere::OEBroodMolBuilder molBuilder(query);

  unsigned written = 0;
  unsigned failedBuild = 0;
  unsigned failedCT = 0;
  unsigned ctAttempts = 0;
  unsigned idx = 0;

  for (const auto& score : scoreHitlist.GetHits())
  {
    ++idx;

    // Rebuild the candidate connection table from the scored fragment.
    OEChem::OEGraphMol ctMol(score.GetFrag());
    const bool ctOk = ctBuilder.Build(ctMol);
    ++ctAttempts;
    if (!ctOk)
      ++failedCT;

    // Build the full Brood hit directly from the same scored fragment.
    OEBioisostere::OEBroodHit hit;
    const unsigned status = molBuilder.Build(score, hit);
    if (status != OEBioisostere::OEBroodStatusCode::Success)
    {
      ++failedBuild;
      printf("[%4u] Build(score) failed: status=%s, ct=%s\n",
             idx,
             OEBioisostere::OEGetBroodStatus(status).c_str(),
             ctOk ? "pass" : "fail");
      continue;
    }

    // Collect build diagnostics for reporting with OEBroodBuildResult.
    OEChem::OEGraphMol diagMol(score.GetFrag());
    OEBioisostere::OEBroodBuildResult result;
    const unsigned diagStatus = molBuilder.Build(diagMol, result);

    OEChem::OEWriteConstMolecule(ofs, hit.GetMol());
    ++written;

    printf("[%4u] ct=%s diagStatus=%-14s buildStatus=%-14s molTC=%.3f strain=%6.2f dStrain=%6.2f\n",
           idx,
           ctOk ? "pass" : "fail",
           OEBioisostere::OEGetBroodStatus(diagStatus).c_str(),
           OEBioisostere::OEGetBroodStatus(result.GetBuildStatus()).c_str(),
           result.GetMolTanimotoCombo(),
           result.GetLocalStrain(),
           result.GetDeltaLocalStrain());
  }

  printf("---- Summary ----\n");
  printf("Curated scores:          %u\n", scoreHitlist.GetHitCount());
  printf("Hits written:            %u\n", written);
  printf("Mol build failures:      %u\n", failedBuild);
  printf("CT checks attempted:     %u\n", ctAttempts);
  printf("CT build failures:       %u\n", failedCT);
  return 0;
}


