//
// $Id$
//
//
// Original author: Matt Chambers <matt.chambers .@. vanderbilt.edu>
//
// Copyright 2009 Vanderbilt University - Nashville, TN 37232
//
// 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.
//

#define PWIZ_SOURCE

#include "SpectrumList_Bruker.hpp"
#include "pwiz/utility/chemistry/Chemistry.hpp"

#ifdef PWIZ_READER_BRUKER
#include "pwiz/data/msdata/MSData.hpp"
#include "pwiz/utility/misc/automation_vector.h"
#include "pwiz/utility/misc/Filesystem.hpp"
#include "pwiz/utility/misc/Std.hpp"
#include "pwiz/utility/misc/IntegerSet.hpp"
#include "pwiz/utility/misc/SHA1Calculator.hpp"
#include "pwiz/utility/minimxml/XMLWriter.hpp"
#include <boost/range/algorithm/find_if.hpp>
#include <boost/spirit/include/karma.hpp>

using namespace pwiz::util;
using namespace pwiz::minimxml;
using namespace pwiz::vendor_api::Bruker;

namespace pwiz {
namespace msdata {
namespace detail {

using namespace Bruker;

PWIZ_API_DECL
SpectrumList_Bruker::SpectrumList_Bruker(MSData& msd,
                                         const string& rootpath,
                                         Reader_Bruker_Format format,
                                         CompassDataPtr compassDataPtr,
                                         const Reader::Config& config)
:   msd_(msd), rootpath_(rootpath), format_(format),
    compassDataPtr_(compassDataPtr), config_(config),
    size_(0)
{
    fillSourceList();
    createIndex();
}

PWIZ_API_DECL size_t SpectrumList_Bruker::size() const
{
    return size_;
}

PWIZ_API_DECL const SpectrumIdentity& SpectrumList_Bruker::spectrumIdentity(size_t index) const
{
    if (index >= size_)
        throw runtime_error(("[SpectrumList_Bruker::spectrumIdentity()] Bad index: "
                            + lexical_cast<string>(index)).c_str());
    return index_[index];
}

PWIZ_API_DECL size_t SpectrumList_Bruker::find(const string& id) const
{
    boost::container::flat_map<string, size_t>::const_iterator scanItr = idToIndexMap_.find(id);
    if (scanItr == idToIndexMap_.end())
    {
        if (format_ == Reader_Bruker_Format_TDF)
        {
            try
            {
                int frame = msdata::id::valueAs<int>(id, "frame");
                int scan = msdata::id::valueAs<int>(id, "scan");
                return compassDataPtr_->getSpectrumIndex(frame, scan);
            }
            catch (exception&)
            {
                // fall through and return size_ (id not found)
            }
        }

        return checkNativeIdFindResult(size_, id);
    }
    return scanItr->second;
}

MSSpectrumPtr SpectrumList_Bruker::getMSSpectrumPtr(size_t scan, vendor_api::Bruker::DetailLevel detailLevel) const
{
    if (format_ == Reader_Bruker_Format_FID)
    {
        compassDataPtr_ = CompassData::create(sourcePaths_[scan].string());
        return compassDataPtr_->getMSSpectrum(1, detailLevel);
    }

    return compassDataPtr_->getMSSpectrum(scan, detailLevel);
}

PWIZ_API_DECL SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, bool getBinaryData) const
{
    return spectrum(index, getBinaryData, MSLevelsNone);
}

PWIZ_API_DECL SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, DetailLevel detailLevel) const
{
    return spectrum(index, detailLevel, MSLevelsNone);
}

PWIZ_API_DECL SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, bool getBinaryData, const pwiz::util::IntegerSet& msLevelsToCentroid) const
{
    return spectrum(index, getBinaryData ? DetailLevel_FullData : DetailLevel_FullMetadata, msLevelsToCentroid);
}

PWIZ_API_DECL SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, DetailLevel detailLevel, const pwiz::util::IntegerSet& msLevelsToCentroid) const
{
    if (index >= size_)
        throw runtime_error(("[SpectrumList_Bruker::spectrum()] Bad index: "
                            + lexical_cast<string>(index)).c_str());

    // allocate a new Spectrum
    SpectrumPtr result(new Spectrum);
    if (!result.get())
        throw runtime_error("[SpectrumList_Bruker::spectrum()] Allocation error.");

    vendor_api::Bruker::DetailLevel brukerDetailLevel;
    switch (detailLevel)
    {
        case DetailLevel_InstantMetadata:
        case DetailLevel_FastMetadata:
            brukerDetailLevel = vendor_api::Bruker::DetailLevel_InstantMetadata;
            break;

        case DetailLevel_FullMetadata:
            brukerDetailLevel = vendor_api::Bruker::DetailLevel_FullMetadata;
            break;

        default:
        case DetailLevel_FullData:
            brukerDetailLevel = vendor_api::Bruker::DetailLevel_FullData;
            break;
    }

    const IndexEntry& si = index_[index];
    result->index = si.index;
    result->id = si.id;

    // the scan element may be empty for MALDI spectra, but it's required for a valid file
    result->scanList.scans.push_back(Scan());
    Scan& scan = result->scanList.scans[0];
    scan.instrumentConfigurationPtr = msd_.run.defaultInstrumentConfigurationPtr;

    //try
    //{
        // is this spectrum from the LC interface?
        if (si.collection > -1)
        {
            // fill the spectrum from the LC interface
            LCSpectrumSourcePtr source = compassDataPtr_->getLCSource(si.source);
            LCSpectrumPtr spectrum = compassDataPtr_->getLCSpectrum(si.source, si.scan);

            if (source->getXAxisUnit() == LCUnit_NanoMeter)
                result->set(MS_EMR_spectrum);
            else
                throw runtime_error("[SpectrumList_Bruker::spectrum()] unexpected XAxisUnit");

            double scanTime = spectrum->getTime();
            if (scanTime > 0)
                scan.set(MS_scan_start_time, scanTime, UO_minute);

            result->set(MS_profile_spectrum);

            vector<double> lcX;
            source->getXAxis(lcX);

            if (detailLevel == DetailLevel_FullData)
            {
                result->setMZIntensityArrays(vector<double>(), vector<double>(), MS_number_of_detector_counts);
                result->defaultArrayLength = lcX.size();

                BinaryDataArrayPtr mzArray = result->getMZArray();
                vector<CVParam>::iterator itr = boost::range::find_if(mzArray->cvParams, CVParamIs(MS_m_z_array));
                *itr = CVParam(MS_wavelength_array);

                swap(mzArray->data, lcX);

                vector<double> lcY;
                spectrum->getData(lcY);
                swap(result->getIntensityArray()->data, lcY);
            }
            else
                result->defaultArrayLength = lcX.size();

            return result;
        }

        // get the spectrum from MS interface; for FID formats scan is 0-based, else it's 1-based
        MSSpectrumPtr spectrum = getMSSpectrumPtr(si.scan, brukerDetailLevel);

        result->cvParams.reserve(8); // Anticipate about this many CVParams

        int msLevel = spectrum->getMSMSStage();
        result->set(MS_ms_level, msLevel);

        if (msLevel == 1)
            result->set(MS_MS1_spectrum);
        else
            result->set(MS_MSn_spectrum);

        double scanTime = spectrum->getRetentionTime();
        if (scanTime > 0)
            scan.set(MS_scan_start_time, scanTime, UO_second);

        pair<double, double> scanRange = spectrum->getScanRange();
        if (scanRange.first > 0 && scanRange.second > 0)
            scan.scanWindows.push_back(ScanWindow(scanRange.first, scanRange.second, MS_m_z));

        IonPolarity polarity = spectrum->getPolarity();
        switch (polarity)
        {
            case IonPolarity_Negative:
                result->set(MS_negative_scan);
                break;
            case IonPolarity_Positive:
                result->set(MS_positive_scan);
                break;
            default:
                break;
        }

        //sd.set(MS_base_peak_m_z, pScanStats_->BPM);
        if (spectrum->getTIC() > 0)
        {
            result->set(MS_base_peak_intensity, spectrum->getBPI());
            result->set(MS_total_ion_current, spectrum->getTIC());
        }

        double oneOverK0 = spectrum->oneOverK0();
        if (oneOverK0 > 0)
        {
            scan.set(MS_inverse_reduced_ion_mobility, oneOverK0, MS_Vs_cm_2);
            int windowGroup = spectrum->getWindowGroup();
            if (windowGroup > 0)
                scan.userParams.push_back(UserParam("windowGroup", lexical_cast<string>(windowGroup))); // diaPASEF data
        }

        if (detailLevel == DetailLevel_InstantMetadata)
            return result;

        if (msLevel > 1)
        {
            Precursor precursor;

            vector<double> fragMZs, isolMZs;
            vector<FragmentationMode> fragModes;
            vector<IsolationMode> isolModes;

            spectrum->getFragmentationData(fragMZs, fragModes);

            if (!fragMZs.empty())
            {
                spectrum->getIsolationData(isolMZs, isolModes);

                for (size_t i = 0; i < fragMZs.size(); ++i)
                {
                    if (fragMZs[i] > 0)
                    {
                        SelectedIon selectedIon(fragMZs[i]);

                        int charge = spectrum->getChargeState();
                        if (charge > 0)
                            selectedIon.set(MS_charge_state, charge);

                        switch (fragModes[i])
                        {
                            case FragmentationMode_CID:
                                precursor.activation.set(MS_CID);
                                break;
                            case FragmentationMode_ETD:
                                precursor.activation.set(MS_ETD);
                                break;
                            case FragmentationMode_CIDETD_CID:
                                precursor.activation.set(MS_CID);
                                precursor.activation.set(MS_ETD);
                                break;
                            case FragmentationMode_CIDETD_ETD:
                                precursor.activation.set(MS_CID);
                                precursor.activation.set(MS_ETD);
                                break;
                            case FragmentationMode_ISCID:
                                precursor.activation.set(MS_in_source_collision_induced_dissociation);
                                break;
                            case FragmentationMode_ECD:
                                precursor.activation.set(MS_ECD);
                                break;
                            case FragmentationMode_IRMPD:
                                precursor.activation.set(MS_IRMPD);
                                break;
                            case FragmentationMode_PTR:
                                break;
                        }
                        //precursor.activation.set(MS_collision_energy, pExScanStats_->CollisionEnergy);

                        precursor.selectedIons.push_back(selectedIon);
                    }

                    if (isolMZs[i] > 0)
                    {
                        precursor.isolationWindow.set(MS_isolation_window_target_m_z, isolMZs[i], MS_m_z);

                        double isolationWidth = spectrum->getIsolationWidth();
                        if (isolationWidth > 0)
                        {
                            precursor.isolationWindow.set(MS_isolation_window_lower_offset, isolationWidth / 2, MS_m_z);
                            precursor.isolationWindow.set(MS_isolation_window_upper_offset, isolationWidth / 2, MS_m_z);
                        }
                    }
                }
            }

            if (precursor.selectedIons.size() > 0 || !precursor.isolationWindow.empty())
                result->precursors.push_back(precursor);
        }

        if (detailLevel == DetailLevel_FastMetadata)
            return result;

        // Enumerating merged scan numbers is not instant.
        IntegerSet scanNumbers = spectrum->getMergedScanNumbers();
        if (config_.combineIonMobilitySpectra)
        {
            result->scanList.set(MS_sum_of_spectra);
            if (scanNumbers.size() < 100)
            {
                using namespace boost::spirit::karma;
                auto frameScanPair = compassDataPtr_->getFrameScanPair(si.scan);
                generate(std::back_insert_iterator<std::string>(scan.spectrumID),
                         "frame=" << int_ << " scan=" << int_,
                         frameScanPair.frame, *scanNumbers.begin());

                vector<Scan>& scans = result->scanList.scans;
                scans.reserve(scanNumbers.size());
                for (auto itr = ++scanNumbers.begin(); itr != scanNumbers.end(); ++itr)
                {
                    scans.push_back(Scan());
                    scans.back().instrumentConfigurationPtr = msd_.run.defaultInstrumentConfigurationPtr;
                    generate(std::back_insert_iterator<std::string>(scans.back().spectrumID),
                             "frame=" << int_ << " scan=" << int_,
                             frameScanPair.frame, *itr);

                    // CONSIDER: do we need this? all scan times will be the same and it's rather verbose
                    //if (scanTime > 0)
                    //    scans.back().set(MS_scan_start_time, scanTime, UO_second);

                    // CONSIDER: do we need this? all scan ranges will be the same and it's very verbose!
                    //if (scanRange.first > 0 && scanRange.second > 0)
                    //    scans.back().scanWindows.push_back(ScanWindow(scanRange.first, scanRange.second, MS_m_z));
                }
            }
        }
        else
            result->scanList.set(MS_no_combination);

        //sd.set(MS_lowest_observed_m_z, minObservedMz);
        //sd.set(MS_highest_observed_m_z, maxObservedMz);

        if (detailLevel == DetailLevel_FullData)
        {
            bool getLineData = msLevelsToCentroid.contains(msLevel);

            result->setMZIntensityArrays(vector<double>(), vector<double>(), MS_number_of_detector_counts);

            if (config_.combineIonMobilitySpectra)
            {
                auto& mz = result->getMZArray()->data;
                auto& intensity = result->getIntensityArray()->data;

                result->set(MS_centroid_spectrum); // TIMS is always centroided

                BinaryDataArrayPtr mobility(new BinaryDataArray);
                result->binaryDataArrayPtrs.push_back(mobility);
                CVParam arrayType(MS_mean_inverse_reduced_ion_mobility_array);
                arrayType.units = MS_Vs_cm_2;
                mobility->cvParams.emplace_back(arrayType);

                spectrum->getCombinedSpectrumData(mz, intensity, mobility->data, config_.sortAndJitter);
                result->defaultArrayLength = mz.size();
            }
            else
            {
                automation_vector<double> mzArray, intensityArray;
                if (!getLineData)
                {
                    spectrum->getProfileData(mzArray, intensityArray);
                    if (mzArray.size() == 0)
                        getLineData = true;  // We preferred profile, but there isn't any - try centroided
                    else
                        result->set(MS_profile_spectrum);
                }

                if (getLineData)
                {
                    result->set(MS_centroid_spectrum); // Declare this as centroided data even if scan is empty
                    spectrum->getLineData(mzArray, intensityArray);
                    if (mzArray.size() > 0 && msLevelsToCentroid.contains(msLevel))
                    {
                        result->set(MS_profile_spectrum); // let SpectrumList_PeakPicker know this was probably also a profile spectrum, but doesn't need conversion (actually checking for profile data is crazy slow)
                    }
                }
                result->getMZArray()->data.assign(mzArray.begin(), mzArray.end());
                result->getIntensityArray()->data.assign(intensityArray.begin(), intensityArray.end());
                result->defaultArrayLength = mzArray.size();
            }
        }
        else if (detailLevel == DetailLevel_FullMetadata)
        {
            if (config_.combineIonMobilitySpectra)
            {
                result->defaultArrayLength = spectrum->getCombinedSpectrumDataSize();
                result->set(MS_centroid_spectrum); // TIMS is always centroided
            }
            else
            {
                // N.B.: just getting the data size from the Bruker API is quite expensive.
                if (msLevelsToCentroid.contains(msLevel) || ((result->defaultArrayLength = spectrum->getProfileDataSize())==0))
                {
                    result->defaultArrayLength = spectrum->getLineDataSize();
                    result->set(MS_centroid_spectrum);
                }
                else
                {
                    result->set(MS_profile_spectrum);
                }
            }
        }
    /*}
    catch (_com_error& e) // not caught by either std::exception or '...'
    {
        throw runtime_error(string("[SpectrumList_Bruker::spectrum()] COM error: ") +
                            (const char*)e.Description());
    }*/

    return result;
}

