// $Id$
//
//  Copyright (C) 2005-2010 Greg Landrum and Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include <fstream>
#include <iomanip>
#include <sstream>
#include <string>

#include <RDGeneral/BoostStartInclude.h>
#include <boost/algorithm/string.hpp>
#include <RDGeneral/BoostEndInclude.h>

#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <RDGeneral/BadFileException.h>
#include <RDGeneral/FileParseException.h>
#include <RDGeneral/RDLog.h>

#ifdef RDK_BUILD_MAEPARSER_SUPPORT
#undef RDK_BUILD_MAEPARSER_SUPPORT
#endif
#include "MolWriters.h"
#include "FileParsers.h"

namespace RDKit {
TDTWriter::TDTWriter(const std::string &fileName) {
  if (fileName != "-") {
    auto *tmpStream = new std::ofstream(fileName.c_str());
    if (!(*tmpStream) || (tmpStream->bad())) {
      delete tmpStream;
      std::ostringstream errout;
      errout << "Bad output file " << fileName;
      throw BadFileException(errout.str());
    }

    dp_ostream = static_cast<std::ostream *>(tmpStream);
    df_owner = true;
  } else {
    dp_ostream = static_cast<std::ostream *>(&std::cout);
    df_owner = false;
  }
  d_molid = 0;
  d_numDigits = 4;
  df_write2D = false;
  df_writeNames = true;
}

TDTWriter::TDTWriter(std::ostream *outStream, bool takeOwnership) {
  PRECONDITION(outStream, "null stream");
  if (outStream->bad()) {
    throw FileParseException("Bad output stream");
  }
  dp_ostream = outStream;
  df_owner = takeOwnership;
  d_molid = 0;
  d_numDigits = 4;
  df_write2D = false;
  df_writeNames = true;
}

TDTWriter::~TDTWriter() {
  // close the writer if it's still open:
  if (dp_ostream != nullptr) {
    close();
  }
}

void TDTWriter::setProps(const STR_VECT &propNames) {
  if (d_molid > 0) {
    BOOST_LOG(rdWarningLog) << "WARNING: Setting property list after a few "
                               "molecules have been written\n";
  }

  d_props = propNames;
}

void TDTWriter::write(const ROMol &mol, int confId) {
  CHECK_INVARIANT(dp_ostream, "no output stream");
  // start by writing a "|" line unless this is the first line
  if (d_molid > 0) {
    (*dp_ostream) << "|\n";
  }

  // write the molecule
  (*dp_ostream) << "$SMI<" << MolToSmiles(mol) << ">\n";

  std::string name;
  if (df_writeNames && mol.getPropIfPresent(common_properties::_Name, name)) {
    (*dp_ostream) << "NAME<" << name << ">\n";
  }

  // do we need to write coordinates?
  if (mol.getNumConformers()) {
    // get the ordering of the atoms in the output SMILES:
    std::vector<unsigned int> atomOrdering;
    mol.getProp(common_properties::_smilesAtomOutputOrder, atomOrdering);

    const Conformer &conf = mol.getConformer(confId);
    if (df_write2D) {
      (*dp_ostream) << "2D<";
    } else {
      (*dp_ostream) << "3D<";
    }
    const RDGeom::POINT3D_VECT &coords = conf.getPositions();
    int nAts = atomOrdering.size();
    for (int i = 0; i < nAts; i++) {
      (*dp_ostream) << std::setprecision(d_numDigits)
                    << coords[atomOrdering[i]].x << ",";
      (*dp_ostream) << std::setprecision(d_numDigits)
                    << coords[atomOrdering[i]].y;
      if (!df_write2D) {
        (*dp_ostream) << "," << std::setprecision(d_numDigits)
                      << coords[atomOrdering[i]].z;
      }
      if (i != nAts - 1) {
        (*dp_ostream) << ",";
      }
    }
    (*dp_ostream) << ";>\n";
  }
  // now write the properties
  if (!d_props.empty()) {
    // check if we have any properties the user specified to write out
    // in which loop over them and write them out
    for (const auto &pi : d_props) {
      if (mol.hasProp(pi)) {
        writeProperty(mol, pi);
      }
    }
  } else {
    // if use did not specify any properties, write all non computed properties
    // out to the file
    STR_VECT properties = mol.getPropList();
    STR_VECT compLst;
    mol.getPropIfPresent(RDKit::detail::computedPropName, compLst);

    for (const auto &pi : properties) {
      // ignore any of the following properties
      if (pi == RDKit::detail::computedPropName ||
          pi == common_properties::_Name || pi == "_MolFileInfo" ||
          pi == "_MolFileComments" ||
          pi == common_properties::_MolFileChiralFlag) {
        continue;
      }

      // check if this property is not computed
      if (std::find(compLst.begin(), compLst.end(), pi) == compLst.end()) {
        writeProperty(mol, pi);
      }
    }
  }
  d_molid++;
}

void TDTWriter::writeProperty(const ROMol &mol, const std::string &name) {
  PRECONDITION(dp_ostream, "no output stream");
  (*dp_ostream) << name << "<";

  // write the property value
  // FIX: we will assume for now that the desired property value is
  // catable to a string
  std::string pval;

  // we need to remove any line breaks in the output, replace them with spaces
  mol.getProp(name, pval);
  boost::replace_all(pval, "\n", " ");
  (*dp_ostream) << pval << ">\n";
}
}  // namespace RDKit