namespace {

void recursivelyEnumerateFIDs(vector<bfs::path>& fidPaths, const bfs::path& rootpath)
{
    const static bfs::directory_iterator endItr = bfs::directory_iterator();

    if (rootpath.leaf() == "fid")
        fidPaths.push_back(rootpath.branch_path());
    else if (bfs::is_directory(rootpath))
    {
        for (bfs::directory_iterator itr(rootpath); itr != endItr; ++itr)
            recursivelyEnumerateFIDs(fidPaths, itr->path());
    }
}

void addSource(MSData& msd, const bfs::path& sourcePath, const bfs::path& rootPath)
{
    SourceFilePtr sourceFile(new SourceFile);
    sourceFile->id = sourcePath.string();
    sourceFile->name = BFS_STRING(sourcePath.leaf());

    // sourcePath: <source>\Analysis.yep|<source>\Analysis.baf|<source>\fid
    // rootPath: c:\path\to\<source>[\Analysis.yep|Analysis.baf|fid]
    bfs::path location = rootPath.has_branch_path() ?
                         BFS_COMPLETE(rootPath.branch_path() / sourcePath) :
                         BFS_COMPLETE(sourcePath); // uses initial path
    sourceFile->location = "file://" + location.branch_path().string();

    msd.fileDescription.sourceFilePtrs.push_back(sourceFile);
}

} // namespace

#if BOOST_FILESYSTEM_VERSION == 2
# define NATIVE_PATH_SLASH "/"
#else
# define NATIVE_PATH_SLASH "\\"
#endif

PWIZ_API_DECL void SpectrumList_Bruker::fillSourceList()
{
    switch (format_)
    {
        case Reader_Bruker_Format_FID:
            recursivelyEnumerateFIDs(sourcePaths_, rootpath_);

            // each fid's source path is a directory but the source file is the fid
            for (size_t i=0; i < sourcePaths_.size(); ++i)
            {
                // in "/foo/bar/1/1SRef/fid", replace "/foo/bar/" with "" so relativePath is "1/1SRef/fid"
                bfs::path relativePath = sourcePaths_[i] / "fid";
                if (rootpath_.has_branch_path())
                    relativePath = bal::replace_first_copy(relativePath.string(), rootpath_.branch_path().string() + NATIVE_PATH_SLASH, "");
                relativePath = bal::replace_all_copy(relativePath.string(), NATIVE_PATH_SLASH, "/");
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_FID_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_FID_format);
            }
            break;

        // a YEP's source path is the same as the source file
        case Reader_Bruker_Format_YEP:
            {
                sourcePaths_.push_back(rootpath_ / "Analysis.yep");
                // strip parent path to get "bar.d/Analysis.yep"
                bfs::path relativePath = bfs::path(rootpath_.filename()) / "Analysis.yep";
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_Agilent_YEP_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_Agilent_YEP_format);
                msd_.run.defaultSourceFilePtr = msd_.fileDescription.sourceFilePtrs.back();
            }
            break;

        // a BAF's source path is the same as the source file
        case Reader_Bruker_Format_BAF:
            {
                sourcePaths_.push_back(rootpath_ / "Analysis.baf");
                // strip parent path to get "bar.d/Analysis.baf"
                bfs::path relativePath = bfs::path(rootpath_.filename()) / "Analysis.baf";
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_BAF_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_BAF_format);
                msd_.run.defaultSourceFilePtr = msd_.fileDescription.sourceFilePtrs.back();
            }
            break;

        // a TDF's source path is the same as the source file
        case Reader_Bruker_Format_TDF:
            {
                sourcePaths_.push_back(rootpath_ / "Analysis.tdf");
                // strip parent path to get "bar.d/Analysis.tdf"
                bfs::path relativePath = bfs::path(rootpath_.filename()) / "Analysis.tdf";
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_TDF_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_TDF_format);
                msd_.run.defaultSourceFilePtr = msd_.fileDescription.sourceFilePtrs.back();
            }

            {
                sourcePaths_.push_back(rootpath_ / "Analysis.tdf_bin");
                // strip parent path to get "bar.d/Analysis.tdf_bin"
                bfs::path relativePath = bfs::path(rootpath_.filename()) / "Analysis.tdf_bin";
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_TDF_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_TDF_format);
            }
            break;

        // a BAF/U2 combo has two sources, with different nativeID formats
        case Reader_Bruker_Format_BAF_and_U2:
            {
                sourcePaths_.push_back(rootpath_ / "Analysis.baf");
                // strip parent path to get "bar.d/Analysis.baf"
                bfs::path relativePath = bfs::path(rootpath_.filename()) / "Analysis.baf";
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_BAF_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_BAF_format);
                msd_.run.defaultSourceFilePtr = msd_.fileDescription.sourceFilePtrs.back();
            }

            {
                sourcePaths_.push_back(bfs::change_extension(rootpath_, ".u2"));
                // in "/foo/bar.d/bar.u2", replace "/foo/" with "" so relativePath is "bar.d/bar.u2"
                bfs::path relativePath = sourcePaths_.back();
                if (rootpath_.has_branch_path())
                    relativePath = bal::replace_first_copy(relativePath.string(), rootpath_.branch_path().string() + NATIVE_PATH_SLASH, "");
                relativePath = bal::replace_all_copy(relativePath.string(), NATIVE_PATH_SLASH, "/");
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_U2_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_U2_format);
            }
            break;

        case Reader_Bruker_Format_U2:
            {
                sourcePaths_.push_back(bfs::change_extension(rootpath_, ".u2"));
                // in "/foo/bar.d/bar.u2", replace "/foo/" with "" so relativePath is "bar.d/bar.u2"
                bfs::path relativePath = sourcePaths_.back();
                if (rootpath_.has_branch_path())
                    relativePath = bal::replace_first_copy(relativePath.string(), rootpath_.branch_path().string() + NATIVE_PATH_SLASH, "");
                relativePath = bal::replace_all_copy(relativePath.string(), NATIVE_PATH_SLASH, "/");
                addSource(msd_, relativePath, rootpath_);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_U2_nativeID_format);
                msd_.fileDescription.sourceFilePtrs.back()->set(MS_Bruker_U2_format);
                msd_.run.defaultSourceFilePtr = msd_.fileDescription.sourceFilePtrs.back();
            }
            break;
    }
}

PWIZ_API_DECL void SpectrumList_Bruker::createIndex()
{
    using namespace boost::spirit::karma;
    map<std::string, size_t> idToIndexTempMap;

    if (format_ == Reader_Bruker_Format_U2 ||
        format_ == Reader_Bruker_Format_BAF_and_U2)
    {
        msd_.fileDescription.fileContent.set(MS_EMR_spectrum);

        for (size_t i=0, end=compassDataPtr_->getLCSourceCount(); i < end; ++i)
        {
            LCSpectrumSourcePtr source = compassDataPtr_->getLCSource(i);

            for (size_t scan=0, end=compassDataPtr_->getLCSpectrumCount(i); scan < end; ++scan)
            {
                index_.push_back(IndexEntry());
                IndexEntry& si = index_.back();
                si.source = i;
                si.collection = source->getCollectionId();
                si.scan = scan;
                si.index = index_.size()-1;
                si.id = "collection=" + lexical_cast<std::string>(si.collection) +
                        " scan=" + lexical_cast<std::string>(si.scan);
                idToIndexTempMap[si.id] = si.index;
            }
        }
    }

    if (format_ == Reader_Bruker_Format_FID)
    {
        int scan = -1;
        BOOST_FOREACH(const SourceFilePtr& sf, msd_.fileDescription.sourceFilePtrs)
        {
            index_.push_back(IndexEntry());
            IndexEntry& si = index_.back();
            si.source = si.collection = -1;
            si.index = index_.size()-1;
            si.scan = ++scan;
            si.id = "file=" + encode_xml_id_copy(sf->id);
            idToIndexTempMap[si.id] = si.index;
        }
    }
    else if (format_ == Reader_Bruker_Format_TDF)
    {
        index_.reserve(compassDataPtr_->getMSSpectrumCount());
        for (size_t scan = 1, end = compassDataPtr_->getMSSpectrumCount(); scan <= end; ++scan)
        {
            index_.emplace_back(IndexEntry());
            IndexEntry& si = index_.back();
            si.source = si.collection = -1;
            si.index = index_.size() - 1;
            si.scan = scan;
            auto frameScanPair = compassDataPtr_->getFrameScanPair(scan);
            std::back_insert_iterator<std::string> sink(si.id);
            if (config_.combineIonMobilitySpectra)
            {
                generate(sink,
                         "merged=" << int_ << " frame=" << int_ << " scanStart=" << int_ << " scanEnd=" << int_,
                         si.index, frameScanPair.frame, frameScanPair.scanStart, frameScanPair.scanEnd);
                idToIndexTempMap[si.id] = si.index;
            }
            else // not inserting into idToIndexTempMap (instead uses on-the-fly logic in find())
                generate(sink,
                         "frame=" << int_ << " scan=" << int_,
                         frameScanPair.frame, frameScanPair.scanStart);
        }
    }
    else if (format_ != Reader_Bruker_Format_U2)
    {
        for (size_t scan=1, end=compassDataPtr_->getMSSpectrumCount(); scan <= end; ++scan)
        {
            index_.push_back(IndexEntry());
            IndexEntry& si = index_.back();
            si.source = si.collection = -1;
            si.index = index_.size()-1;
            si.scan = scan;
            std::back_insert_iterator<std::string> sink(si.id);
            generate(sink,
                     "scan=" << int_,
                     si.scan);
            idToIndexTempMap[si.id] = si.index;
        }
    }

    idToIndexMap_.reserve(idToIndexTempMap.size());
    idToIndexMap_.insert(boost::container::ordered_unique_range, idToIndexTempMap.begin(), idToIndexTempMap.end());
    size_ = index_.size();
}

PWIZ_API_DECL bool SpectrumList_Bruker::hasIonMobility() const
{
    return format_ == Reader_Bruker_Format_TDF;
}

PWIZ_API_DECL bool SpectrumList_Bruker::hasPASEF() const
{
    return compassDataPtr_->hasPASEFData();
}

PWIZ_API_DECL bool SpectrumList_Bruker::canConvertIonMobilityAndCCS() const
{
    return format_ == Reader_Bruker_Format_TDF;
}

// Per email thread Aug 22 2017 bpratt, mattc, Bruker's SvenB:
// The gas is nitrogen(14.0067 AMU) and the temperature is(according to Sven) assumed to be 305K.
static const double ccs_conversion_factor = 18509.863216340458;
static const double MolWeightGas = 14.0067;
static const double Temperature = 305;

PWIZ_API_DECL double SpectrumList_Bruker::ionMobilityToCCS(double inverseK0, double mz, int charge) const
{
    double MolWeight = mz * abs(charge) + chemistry::Electron * charge;
    double ReducedMass = MolWeight * MolWeightGas / (MolWeight + MolWeightGas);
    double K0 = (inverseK0 == 0) ? 0 : (1.0 / inverseK0);
    double ccs = ccs_conversion_factor * abs(charge) / (sqrt(ReducedMass * Temperature) * K0);
    return ccs;    // in Angstrom^2
}

PWIZ_API_DECL double SpectrumList_Bruker::ccsToIonMobility(double ccs, double mz, int charge) const
{
    double MolWeight = mz * abs(charge) + chemistry::Electron * charge;
    double ReducedMass = MolWeight * MolWeightGas / (MolWeight + MolWeightGas);
    double K0 = ccs_conversion_factor * abs(charge) / (sqrt(ReducedMass * Temperature) * ccs);
    return K0 == 0 ? 0 : 1 / K0;    // in Vs/cm^2
}

} // detail
} // msdata
} // pwiz

#else // PWIZ_READER_BRUKER

//
// non-MSVC implementation
//

namespace pwiz {
namespace msdata {
namespace detail {

namespace {const SpectrumIdentity emptyIdentity;}

size_t SpectrumList_Bruker::size() const {return 0;}
const SpectrumIdentity& SpectrumList_Bruker::spectrumIdentity(size_t index) const {return emptyIdentity;}
size_t SpectrumList_Bruker::find(const std::string& id) const {return 0;}
SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, bool getBinaryData) const {return SpectrumPtr();}
SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, DetailLevel detailLevel) const {return SpectrumPtr();}
SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, bool getBinaryData, const pwiz::util::IntegerSet& msLevelsToCentroid) const {return SpectrumPtr();}
SpectrumPtr SpectrumList_Bruker::spectrum(size_t index, DetailLevel detailLevel, const pwiz::util::IntegerSet& msLevelsToCentroid) const {return SpectrumPtr();}
bool SpectrumList_Bruker::hasIonMobility() const { return false; }
bool SpectrumList_Bruker::hasPASEF() const { return false; }
bool SpectrumList_Bruker::canConvertIonMobilityAndCCS() const { return false; }
double SpectrumList_Bruker::ionMobilityToCCS(double ionMobility, double mz, int charge) const {return 0;}
double SpectrumList_Bruker::ccsToIonMobility(double ccs, double mz, int charge) const {return 0;}
} // detail
} // msdata
} // pwiz

#endif // PWIZ_READER_BRUKER


////////////////////////////////////////////////////////////////////////////
//! \file    util.cpp
//! \date    February 2018
//! \author  Eric Barnett
//!          Marc-Antoine Lacasse
//!
//! Copyright (C) 2018 Eric Barnett         <e.barnett@robotiq.com>
//! Copyright (C) 2018 Marc-Antoine Lacasse <malacasse@robotiq.com>
//! Copyright (C) 2018 Clément Gosselin     <gosselin@gmc.ulaval.ca>
//!
//! \brief Time, file IO, and general std::vector functions
//!
//! This file is part of BA, a bisection algorithm for time-optimal trajectory
//! planning
//!
//! This Source Code Form is subject to the terms of the Berkeley Software
//! Distribution (BSD) 3-clause license . If a copy of the BSD license was not
//! distributed with this file, you can obtain one at
//! https://opensource.org/licenses/BSD-3-Clause.
////////////////////////////////////////////////////////////////////////////////

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
  #define isWin
#endif

////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////
#include "util.h"
#ifdef _MSC_VER
#define NOMINMAX // for std::min
#endif
#ifdef isWin
  #include <windows.h> // for cpu time tracking
  #include <io.h> // for function doesFileExist()
#else //Linux
  #include "stdio.h" //for file IO
  #include <unistd.h> // for function doesFileExist(): for testing for file existence
#endif

#include <Eigen/Dense>
using namespace Eigen;

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief getTime
//!
//! get the current time (for computation time tracking)
//!
//////////////////////////////////////////////////////////////////////////////
Time getTime(void)
{
  Time time;
  #ifdef isWin
     LARGE_INTEGER count,freq;
     QueryPerformanceCounter(&count);
     QueryPerformanceFrequency(&freq);
     time.a=(int64_t)count.QuadPart;
     time.b=(int64_t)freq.QuadPart;
  #else
    struct timespec CurrentTime;
    clock_gettime(CLOCK_REALTIME, &CurrentTime);
    time.a=(int64_t)CurrentTime.tv_sec;
    time.b=(int64_t)CurrentTime.tv_nsec;
  #endif
  return time;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief diffTime
//!
//! Compute the difference between two time structs (for computation time tracking)
//!
//////////////////////////////////////////////////////////////////////////////
double diffTime(const Time &endTime,const Time &startTime)
{
  double dTime;
  #ifdef isWin
    dTime=(double)(endTime.a-startTime.a)/(double)startTime.b;
  #else
    if(endTime.b < startTime.b)
         dTime=(double)(endTime.a-startTime.a)-1+(double)(endTime.b-startTime.b+1.0e9)/1.0e9;
    else dTime=(double)(endTime.a-startTime.a)  +(double)(endTime.b-startTime.b)/1.0e9;
  #endif
  return dTime;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief NextLine
//!
//!Rread in characters one at a time from a file until end-of-line or
//! end-of-file is reached
//!
//////////////////////////////////////////////////////////////////////////////
int NextLine(FILE *fid)
{
  int c=0, EOL=10;
  while (c!=EOL && c!=EOF) c=fgetc(fid);
  return c;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief doesFileExist
//!
//! Check for file existence
//!
//////////////////////////////////////////////////////////////////////////////
int doesFileExist(const char *fname)
{
#ifdef isWin
  if(_access(fname, 0) == 0)
    return 0; // exists
  else
    return -1; // does not exist
#else // Linux
  if( access( fname, F_OK ) != -1 )
    return 0; // exists
  else
    return -1; // does not exist
#endif
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief doesFileExist
//!
//! Make a directory if it does not exist
//!
//////////////////////////////////////////////////////////////////////////////
int mkDirIfNec(const char *dirname)
{
   int retVal = 0;
#ifdef isWin
   if(!CreateDirectoryA(dirname ,NULL))
   {
      retVal = -1;
   }
#else
   if(mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1 )
   {
      retVal = -1;
   }
#endif

   return retVal;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief readChar
//!
//! read in a char array and store it in a std::string
//!
//////////////////////////////////////////////////////////////////////////////
std::string readChar(FILE *fid,int &readCountTotal)
{
   char tmpStr[80];
   std::string outStr;
   readCountTotal+=fscanf(fid,"%s",tmpStr);
   NextLine(fid);
   outStr=tmpStr;
   return(outStr);
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief readInt
//!
//! read in an int
//!
//////////////////////////////////////////////////////////////////////////////
int readInt(FILE *fid,int &readCountTotal)
{
   int tmpInt;
   readCountTotal+=fscanf(fid,"%d",&tmpInt);
   NextLine(fid);
   return(tmpInt);
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief readDouble
//!
//! read in a double
//!
//////////////////////////////////////////////////////////////////////////////
double readDouble(FILE *fid,int &readCountTotal)
{
   double tmpDouble;
   readCountTotal+=fscanf(fid,"%lf",&tmpDouble);
   NextLine(fid);
   return(tmpDouble);
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief readDoubleVector
//!
//! read in a vector of doubles
//!
//////////////////////////////////////////////////////////////////////////////
std::vector<double> readDoubleVector(FILE *fid,
                                     int &readCountTotal,
                                     const int vectorLen)
{

   std::vector<double> tmpVect(vectorLen);

   for(int i=0;i<vectorLen;i++)
   {
      readCountTotal += fscanf(fid,"%lf", &tmpVect[i]);
   }
   NextLine(fid);

   return tmpVect;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief readBool
//!
//! read in a bool
//!
//////////////////////////////////////////////////////////////////////////////
bool readBool(FILE *fid,int &readCountTotal)
{
   return(readInt(fid,readCountTotal)==1);
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief normalizeArcLength
//!
//! Normalize the monotonic vector s to lie in the range 0 and 1
//!
//////////////////////////////////////////////////////////////////////////////
int normalizeArcLength(std::vector<double> &s)
{
  s=(1.0/s[s.size()-1])*s;
  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief smooth
//!
//! moving average filter for a vector of data
//!
//////////////////////////////////////////////////////////////////////////////
int smooth(std::vector<double>&x,int w)
{
   int n = (int)x.size();
   w = std::min(w,n);
   int wMid = w/2 + w%2 - 1;
   w = 2*wMid + 1;

   std::vector<double> x2(n);
   double xt, xte;

   x2[0] = x[0];
   x2[n-1] = x[n-1];

   for(int i=1; i<wMid; ++i)
   {
      xt = 0;
      xte = 0;
      int nPtsT = 2*i+1;
      for(int j=0; j<nPtsT; ++j)
      {
         xt += x[j];
         xte += x[n-j-1];
      }
      x2[i] = xt/nPtsT;
      x2[n-i-1] = xte/nPtsT;
   }
   for(int i=wMid; i<n-wMid; ++i)
   {
      xt=0;
      for(int j=i-wMid; j<i+wMid+1; ++j) xt += x[j];
      x2[i] = xt/w;
   }
   x = x2;
   return 0;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief minsmooth
//!
//! minimum of a moving window for a vector
//!
//////////////////////////////////////////////////////////////////////////////
int minsmooth(std::vector<double>&x,int w)
{
   int n = (int)x.size();
   w = std::min(w,n);
   int wMid = w/2 + w%2 - 1;
   w = 2*wMid + 1;

   std::vector<double> x2(n);
   double xt, xte;

   x2[0] = x[0];
   x2[n-1] = x[n-1];

   for(int i=1; i<wMid; ++i)
   {
      xt = x[0];
      xte = x[n-1];
      int nPtsT = 2*i + 1;
      for(int j=1; j<nPtsT; ++j)
      {
         xt  = std::min(xt,x[j]);
         xte = std::min(xte,x[n-j-1]);
      }
      x2[i] = xt;
      x2[n-i-1] = xte;
   }
   for(int i=wMid; i<n-wMid; ++i)
   {
      xt = x[i-wMid];
      for(int j=i-wMid+1;j<i+wMid+1; ++j) xt = std::min(xt,x[j]);
      x2[i] = xt;
   }

   smooth(x2, w);
   for(int i=0; i<n; ++i) x[i] = std::min(x[i], x2[i]);

   return 0;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief decimate
//!
//! decimate a vector of data
//!
//////////////////////////////////////////////////////////////////////////////
int decimate(std::vector<double>&x,int w)
{
  int nIn=(int)x.size();
  int nOut=(nIn-1)/w+1;
  for(int i=0;i<nOut;i++) x[i]=x[w*i];
  if(w*(nOut-1)+1 != nIn) x[nOut-1]=x[nIn-1];

  x.resize(nOut);
  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief solveQuadratic
//!
//! Solve the quadratic equation Ax^2 + Bx + C = 0 for x
//!
//////////////////////////////////////////////////////////////////////////////
int solveQuadratic(const double A,const double B,const double C,
  double &sol1,double &sol2)
{

  if(std::abs(A)<1e-308)
  {
    if(std::abs(B)<1e-308) return -2; // infinite solutions
    sol1=-C/B; sol2=sol1;
    return 0;
  }

  double rad,den,F1,F2;
  rad=B*B-4*A*C;
  if(rad<0) return -1;

  den=2*A;
  F1=-B/den; F2=std::sqrt(rad)/den;

  sol1=F1+F2;
  sol2=F1-F2;

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief findMedian
//!
//! Find the median value for a vector of data
//!
//////////////////////////////////////////////////////////////////////////////
double findMedian(std::vector <double> &x)
{
  size_t nPts=x.size();
  std::vector<double> xSorted=x;
  std::sort (xSorted.begin(), xSorted.end());
  bool isOdd=nPts%2==1; // % is the MOD operator
  double xMED;
  size_t midPt=nPts/2;
  if(isOdd) xMED=xSorted[midPt];
  else      xMED=.5*(xSorted[midPt-1]+xSorted[midPt]);

  return(xMED);
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief LUsolve
//!
//! Solve linear system using LU decomposition or SVD
//!
//////////////////////////////////////////////////////////////////////////////
bool solveLinSys(const std::vector<std::vector<double>> &Av,
               const std::vector<double> &bv, std::vector<double> &xv,const bool isSVD)
{
  bool isIllCond=false;
  double minCondNum=100.0*std::numeric_limits<double>::epsilon();

  int i,j,dim=(int)bv.size();

  MatrixXd A(dim,dim);
  VectorXd b(dim),x(dim);

  for(i=0;i<dim;i++)
  {
    for(j=0;j<dim;j++) A(i,j)=Av[i][j];
    b[i]=bv[i];
  }

  if(isSVD)
  {
    JacobiSVD<MatrixXd> A_svd(A, ComputeThinU | ComputeThinV);
    double condNum = A_svd.singularValues()(0)
        / A_svd.singularValues()(A_svd.singularValues().size()-1);
    if(condNum<minCondNum) {isIllCond=true; return isIllCond;}
    x=A_svd.solve(b);
  }
  else x = A.lu().solve(b);

  for(i=0;i<dim;i++) xv[i]=x[i];
  return isIllCond;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief remClosePts
//!
//! remove close pts in input data
//!
//////////////////////////////////////////////////////////////////////////////
int remClosePts(std::vector<std::vector<double>> &x,
                    std::vector<std::vector<double>> &y,
                    double xThresh)
{
  printf("remClosePts():  ||dtheta||_min=%f deg imposed. ",xThresh);

  double dthetaThreshSQ=xThresh*xThresh;
  double dtheta,dthetaSQsum;
  bool anyRem=false;
  int i,j,curPt;
  int nPts=(int)x[0].size();
  int nPtsi=nPts;
  std::vector<double> isRem(nPts);

  int xLen=(int)x.size();
  int yLen=(int)y.size();

  // A minimum point-to-point distance threshold is imposed
  // At each iteration, only non-adjacent points can be designated for removal
  while(1)
  {
    anyRem=false;

    // Points are tagged for removal
    for(i=1;i<nPts;i++)
    {
      dthetaSQsum=0;
      for(j=0;j<xLen;j++)
      {
        dtheta=x[j][i]-x[j][i-1];
        dthetaSQsum+=dtheta*dtheta;
      }
      if(dthetaSQsum<dthetaThreshSQ && !isRem[i-1])
      {
        isRem[i]=true;
        anyRem=true;
      }
    }
    if(isRem[nPts-1] && nPts>2)
    {
      isRem[nPts-1]=false;
      isRem[nPts-2]=true;
      isRem[nPts-3]=false;
    }
    curPt=0;
    if(anyRem)
    {
      // Tagged points are removed from the trajectory
      for(i=0;i<nPts;i++)
      {
        if(!isRem[i])
        {
          for(j=0;j<xLen;j++) x[j][curPt]= x[j][i];
          if(yLen>0)
          {
             for(j=0;j<yLen;j++) y[j][curPt]= y[j][i];
          }
          curPt++;
        }
      }
      nPts=curPt;
      for(j=0;j<xLen;j++) x[j].resize(curPt);
      if(yLen>0)
      {
         for(j=0;j<yLen;j++) y[j].resize(curPt);
      }
      isRem.assign(nPts,false);
    }
    else break;
  }
  printf(" before %d points; after %d points\n",nPtsi,nPts);
  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief aa2q
//!
//! axis-angle to quaternion orientation
//!
//////////////////////////////////////////////////////////////////////////////
std::array<double,4> aa2q(std::array<double,3> aa)
{
   std::array<double,4> q;
   double theta=std::sqrt(aa[0]*aa[0] + aa[1]*aa[1] + aa[2]*aa[2]);

   if(theta<1e-6)
   {
      q={1.0,0.0,0.0,0.0};
   }
   else
   {
      double sin_half_theta=std::sin(0.5*theta);
      q[0]=std::cos(0.5*theta);
      for(int i=0; i<3; i++)
      {
         q[i+1]=aa[i]*sin_half_theta/theta;
      }
   }
   return q;
}

//////////////////////////////////////////////////////////////////////////////
//!
//! \brief q2aa
//!
//! quaternion to axis-angle orientation
//!
//////////////////////////////////////////////////////////////////////////////
std::array<double,3> q2aa(std::array<double,4> q)
{
   std::array<double,3> aa;
   double norme=std::sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);

   if(norme<1e-6)
   {
      aa={0.0,0.0,0.0};
   }
   else
   {
      double theta=2.0*std::atan2(norme,q[0])/norme;
      for(int i=0; i<3; i++)
      {
         aa[i]=theta*q[i+1];
      }
   }
   return aa;
}


// $Id$
//
//   @@ 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.
//
//  Contribution from Roger Sayle
#include <vector>
#include <DataStructs/ExplicitBitVect.h>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/Substruct/SubstructMatch.h>
#include <GraphMol/MolOps.h>

#include <RDGeneral/BoostStartInclude.h>
#include <boost/flyweight.hpp>
#include <boost/flyweight/no_tracking.hpp>
#include <RDGeneral/BoostEndInclude.h>

namespace {
struct Patterns {
  RDKit::ROMol *bit_8;
  RDKit::ROMol *bit_11;
  RDKit::ROMol *bit_13;
  RDKit::ROMol *bit_14;
  RDKit::ROMol *bit_15;
  RDKit::ROMol *bit_16;
  RDKit::ROMol *bit_17;
  RDKit::ROMol *bit_19;
  RDKit::ROMol *bit_20;
  RDKit::ROMol *bit_21;
  RDKit::ROMol *bit_22;
  RDKit::ROMol *bit_23;
  RDKit::ROMol *bit_24;
  RDKit::ROMol *bit_25;
  RDKit::ROMol *bit_26;
  RDKit::ROMol *bit_28;
  RDKit::ROMol *bit_30;
  RDKit::ROMol *bit_31;
  RDKit::ROMol *bit_32;
  RDKit::ROMol *bit_33;
  RDKit::ROMol *bit_34;
  RDKit::ROMol *bit_36;
  RDKit::ROMol *bit_37;
  RDKit::ROMol *bit_38;
  RDKit::ROMol *bit_39;
  RDKit::ROMol *bit_40;
  RDKit::ROMol *bit_41;
  RDKit::ROMol *bit_43;
  RDKit::ROMol *bit_44;
  RDKit::ROMol *bit_45;
  RDKit::ROMol *bit_47;
  RDKit::ROMol *bit_48;
  RDKit::ROMol *bit_49;
  RDKit::ROMol *bit_50;
  RDKit::ROMol *bit_51;
  RDKit::ROMol *bit_52;
  RDKit::ROMol *bit_53;
  RDKit::ROMol *bit_54;
  RDKit::ROMol *bit_55;
  RDKit::ROMol *bit_56;
  RDKit::ROMol *bit_57;
  RDKit::ROMol *bit_58;
  RDKit::ROMol *bit_59;
  RDKit::ROMol *bit_60;
  RDKit::ROMol *bit_61;
  RDKit::ROMol *bit_62;
  RDKit::ROMol *bit_63;
  RDKit::ROMol *bit_64;
  RDKit::ROMol *bit_65;
  RDKit::ROMol *bit_66;
  RDKit::ROMol *bit_67;
  RDKit::ROMol *bit_68;
  RDKit::ROMol *bit_69;
  RDKit::ROMol *bit_70;
  RDKit::ROMol *bit_71;
  RDKit::ROMol *bit_72;
  RDKit::ROMol *bit_73;
  RDKit::ROMol *bit_74;
  RDKit::ROMol *bit_75;
  RDKit::ROMol *bit_76;
  RDKit::ROMol *bit_77;
  RDKit::ROMol *bit_78;
  RDKit::ROMol *bit_79;
  RDKit::ROMol *bit_80;
  RDKit::ROMol *bit_81;
  RDKit::ROMol *bit_82;
  RDKit::ROMol *bit_83;
  RDKit::ROMol *bit_84;
  RDKit::ROMol *bit_85;
  RDKit::ROMol *bit_86;
  RDKit::ROMol *bit_87;
  RDKit::ROMol *bit_89;
  RDKit::ROMol *bit_90;
  RDKit::ROMol *bit_91;
  RDKit::ROMol *bit_92;
  RDKit::ROMol *bit_93;
  RDKit::ROMol *bit_94;
  RDKit::ROMol *bit_95;
  RDKit::ROMol *bit_96;
  RDKit::ROMol *bit_97;
  RDKit::ROMol *bit_98;
  RDKit::ROMol *bit_99;
  RDKit::ROMol *bit_100;
  RDKit::ROMol *bit_101;
  RDKit::ROMol *bit_102;
  RDKit::ROMol *bit_104;
  RDKit::ROMol *bit_105;
  RDKit::ROMol *bit_106;
  RDKit::ROMol *bit_107;
  RDKit::ROMol *bit_108;
  RDKit::ROMol *bit_109;
  RDKit::ROMol *bit_110;
  RDKit::ROMol *bit_111;
  RDKit::ROMol *bit_112;
  RDKit::ROMol *bit_113;
  RDKit::ROMol *bit_114;
  RDKit::ROMol *bit_115;
  RDKit::ROMol *bit_116;
  RDKit::ROMol *bit_117;
  RDKit::ROMol *bit_118;
  RDKit::ROMol *bit_119;
  RDKit::ROMol *bit_120;
  RDKit::ROMol *bit_121;
  RDKit::ROMol *bit_122;
  RDKit::ROMol *bit_123;
  RDKit::ROMol *bit_124;
  RDKit::ROMol *bit_126;
  RDKit::ROMol *bit_127;
  RDKit::ROMol *bit_128;
  RDKit::ROMol *bit_129;
  RDKit::ROMol *bit_131;
  RDKit::ROMol *bit_132;
  RDKit::ROMol *bit_133;
  RDKit::ROMol *bit_135;
  RDKit::ROMol *bit_136;
  RDKit::ROMol *bit_137;
  RDKit::ROMol *bit_138;
  RDKit::ROMol *bit_139;
  RDKit::ROMol *bit_140;
  RDKit::ROMol *bit_141;
  RDKit::ROMol *bit_142;
  RDKit::ROMol *bit_144;
  RDKit::ROMol *bit_145;
  RDKit::ROMol *bit_147;
  RDKit::ROMol *bit_148;
  RDKit::ROMol *bit_149;
  RDKit::ROMol *bit_150;
  RDKit::ROMol *bit_151;
  RDKit::ROMol *bit_152;
  RDKit::ROMol *bit_154;
  RDKit::ROMol *bit_155;
  RDKit::ROMol *bit_156;
  RDKit::ROMol *bit_157;
  RDKit::ROMol *bit_158;
  RDKit::ROMol *bit_162;
  RDKit::ROMol *bit_165;
  Patterns()
      : bit_8(RDKit::SmartsToMol("[!#6!#1]1~*~*~*~1")),
        bit_11(RDKit::SmartsToMol("*1~*~*~*~1")),
        bit_13(RDKit::SmartsToMol("[#8]~[#7](~[#6])~[#6]")),
        bit_14(RDKit::SmartsToMol("[#16]-[#16]")),
        bit_15(RDKit::SmartsToMol("[#8]~[#6](~[#8])~[#8]")),
        bit_16(RDKit::SmartsToMol("[!#6!#1]1~*~*~1")),
        bit_17(RDKit::SmartsToMol("[#6]#[#6]")),
        bit_19(RDKit::SmartsToMol("*1~*~*~*~*~*~*~1")),
        bit_20(RDKit::SmartsToMol("[#14]")),
        bit_21(RDKit::SmartsToMol("[#6]=[#6](~[!#6!#1])~[!#6!#1]")),
        bit_22(RDKit::SmartsToMol("*1~*~*~1")),
        bit_23(RDKit::SmartsToMol("[#7]~[#6](~[#8])~[#8]")),
        bit_24(RDKit::SmartsToMol("[#7]-[#8]")),
        bit_25(RDKit::SmartsToMol("[#7]~[#6](~[#7])~[#7]")),
        bit_26(RDKit::SmartsToMol("[#6]=@[#6](@*)@*")),
        bit_28(RDKit::SmartsToMol("[!#6!#1]~[CH2]~[!#6!#1]")),
        bit_30(RDKit::SmartsToMol("[#6]~[!#6!#1](~[#6])(~[#6])~*")),
        bit_31(RDKit::SmartsToMol("[!#6!#1]~[F,Cl,Br,I]")),
        bit_32(RDKit::SmartsToMol("[#6]~[#16]~[#7]")),
        bit_33(RDKit::SmartsToMol("[#7]~[#16]")),
        bit_34(RDKit::SmartsToMol("[CH2]=*")),
        bit_36(RDKit::SmartsToMol("[#16R]")),
        bit_37(RDKit::SmartsToMol("[#7]~[#6](~[#8])~[#7]")),
        bit_38(RDKit::SmartsToMol("[#7]~[#6](~[#6])~[#7]")),
        bit_39(RDKit::SmartsToMol("[#8]~[#16](~[#8])~[#8]")),
        bit_40(RDKit::SmartsToMol("[#16]-[#8]")),
        bit_41(RDKit::SmartsToMol("[#6]#[#7]")),
        bit_43(RDKit::SmartsToMol("[!#6!#1!H0]~*~[!#6!#1!H0]")),
        bit_44(RDKit::SmartsToMol(
            "[!#1;!#6;!#7;!#8;!#9;!#14;!#15;!#16;!#17;!#35;!#53]")),
        bit_45(RDKit::SmartsToMol("[#6]=[#6]~[#7]")),
        bit_47(RDKit::SmartsToMol("[#16]~*~[#7]")),
        bit_48(RDKit::SmartsToMol("[#8]~[!#6!#1](~[#8])~[#8]")),
        bit_49(RDKit::SmartsToMol("[!+0]")),
        bit_50(RDKit::SmartsToMol("[#6]=[#6](~[#6])~[#6]")),
        bit_51(RDKit::SmartsToMol("[#6]~[#16]~[#8]")),
        bit_52(RDKit::SmartsToMol("[#7]~[#7]")),
        bit_53(RDKit::SmartsToMol("[!#6!#1!H0]~*~*~*~[!#6!#1!H0]")),
        bit_54(RDKit::SmartsToMol("[!#6!#1!H0]~*~*~[!#6!#1!H0]")),
        bit_55(RDKit::SmartsToMol("[#8]~[#16]~[#8]")),
        bit_56(RDKit::SmartsToMol("[#8]~[#7](~[#8])~[#6]")),
        bit_57(RDKit::SmartsToMol("[#8R]")),
        bit_58(RDKit::SmartsToMol("[!#6!#1]~[#16]~[!#6!#1]")),
        bit_59(RDKit::SmartsToMol("[#16]!:*:*")),
        bit_60(RDKit::SmartsToMol("[#16]=[#8]")),
        bit_61(RDKit::SmartsToMol("*~[#16](~*)~*")),
        bit_62(RDKit::SmartsToMol("*@*!@*@*")),
        bit_63(RDKit::SmartsToMol("[#7]=[#8]")),
        bit_64(RDKit::SmartsToMol("*@*!@[#16]")),
        bit_65(RDKit::SmartsToMol("c:n")),
        bit_66(RDKit::SmartsToMol("[#6]~[#6](~[#6])(~[#6])~*")),
        bit_67(RDKit::SmartsToMol("[!#6!#1]~[#16]")),
        bit_68(RDKit::SmartsToMol("[!#6!#1!H0]~[!#6!#1!H0]")),
        bit_69(RDKit::SmartsToMol("[!#6!#1]~[!#6!#1!H0]")),
        bit_70(RDKit::SmartsToMol("[!#6!#1]~[#7]~[!#6!#1]")),
        bit_71(RDKit::SmartsToMol("[#7]~[#8]")),
        bit_72(RDKit::SmartsToMol("[#8]~*~*~[#8]")),
        bit_73(RDKit::SmartsToMol("[#16]=*")),
        bit_74(RDKit::SmartsToMol("[CH3]~*~[CH3]")),
        bit_75(RDKit::SmartsToMol("*!@[#7]@*")),
        bit_76(RDKit::SmartsToMol("[#6]=[#6](~*)~*")),
        bit_77(RDKit::SmartsToMol("[#7]~*~[#7]")),
        bit_78(RDKit::SmartsToMol("[#6]=[#7]")),
        bit_79(RDKit::SmartsToMol("[#7]~*~*~[#7]")),
        bit_80(RDKit::SmartsToMol("[#7]~*~*~*~[#7]")),
        bit_81(RDKit::SmartsToMol("[#16]~*(~*)~*")),
        bit_82(RDKit::SmartsToMol("*~[CH2]~[!#6!#1!H0]")),
        bit_83(RDKit::SmartsToMol("[!#6!#1]1~*~*~*~*~1")),
        bit_84(RDKit::SmartsToMol("[NH2]")),
        bit_85(RDKit::SmartsToMol("[#6]~[#7](~[#6])~[#6]")),
        bit_86(RDKit::SmartsToMol("[C;H2,H3][!#6!#1][C;H2,H3]")),
        bit_87(RDKit::SmartsToMol("[F,Cl,Br,I]!@*@*")),
        bit_89(RDKit::SmartsToMol("[#8]~*~*~*~[#8]")),
        bit_90(RDKit::SmartsToMol(
            "[$([!#6!#1!H0]~*~*~[CH2]~*),$([!#6!#1!H0R]1@[R]@[R]@[CH2R]1),$([!#"
            "6!#1!H0]~[R]1@[R]@[CH2R]1)]")),
        bit_91(RDKit::SmartsToMol(
            "[$([!#6!#1!H0]~*~*~*~[CH2]~*),$([!#6!#1!H0R]1@[R]@[R]@[R]@[CH2R]1)"
            ",$([!#6!#1!H0]~[R]1@[R]@[R]@[CH2R]1),$([!#6!#1!H0]~*~[R]1@[R]@["
            "CH2R]1)]")),
        bit_92(RDKit::SmartsToMol("[#8]~[#6](~[#7])~[#6]")),
        bit_93(RDKit::SmartsToMol("[!#6!#1]~[CH3]")),
        bit_94(RDKit::SmartsToMol("[!#6!#1]~[#7]")),
        bit_95(RDKit::SmartsToMol("[#7]~*~*~[#8]")),
        bit_96(RDKit::SmartsToMol("*1~*~*~*~*~1")),
        bit_97(RDKit::SmartsToMol("[#7]~*~*~*~[#8]")),
        bit_98(RDKit::SmartsToMol("[!#6!#1]1~*~*~*~*~*~1")),
        bit_99(RDKit::SmartsToMol("[#6]=[#6]")),
        bit_100(RDKit::SmartsToMol("*~[CH2]~[#7]")),
        bit_101(RDKit::SmartsToMol(
            "[$([R]1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1),$([R]1@[R]@[R]@[R]@[R]@[R]@"
            "[R]@[R]@[R]@1),$([R]1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1),$([R]"
            "1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1),$([R]1@[R]@[R]@[R]@["
            "R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1),$([R]1@[R]@[R]@[R]@[R]@[R]@[R]@["
            "R]@[R]@[R]@[R]@[R]@[R]@1),$([R]1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@["
            "R]@[R]@[R]@[R]@[R]@1)]")),
        bit_102(RDKit::SmartsToMol("[!#6!#1]~[#8]")),
        bit_104(RDKit::SmartsToMol("[!#6!#1!H0]~*~[CH2]~*")),
        bit_105(RDKit::SmartsToMol("*@*(@*)@*")),
        bit_106(RDKit::SmartsToMol("[!#6!#1]~*(~[!#6!#1])~[!#6!#1]")),
        bit_107(RDKit::SmartsToMol("[F,Cl,Br,I]~*(~*)~*")),
        bit_108(RDKit::SmartsToMol("[CH3]~*~*~*~[CH2]~*")),
        bit_109(RDKit::SmartsToMol("*~[CH2]~[#8]")),
        bit_110(RDKit::SmartsToMol("[#7]~[#6]~[#8]")),
        bit_111(RDKit::SmartsToMol("[#7]~*~[CH2]~*")),
        bit_112(RDKit::SmartsToMol("*~*(~*)(~*)~*")),
        bit_113(RDKit::SmartsToMol("[#8]!:*:*")),
        bit_114(RDKit::SmartsToMol("[CH3]~[CH2]~*")),
        bit_115(RDKit::SmartsToMol("[CH3]~*~[CH2]~*")),
        bit_116(
            RDKit::SmartsToMol("[$([CH3]~*~*~[CH2]~*),$([CH3]~*1~*~[CH2]1)]")),
        bit_117(RDKit::SmartsToMol("[#7]~*~[#8]")),
        bit_118(RDKit::SmartsToMol("[$(*~[CH2]~[CH2]~*),$(*1~[CH2]~[CH2]1)]")),
        bit_119(RDKit::SmartsToMol("[#7]=*")),
        bit_120(RDKit::SmartsToMol("[!#6R]")),
        bit_121(RDKit::SmartsToMol("[#7R]")),
        bit_122(RDKit::SmartsToMol("*~[#7](~*)~*")),
        bit_123(RDKit::SmartsToMol("[#8]~[#6]~[#8]")),
        bit_124(RDKit::SmartsToMol("[!#6!#1]~[!#6!#1]")),
        bit_126(RDKit::SmartsToMol("*!@[#8]!@*")),
        bit_127(RDKit::SmartsToMol("*@*!@[#8]")),
        bit_128(RDKit::SmartsToMol(
            "[$(*~[CH2]~*~*~*~[CH2]~*),$([R]1@[CH2R]@[R]@[R]@[R]@[CH2R]1),$(*~["
            "CH2]~[R]1@[R]@[R]@[CH2R]1),$(*~[CH2]~*~[R]1@[R]@[CH2R]1)]")),
        bit_129(RDKit::SmartsToMol(
            "[$(*~[CH2]~*~*~[CH2]~*),$([R]1@[CH2]@[R]@[R]@[CH2R]1),$(*~[CH2]~["
            "R]1@[R]@[CH2R]1)]")),
        bit_131(RDKit::SmartsToMol("[!#6!#1!H0]")),
        bit_132(RDKit::SmartsToMol("[#8]~*~[CH2]~*")),
        bit_133(RDKit::SmartsToMol("*@*!@[#7]")),
        bit_135(RDKit::SmartsToMol("[#7]!:*:*")),
        bit_136(RDKit::SmartsToMol("[#8]=*")),
        bit_137(RDKit::SmartsToMol("[!C!cR]")),
        bit_138(RDKit::SmartsToMol("[!#6!#1]~[CH2]~*")),
        bit_139(RDKit::SmartsToMol("[O!H0]")),
        bit_140(RDKit::SmartsToMol("[#8]")),
        bit_141(RDKit::SmartsToMol("[CH3]")),
        bit_142(RDKit::SmartsToMol("[#7]")),
        bit_144(RDKit::SmartsToMol("*!:*:*!:*")),
        bit_145(RDKit::SmartsToMol("*1~*~*~*~*~*~1")),
        bit_147(
            RDKit::SmartsToMol("[$(*~[CH2]~[CH2]~*),$([R]1@[CH2R]@[CH2R]1)]")),
        bit_148(RDKit::SmartsToMol("*~[!#6!#1](~*)~*")),
        bit_149(RDKit::SmartsToMol("[C;H3,H4]")),
        bit_150(RDKit::SmartsToMol("*!@*@*!@*")),
        bit_151(RDKit::SmartsToMol("[#7!H0]")),
        bit_152(RDKit::SmartsToMol("[#8]~[#6](~[#6])~[#6]")),
        bit_154(RDKit::SmartsToMol("[#6]=[#8]")),
        bit_155(RDKit::SmartsToMol("*!@[CH2]!@*")),
        bit_156(RDKit::SmartsToMol("[#7]~*(~*)~*")),
        bit_157(RDKit::SmartsToMol("[#6]-[#8]")),
        bit_158(RDKit::SmartsToMol("[#6]-[#7]")),
        bit_162(RDKit::SmartsToMol("a")),
        bit_165(RDKit::SmartsToMol("[R]")) {}
};

boost::flyweight<std::vector<Patterns *>, boost::flyweights::no_tracking> gpats;
void GenerateFP(const RDKit::ROMol &mol, ExplicitBitVect &fp) {
  if (gpats.get().size() == 0) {
    std::vector<Patterns *> ps;
    ps.push_back(new Patterns());
    gpats = ps;
  }
  const Patterns &pats = *(gpats.get().front());
  PRECONDITION(fp.size() == 167, "bad fingerprint");
  fp.clearBits();

  if (!mol.getNumAtoms()) return;

  std::vector<RDKit::MatchVectType> matches;
  RDKit::RWMol::ConstAtomIterator atom;
  RDKit::MatchVectType match;
  unsigned int count;

  for (atom = mol.beginAtoms(); atom != mol.endAtoms(); ++atom)
    switch ((*atom)->getAtomicNum()) {
      case 3:
      case 11:
      case 19:
      case 37:
      case 55:
      case 87:
        fp.setBit(35);
        break;
      case 4:
      case 12:
      case 20:
      case 38:
      case 56:
      case 88:
        fp.setBit(10);
        break;
      case 5:
      case 13:
      case 31:
      case 49:
      case 81:
        fp.setBit(18);
        break;
      case 9:
        fp.setBit(42);
        fp.setBit(134);
        break;
      case 15:
        fp.setBit(29);
        break;
      case 16:
        fp.setBit(88);
        break;
      case 17:
        fp.setBit(103);
        fp.setBit(134);
        break;
      case 21:
      case 22:
      case 39:
      case 40:
      case 72:
        fp.setBit(5);
        break;
      case 23:
      case 24:
      case 25:
      case 41:
      case 42:
      case 43:
      case 73:
      case 74:
      case 75:
        fp.setBit(7);
        break;
      case 26:
      case 27:
      case 28:
      case 44:
      case 45:
      case 46:
      case 76:
      case 77:
      case 78:
        fp.setBit(9);
        break;
      case 29:
      case 30:
      case 47:
      case 48:
      case 79:
      case 80:
        fp.setBit(12);
        break;
      case 32:
      case 33:
      case 34:
      case 50:
      case 51:
      case 52:
      case 82:
      case 83:
      case 84:
        fp.setBit(3);
        break;
      case 35:
        fp.setBit(46);
        fp.setBit(134);
        break;
      case 53:
        fp.setBit(27);
        fp.setBit(134);
        break;
      case 57:
      case 58:
      case 59:
      case 60:
      case 61:
      case 62:
      case 63:
      case 64:
      case 65:
      case 66:
      case 67:
      case 68:
      case 69:
      case 70:
      case 71:
        fp.setBit(6);
        break;
      case 89:
      case 90:
      case 91:
      case 92:
      case 93:
      case 94:
      case 95:
      case 96:
      case 97:
      case 98:
      case 99:
      case 100:
      case 101:
      case 102:
      case 103:
        fp.setBit(4);
        break;
      case 104:
        fp.setBit(2);
        break;
    }

  if (RDKit::SubstructMatch(mol, *pats.bit_8, match, true)) fp.setBit(8);
  if (RDKit::SubstructMatch(mol, *pats.bit_11, match, true)) fp.setBit(11);
  if (RDKit::SubstructMatch(mol, *pats.bit_13, match, true)) fp.setBit(13);
  if (RDKit::SubstructMatch(mol, *pats.bit_14, match, true)) fp.setBit(14);
  if (RDKit::SubstructMatch(mol, *pats.bit_15, match, true)) fp.setBit(15);
  if (RDKit::SubstructMatch(mol, *pats.bit_16, match, true)) fp.setBit(16);
  if (RDKit::SubstructMatch(mol, *pats.bit_17, match, true)) fp.setBit(17);
  if (RDKit::SubstructMatch(mol, *pats.bit_19, match, true)) fp.setBit(19);
  if (RDKit::SubstructMatch(mol, *pats.bit_20, match, true)) fp.setBit(20);
  if (RDKit::SubstructMatch(mol, *pats.bit_21, match, true)) fp.setBit(21);
  if (RDKit::SubstructMatch(mol, *pats.bit_22, match, true)) fp.setBit(22);
  if (RDKit::SubstructMatch(mol, *pats.bit_23, match, true)) fp.setBit(23);
  if (RDKit::SubstructMatch(mol, *pats.bit_24, match, true)) fp.setBit(24);
  if (RDKit::SubstructMatch(mol, *pats.bit_25, match, true)) fp.setBit(25);
  if (RDKit::SubstructMatch(mol, *pats.bit_26, match, true)) fp.setBit(26);
  if (RDKit::SubstructMatch(mol, *pats.bit_28, match, true)) fp.setBit(28);
  if (RDKit::SubstructMatch(mol, *pats.bit_30, match, true)) fp.setBit(30);
  if (RDKit::SubstructMatch(mol, *pats.bit_31, match, true)) fp.setBit(31);
  if (RDKit::SubstructMatch(mol, *pats.bit_32, match, true)) fp.setBit(32);
  if (RDKit::SubstructMatch(mol, *pats.bit_33, match, true)) fp.setBit(33);
  if (RDKit::SubstructMatch(mol, *pats.bit_34, match, true)) fp.setBit(34);
  if (RDKit::SubstructMatch(mol, *pats.bit_36, match, true)) fp.setBit(36);
  if (RDKit::SubstructMatch(mol, *pats.bit_37, match, true)) fp.setBit(37);
  if (RDKit::SubstructMatch(mol, *pats.bit_38, match, true)) fp.setBit(38);
  if (RDKit::SubstructMatch(mol, *pats.bit_39, match, true)) fp.setBit(39);
  if (RDKit::SubstructMatch(mol, *pats.bit_40, match, true)) fp.setBit(40);
  if (RDKit::SubstructMatch(mol, *pats.bit_41, match, true)) fp.setBit(41);
  if (RDKit::SubstructMatch(mol, *pats.bit_43, match, true)) fp.setBit(43);
  if (RDKit::SubstructMatch(mol, *pats.bit_44, match, true)) fp.setBit(44);
  if (RDKit::SubstructMatch(mol, *pats.bit_45, match, true)) fp.setBit(45);
  if (RDKit::SubstructMatch(mol, *pats.bit_47, match, true)) fp.setBit(47);
  if (RDKit::SubstructMatch(mol, *pats.bit_48, match, true)) fp.setBit(48);
  if (RDKit::SubstructMatch(mol, *pats.bit_49, match, true)) fp.setBit(49);
  if (RDKit::SubstructMatch(mol, *pats.bit_50, match, true)) fp.setBit(50);
  if (RDKit::SubstructMatch(mol, *pats.bit_51, match, true)) fp.setBit(51);
  if (RDKit::SubstructMatch(mol, *pats.bit_52, match, true)) fp.setBit(52);
  if (RDKit::SubstructMatch(mol, *pats.bit_53, match, true)) fp.setBit(53);
  if (RDKit::SubstructMatch(mol, *pats.bit_54, match, true)) fp.setBit(54);
  if (RDKit::SubstructMatch(mol, *pats.bit_55, match, true)) fp.setBit(55);
  if (RDKit::SubstructMatch(mol, *pats.bit_56, match, true)) fp.setBit(56);
  if (RDKit::SubstructMatch(mol, *pats.bit_57, match, true)) fp.setBit(57);
  if (RDKit::SubstructMatch(mol, *pats.bit_58, match, true)) fp.setBit(58);
  if (RDKit::SubstructMatch(mol, *pats.bit_59, match, true)) fp.setBit(59);
  if (RDKit::SubstructMatch(mol, *pats.bit_60, match, true)) fp.setBit(60);
  if (RDKit::SubstructMatch(mol, *pats.bit_61, match, true)) fp.setBit(61);
  if (RDKit::SubstructMatch(mol, *pats.bit_62, match, true)) fp.setBit(62);
  if (RDKit::SubstructMatch(mol, *pats.bit_63, match, true)) fp.setBit(63);
  if (RDKit::SubstructMatch(mol, *pats.bit_64, match, true)) fp.setBit(64);
  if (RDKit::SubstructMatch(mol, *pats.bit_65, match, true)) fp.setBit(65);
  if (RDKit::SubstructMatch(mol, *pats.bit_66, match, true)) fp.setBit(66);
  if (RDKit::SubstructMatch(mol, *pats.bit_67, match, true)) fp.setBit(67);
  if (RDKit::SubstructMatch(mol, *pats.bit_68, match, true)) fp.setBit(68);
  if (RDKit::SubstructMatch(mol, *pats.bit_69, match, true)) fp.setBit(69);
  if (RDKit::SubstructMatch(mol, *pats.bit_70, match, true)) fp.setBit(70);
  if (RDKit::SubstructMatch(mol, *pats.bit_71, match, true)) fp.setBit(71);
  if (RDKit::SubstructMatch(mol, *pats.bit_72, match, true)) fp.setBit(72);
  if (RDKit::SubstructMatch(mol, *pats.bit_73, match, true)) fp.setBit(73);
  if (RDKit::SubstructMatch(mol, *pats.bit_74, match, true)) fp.setBit(74);
  if (RDKit::SubstructMatch(mol, *pats.bit_75, match, true)) fp.setBit(75);
  if (RDKit::SubstructMatch(mol, *pats.bit_76, match, true)) fp.setBit(76);
  if (RDKit::SubstructMatch(mol, *pats.bit_77, match, true)) fp.setBit(77);
  if (RDKit::SubstructMatch(mol, *pats.bit_78, match, true)) fp.setBit(78);
  if (RDKit::SubstructMatch(mol, *pats.bit_79, match, true)) fp.setBit(79);
  if (RDKit::SubstructMatch(mol, *pats.bit_80, match, true)) fp.setBit(80);
  if (RDKit::SubstructMatch(mol, *pats.bit_81, match, true)) fp.setBit(81);
  if (RDKit::SubstructMatch(mol, *pats.bit_82, match, true)) fp.setBit(82);
  if (RDKit::SubstructMatch(mol, *pats.bit_83, match, true)) fp.setBit(83);
  if (RDKit::SubstructMatch(mol, *pats.bit_84, match, true)) fp.setBit(84);
  if (RDKit::SubstructMatch(mol, *pats.bit_85, match, true)) fp.setBit(85);
  if (RDKit::SubstructMatch(mol, *pats.bit_86, match, true)) fp.setBit(86);
  if (RDKit::SubstructMatch(mol, *pats.bit_87, match, true)) fp.setBit(87);
  if (RDKit::SubstructMatch(mol, *pats.bit_89, match, true)) fp.setBit(89);
  if (RDKit::SubstructMatch(mol, *pats.bit_90, match, true)) fp.setBit(90);
  if (RDKit::SubstructMatch(mol, *pats.bit_91, match, true)) fp.setBit(91);
  if (RDKit::SubstructMatch(mol, *pats.bit_92, match, true)) fp.setBit(92);
  if (RDKit::SubstructMatch(mol, *pats.bit_93, match, true)) fp.setBit(93);
  if (RDKit::SubstructMatch(mol, *pats.bit_94, match, true)) fp.setBit(94);
  if (RDKit::SubstructMatch(mol, *pats.bit_95, match, true)) fp.setBit(95);
  if (RDKit::SubstructMatch(mol, *pats.bit_96, match, true)) fp.setBit(96);
  if (RDKit::SubstructMatch(mol, *pats.bit_97, match, true)) fp.setBit(97);
  if (RDKit::SubstructMatch(mol, *pats.bit_98, match, true)) fp.setBit(98);
  if (RDKit::SubstructMatch(mol, *pats.bit_99, match, true)) fp.setBit(99);
  if (RDKit::SubstructMatch(mol, *pats.bit_100, match, true)) fp.setBit(100);
  if (RDKit::SubstructMatch(mol, *pats.bit_101, match, true)) fp.setBit(101);
  if (RDKit::SubstructMatch(mol, *pats.bit_102, match, true)) fp.setBit(102);
  if (RDKit::SubstructMatch(mol, *pats.bit_104, match, true)) fp.setBit(104);
  if (RDKit::SubstructMatch(mol, *pats.bit_105, match, true)) fp.setBit(105);
  if (RDKit::SubstructMatch(mol, *pats.bit_106, match, true)) fp.setBit(106);
  if (RDKit::SubstructMatch(mol, *pats.bit_107, match, true)) fp.setBit(107);
  if (RDKit::SubstructMatch(mol, *pats.bit_108, match, true)) fp.setBit(108);
  if (RDKit::SubstructMatch(mol, *pats.bit_109, match, true)) fp.setBit(109);
  if (RDKit::SubstructMatch(mol, *pats.bit_110, match, true)) fp.setBit(110);
  if (RDKit::SubstructMatch(mol, *pats.bit_111, match, true)) fp.setBit(111);
  if (RDKit::SubstructMatch(mol, *pats.bit_112, match, true)) fp.setBit(112);
  if (RDKit::SubstructMatch(mol, *pats.bit_113, match, true)) fp.setBit(113);
  if (RDKit::SubstructMatch(mol, *pats.bit_114, match, true)) fp.setBit(114);
  if (RDKit::SubstructMatch(mol, *pats.bit_115, match, true)) fp.setBit(115);
  if (RDKit::SubstructMatch(mol, *pats.bit_116, match, true)) fp.setBit(116);
  if (RDKit::SubstructMatch(mol, *pats.bit_117, match, true)) fp.setBit(117);
  if (RDKit::SubstructMatch(mol, *pats.bit_118, matches, true, true) > 1)
    fp.setBit(118);
  if (RDKit::SubstructMatch(mol, *pats.bit_119, match, true)) fp.setBit(119);
  if (RDKit::SubstructMatch(mol, *pats.bit_120, matches, true, true) > 1)
    fp.setBit(120);
  if (RDKit::SubstructMatch(mol, *pats.bit_121, match, true)) fp.setBit(121);
  if (RDKit::SubstructMatch(mol, *pats.bit_122, match, true)) fp.setBit(122);
  if (RDKit::SubstructMatch(mol, *pats.bit_123, match, true)) fp.setBit(123);
  count = RDKit::SubstructMatch(mol, *pats.bit_124, matches, true, true);
  if (count > 0) fp.setBit(124);
  if (count > 1) fp.setBit(130);
  if (RDKit::SubstructMatch(mol, *pats.bit_126, match, true)) fp.setBit(126);
  count = RDKit::SubstructMatch(mol, *pats.bit_127, matches, true, true);
  if (count > 1) fp.setBit(127);
  if (count > 0) fp.setBit(143);
  if (RDKit::SubstructMatch(mol, *pats.bit_128, match, true)) fp.setBit(128);
  if (RDKit::SubstructMatch(mol, *pats.bit_129, match, true)) fp.setBit(129);
  if (RDKit::SubstructMatch(mol, *pats.bit_131, matches, true, true) > 1)
    fp.setBit(131);
  if (RDKit::SubstructMatch(mol, *pats.bit_132, match, true)) fp.setBit(132);
  if (RDKit::SubstructMatch(mol, *pats.bit_133, match, true)) fp.setBit(133);
  if (RDKit::SubstructMatch(mol, *pats.bit_135, match, true)) fp.setBit(135);
  if (RDKit::SubstructMatch(mol, *pats.bit_136, matches, true, true) > 1)
    fp.setBit(136);
  if (RDKit::SubstructMatch(mol, *pats.bit_137, match, true)) fp.setBit(137);
  count = RDKit::SubstructMatch(mol, *pats.bit_138, matches, true, true);
  if (count > 1) fp.setBit(138);
  if (count > 0) fp.setBit(153);
  if (RDKit::SubstructMatch(mol, *pats.bit_139, match, true)) fp.setBit(139);
  count = RDKit::SubstructMatch(mol, *pats.bit_140, matches, true, true);
  if (count > 3) fp.setBit(140);
  if (count > 2) fp.setBit(146);
  if (count > 1) fp.setBit(159);
  if (count > 0) fp.setBit(164);
  if (RDKit::SubstructMatch(mol, *pats.bit_141, matches, true, true) > 2)
    fp.setBit(141);
  count = RDKit::SubstructMatch(mol, *pats.bit_142, matches, true, true);
  if (count > 1) fp.setBit(142);
  if (count > 0) fp.setBit(161);
  if (RDKit::SubstructMatch(mol, *pats.bit_144, match, true)) fp.setBit(144);
  count = RDKit::SubstructMatch(mol, *pats.bit_145, matches, true, true);
  if (count > 1) fp.setBit(145);
  if (count > 0) fp.setBit(163);
  if (RDKit::SubstructMatch(mol, *pats.bit_147, match, true)) fp.setBit(147);
  if (RDKit::SubstructMatch(mol, *pats.bit_148, match, true)) fp.setBit(148);
  count = RDKit::SubstructMatch(mol, *pats.bit_149, matches, true, true);
  if (count > 1) fp.setBit(149);
  if (count > 0) fp.setBit(160);
  if (RDKit::SubstructMatch(mol, *pats.bit_150, match, true)) fp.setBit(150);
  if (RDKit::SubstructMatch(mol, *pats.bit_151, match, true)) fp.setBit(151);
  if (RDKit::SubstructMatch(mol, *pats.bit_152, match, true)) fp.setBit(152);
  if (RDKit::SubstructMatch(mol, *pats.bit_154, match, true)) fp.setBit(154);
  if (RDKit::SubstructMatch(mol, *pats.bit_155, match, true)) fp.setBit(155);
  if (RDKit::SubstructMatch(mol, *pats.bit_156, match, true)) fp.setBit(156);
  if (RDKit::SubstructMatch(mol, *pats.bit_157, match, true)) fp.setBit(157);
  if (RDKit::SubstructMatch(mol, *pats.bit_158, match, true)) fp.setBit(158);
  if (RDKit::SubstructMatch(mol, *pats.bit_162, match, true)) fp.setBit(162);
  if (RDKit::SubstructMatch(mol, *pats.bit_165, match, true)) fp.setBit(165);

  /* BIT 125 */
  RDKit::RingInfo *info = mol.getRingInfo();
  unsigned int ringcount = info->numRings();
  unsigned int nArom = 0;
  for (unsigned int i = 0; i < ringcount; i++) {
    bool isArom = true;
    const std::vector<int> *ring = &info->bondRings()[i];
    std::vector<int>::const_iterator iter;
    for (iter = ring->begin(); iter != ring->end(); ++iter)
      if (!mol.getBondWithIdx(*iter)->getIsAromatic()) {
        isArom = false;
        break;
      }
    if (isArom) {
      if (nArom) {
        fp.setBit(125);
        break;
      } else
        nArom++;
    }
  }

  /* BIT 166 */
  std::vector<int> mapping;
  if (RDKit::MolOps::getMolFrags(mol, mapping) > 1) fp.setBit(166);
}
}  // end of local anonymous namespace

namespace RDKit {
namespace MACCSFingerprints {
ExplicitBitVect *getFingerprintAsBitVect(const ROMol &mol) {
  ExplicitBitVect *fp = new ExplicitBitVect(167);
  GenerateFP(mol, *fp);
  return fp;
}
}
}