// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017-2020.
// Modifications copyright (c) 2017-2020, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_PROJECTIONS_FACTORY_HPP
#define BOOST_GEOMETRY_PROJECTIONS_FACTORY_HPP

#include <map>
#include <string>

#include <boost/shared_ptr.hpp>

#include <boost/geometry/core/static_assert.hpp>

#include <boost/geometry/srs/projections/dpar.hpp>
#include <boost/geometry/srs/projections/proj4.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/proj/aea.hpp>
#include <boost/geometry/srs/projections/proj/aeqd.hpp>
#include <boost/geometry/srs/projections/proj/airy.hpp>
#include <boost/geometry/srs/projections/proj/aitoff.hpp>
#include <boost/geometry/srs/projections/proj/august.hpp>
#include <boost/geometry/srs/projections/proj/bacon.hpp>
#include <boost/geometry/srs/projections/proj/bipc.hpp>
#include <boost/geometry/srs/projections/proj/boggs.hpp>
#include <boost/geometry/srs/projections/proj/bonne.hpp>
#include <boost/geometry/srs/projections/proj/cass.hpp>
#include <boost/geometry/srs/projections/proj/cc.hpp>
#include <boost/geometry/srs/projections/proj/cea.hpp>
#include <boost/geometry/srs/projections/proj/chamb.hpp>
#include <boost/geometry/srs/projections/proj/collg.hpp>
#include <boost/geometry/srs/projections/proj/crast.hpp>
#include <boost/geometry/srs/projections/proj/denoy.hpp>
#include <boost/geometry/srs/projections/proj/eck1.hpp>
#include <boost/geometry/srs/projections/proj/eck2.hpp>
#include <boost/geometry/srs/projections/proj/eck3.hpp>
#include <boost/geometry/srs/projections/proj/eck4.hpp>
#include <boost/geometry/srs/projections/proj/eck5.hpp>
#include <boost/geometry/srs/projections/proj/eqc.hpp>
#include <boost/geometry/srs/projections/proj/eqdc.hpp>
#include <boost/geometry/srs/projections/proj/etmerc.hpp>
#include <boost/geometry/srs/projections/proj/fahey.hpp>
#include <boost/geometry/srs/projections/proj/fouc_s.hpp>
#include <boost/geometry/srs/projections/proj/gall.hpp>
#include <boost/geometry/srs/projections/proj/geocent.hpp>
#include <boost/geometry/srs/projections/proj/geos.hpp>
#include <boost/geometry/srs/projections/proj/gins8.hpp>
#include <boost/geometry/srs/projections/proj/gn_sinu.hpp>
#include <boost/geometry/srs/projections/proj/gnom.hpp>
#include <boost/geometry/srs/projections/proj/goode.hpp>
#include <boost/geometry/srs/projections/proj/gstmerc.hpp>
#include <boost/geometry/srs/projections/proj/hammer.hpp>
#include <boost/geometry/srs/projections/proj/hatano.hpp>
#include <boost/geometry/srs/projections/proj/healpix.hpp>
#include <boost/geometry/srs/projections/proj/krovak.hpp>
#include <boost/geometry/srs/projections/proj/igh.hpp>
#include <boost/geometry/srs/projections/proj/imw_p.hpp>
#include <boost/geometry/srs/projections/proj/isea.hpp>
#include <boost/geometry/srs/projections/proj/laea.hpp>
#include <boost/geometry/srs/projections/proj/labrd.hpp>
#include <boost/geometry/srs/projections/proj/lagrng.hpp>
#include <boost/geometry/srs/projections/proj/larr.hpp>
#include <boost/geometry/srs/projections/proj/lask.hpp>
#include <boost/geometry/srs/projections/proj/latlong.hpp>
#include <boost/geometry/srs/projections/proj/lcc.hpp>
#include <boost/geometry/srs/projections/proj/lcca.hpp>
#include <boost/geometry/srs/projections/proj/loxim.hpp>
#include <boost/geometry/srs/projections/proj/lsat.hpp>
#include <boost/geometry/srs/projections/proj/mbtfpp.hpp>
#include <boost/geometry/srs/projections/proj/mbtfpq.hpp>
#include <boost/geometry/srs/projections/proj/mbt_fps.hpp>
#include <boost/geometry/srs/projections/proj/merc.hpp>
#include <boost/geometry/srs/projections/proj/mill.hpp>
#include <boost/geometry/srs/projections/proj/mod_ster.hpp>
#include <boost/geometry/srs/projections/proj/moll.hpp>
#include <boost/geometry/srs/projections/proj/natearth.hpp>
#include <boost/geometry/srs/projections/proj/nell.hpp>
#include <boost/geometry/srs/projections/proj/nell_h.hpp>
#include <boost/geometry/srs/projections/proj/nocol.hpp>
#include <boost/geometry/srs/projections/proj/nsper.hpp>
#include <boost/geometry/srs/projections/proj/nzmg.hpp>
#include <boost/geometry/srs/projections/proj/ob_tran.hpp>
#include <boost/geometry/srs/projections/proj/ocea.hpp>
#include <boost/geometry/srs/projections/proj/oea.hpp>
#include <boost/geometry/srs/projections/proj/omerc.hpp>
#include <boost/geometry/srs/projections/proj/ortho.hpp>
#include <boost/geometry/srs/projections/proj/qsc.hpp>
#include <boost/geometry/srs/projections/proj/poly.hpp>
#include <boost/geometry/srs/projections/proj/putp2.hpp>
#include <boost/geometry/srs/projections/proj/putp3.hpp>
#include <boost/geometry/srs/projections/proj/putp4p.hpp>
#include <boost/geometry/srs/projections/proj/putp5.hpp>
#include <boost/geometry/srs/projections/proj/putp6.hpp>
#include <boost/geometry/srs/projections/proj/robin.hpp>
#include <boost/geometry/srs/projections/proj/rouss.hpp>
#include <boost/geometry/srs/projections/proj/rpoly.hpp>
#include <boost/geometry/srs/projections/proj/sconics.hpp>
#include <boost/geometry/srs/projections/proj/somerc.hpp>
#include <boost/geometry/srs/projections/proj/stere.hpp>
#include <boost/geometry/srs/projections/proj/sterea.hpp>
#include <boost/geometry/srs/projections/proj/sts.hpp>
#include <boost/geometry/srs/projections/proj/tcc.hpp>
#include <boost/geometry/srs/projections/proj/tcea.hpp>
#include <boost/geometry/srs/projections/proj/tmerc.hpp>
#include <boost/geometry/srs/projections/proj/tpeqd.hpp>
#include <boost/geometry/srs/projections/proj/urm5.hpp>
#include <boost/geometry/srs/projections/proj/urmfps.hpp>
#include <boost/geometry/srs/projections/proj/vandg.hpp>
#include <boost/geometry/srs/projections/proj/vandg2.hpp>
#include <boost/geometry/srs/projections/proj/vandg4.hpp>
#include <boost/geometry/srs/projections/proj/wag2.hpp>
#include <boost/geometry/srs/projections/proj/wag3.hpp>
#include <boost/geometry/srs/projections/proj/wag7.hpp>
#include <boost/geometry/srs/projections/proj/wink1.hpp>
#include <boost/geometry/srs/projections/proj/wink2.hpp>

namespace boost { namespace geometry { namespace projections
{

namespace detail
{

template <typename Params>
struct factory_key
{
    BOOST_GEOMETRY_STATIC_ASSERT_FALSE(
        "Invalid parameters type.",
        Params);
};

template <>
struct factory_key<srs::detail::proj4_parameters>
{
    typedef std::string type;
    template <typename ProjParams>
    static type const& get(ProjParams const& par)
    {
        return par.id.name;
    }
    static const char* get(const char* name, srs::dpar::value_proj id)
    {
        return name;
    }
};

template <typename T>
struct factory_key<srs::dpar::parameters<T> >
{
    typedef srs::dpar::value_proj type;
    template <typename ProjParams>
    static type const& get(ProjParams const& par)
    {
        return par.id.id;
    }
    static srs::dpar::value_proj get(const char* name, srs::dpar::value_proj id)
    {
        return id;
    }
};

template <typename Params, typename CT, typename ProjParams>
class factory
{
private:
    typedef detail::factory_entry
        <
            Params,
            CT,
            ProjParams
        > entry_base;

    typedef factory_key<Params> key;
    typedef typename key::type key_type;
    typedef boost::shared_ptr<entry_base> entry_ptr;

    typedef std::map<key_type, entry_ptr> entries_map;

    entries_map m_entries;

public:

    factory()
    {
        detail::aea_init(*this);
        detail::aeqd_init(*this);
        detail::airy_init(*this);
        detail::aitoff_init(*this);
        detail::august_init(*this);
        detail::bacon_init(*this);
        detail::bipc_init(*this);
        detail::boggs_init(*this);
        detail::bonne_init(*this);
        detail::cass_init(*this);
        detail::cc_init(*this);
        detail::cea_init(*this);
        detail::chamb_init(*this);
        detail::collg_init(*this);
        detail::crast_init(*this);
        detail::denoy_init(*this);
        detail::eck1_init(*this);
        detail::eck2_init(*this);
        detail::eck3_init(*this);
        detail::eck4_init(*this);
        detail::eck5_init(*this);
        detail::eqc_init(*this);
        detail::eqdc_init(*this);
        detail::etmerc_init(*this);
        detail::fahey_init(*this);
        detail::fouc_s_init(*this);
        detail::gall_init(*this);
        detail::geocent_init(*this);
        detail::geos_init(*this);
        detail::gins8_init(*this);
        detail::gn_sinu_init(*this);
        detail::gnom_init(*this);
        detail::goode_init(*this);
        detail::gstmerc_init(*this);
        detail::hammer_init(*this);
        detail::hatano_init(*this);
        detail::healpix_init(*this);
        detail::krovak_init(*this);
        detail::igh_init(*this);
        detail::imw_p_init(*this);
        detail::isea_init(*this);
        detail::labrd_init(*this);
        detail::laea_init(*this);
        detail::lagrng_init(*this);
        detail::larr_init(*this);
        detail::lask_init(*this);
        detail::latlong_init(*this);
        detail::lcc_init(*this);
        detail::lcca_init(*this);
        detail::loxim_init(*this);
        detail::lsat_init(*this);
        detail::mbtfpp_init(*this);
        detail::mbtfpq_init(*this);
        detail::mbt_fps_init(*this);
        detail::merc_init(*this);
        detail::mill_init(*this);
        detail::mod_ster_init(*this);
        detail::moll_init(*this);
        detail::natearth_init(*this);
        detail::nell_init(*this);
        detail::nell_h_init(*this);
        detail::nocol_init(*this);
        detail::nsper_init(*this);
        detail::nzmg_init(*this);
        detail::ob_tran_init(*this);
        detail::ocea_init(*this);
        detail::oea_init(*this);
        detail::omerc_init(*this);
        detail::ortho_init(*this);
        detail::qsc_init(*this);
        detail::poly_init(*this);
        detail::putp2_init(*this);
        detail::putp3_init(*this);
        detail::putp4p_init(*this);
        detail::putp5_init(*this);
        detail::putp6_init(*this);
        detail::robin_init(*this);
        detail::rouss_init(*this);
        detail::rpoly_init(*this);
        detail::sconics_init(*this);
        detail::somerc_init(*this);
        detail::stere_init(*this);
        detail::sterea_init(*this);
        detail::sts_init(*this);
        detail::tcc_init(*this);
        detail::tcea_init(*this);
        detail::tmerc_init(*this);
        detail::tpeqd_init(*this);
        detail::urm5_init(*this);
        detail::urmfps_init(*this);
        detail::vandg_init(*this);
        detail::vandg2_init(*this);
        detail::vandg4_init(*this);
        detail::wag2_init(*this);
        detail::wag3_init(*this);
        detail::wag7_init(*this);
        detail::wink1_init(*this);
        detail::wink2_init(*this);
    }

    void add_to_factory(const char* name, srs::dpar::value_proj id, entry_base* entry)
    {
        // The pointer has to be owned before std::map::operator[] in case it thrown an exception.
        entry_ptr ptr(entry);
        m_entries[key::get(name, id)] = ptr;
    }

    detail::dynamic_wrapper_b<CT, ProjParams>* create_new(Params const& params, ProjParams const& proj_par) const
    {
        typedef typename entries_map::const_iterator const_iterator;
        const_iterator it = m_entries.find(key::get(proj_par));
        if (it != m_entries.end())
        {
            return it->second->create_new(params, proj_par);
        }

        return 0;
    }
};

template <typename T>
inline detail::dynamic_wrapper_b<T, projections::parameters<T> >*
    create_new(srs::detail::proj4_parameters const& params,
               projections::parameters<T> const& parameters)
{
    static factory<srs::detail::proj4_parameters, T, projections::parameters<T> > const fac;
    return fac.create_new(params, parameters);
}

template <typename T>
inline detail::dynamic_wrapper_b<T, projections::parameters<T> >*
    create_new(srs::dpar::parameters<T> const& params,
               projections::parameters<T> const& parameters)
{
    static factory<srs::dpar::parameters<T>, T, projections::parameters<T> > const fac;
    return fac.create_new(params, parameters);
}

} // namespace detail

}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_FACTORY_HPP


#pragma once

#include "generator/feature_builder.hpp"
#include "generator/regions/region_info.hpp"
#include "generator/translation.hpp"

#include "geometry/rect2d.hpp"

#include "coding/string_utf8_multilang.hpp"

#include "base/geo_object_id.hpp"

#include <cstdint>
#include <string>

#include <boost/geometry.hpp>
#include <boost/optional.hpp>

namespace generator
{
namespace regions
{
using Point = feature::FeatureBuilder::PointSeq::value_type;
using BoostPoint = boost::geometry::model::point<double, 2, boost::geometry::cs::cartesian>;
using BoostPolygon = boost::geometry::model::polygon<BoostPoint>;
using BoostRect = boost::geometry::model::box<BoostPoint>;

class RegionWithName
{
public:
  explicit RegionWithName(StringUtf8Multilang name) : m_name(std::move(name)) {}

  std::string GetTranslatedOrTransliteratedName(LanguageCode languageCode) const;
  // returns default name if int_name is empty
  std::string GetInternationalName() const;
  std::string GetName(int8_t lang = StringUtf8Multilang::kDefaultCode) const;
  StringUtf8Multilang const & GetMultilangName() const;

protected:
  StringUtf8Multilang m_name;
};

class RegionWithData
{
public:
  explicit RegionWithData(RegionDataProxy const & regionData) : m_regionData(regionData) {}

  base::GeoObjectId GetId() const;
  boost::optional<std::string> GetIsoCode() const;

  boost::optional<base::GeoObjectId> GetLabelOsmId() const;

  AdminLevel GetAdminLevel() const { return m_regionData.GetAdminLevel(); }
  PlaceType GetPlaceType() const { return m_regionData.GetPlaceType(); }

  void SetAdminLevel(AdminLevel adminLevel) { m_regionData.SetAdminLevel(adminLevel); }
  void SetPlaceType(PlaceType placeType) { m_regionData.SetPlaceType(placeType); }

  RegionDataProxy const & GetRegionData() const { return m_regionData; }

protected:
  RegionDataProxy m_regionData;
};

template <typename Place>
std::string GetRegionNotation(Place const & place)
{
  auto notation = place.GetTranslatedOrTransliteratedName(StringUtf8Multilang::GetLangIndex("en"));
  if (notation.empty())
    return place.GetName();

  if (notation != place.GetName())
    notation += " / " + place.GetName();
  return notation;
}
}  // namespace regions
}  // namespace generator


/******************************************************************************
  Copyright 2015-2017 Matthew The <matthew.the@scilifelab.se>
  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.

 ******************************************************************************/

#include "FeatureAlignment.h"

#include <boost/lexical_cast.hpp>
#include <boost/assign/list_of.hpp>

namespace quandenser {

const std::vector<std::pair<std::string, double> >
FeatureAlignment::kLinkFeatureNames =
  boost::assign::list_of(make_pair("ppmDiff", -3.0))
    (make_pair("rtDiff",-1.0))
    (make_pair("precMz", 0.0))
    (make_pair("rTime", 0.0))
    /* (make_pair("xicRateDiff", -0.5)) */
    (make_pair("queryIsPlaceHolder", 0.0))
    (make_pair("targetIsPlaceHolder", 0.0))
    (make_pair("charge1", 0.0))
    (make_pair("charge2", 0.0))
    (make_pair("charge3", 0.0))
    (make_pair("charge4plus", 0.0));

bool operator<(const FeatureFeatureMatch& l, const FeatureFeatureMatch& r) {
  return l.score < r.score;
}

void FeatureAlignment::matchFeatures(
    const std::vector<std::pair<int, FilePair> >& featureAlignmentQueue,
    const maracluster::SpectrumFileList& fileList,
    AlignRetention& alignRetention,
    std::vector<DinosaurFeatureList>& allFeatures,
    const std::string& tmpFilePrefix) {
  std::vector<std::pair<int, FilePair> >::const_iterator filePairIt;
  size_t alignmentCnt = 1u;
  for (filePairIt = featureAlignmentQueue.begin();
        filePairIt != featureAlignmentQueue.end(); ++filePairIt, ++alignmentCnt) {
    FilePair filePair = filePairIt->second;
    if (Globals::VERB > 1) {
      std::cerr << "Matching features " << filePair.fileIdx1
                << "->" << filePair.fileIdx2
                << " (" << alignmentCnt << "/"
                << featureAlignmentQueue.size() << ")" << std::endl;
    }
    std::string targetMzMLFile = fileList.getFilePath(filePair.fileIdx2);
    getFeatureMap(filePair, targetMzMLFile,
        alignRetention.getAlignment(filePair),
        allFeatures.at(filePair.fileIdx1),
        allFeatures.at(filePair.fileIdx2),
        tmpFilePrefix);
  }
}

void FeatureAlignment::getFeatureMap(FilePair& filePair,
    const std::string& targetMzMLFile,
    SplineRegression& alignment,
    DinosaurFeatureList& featuresQueryRun,
    DinosaurFeatureList& featuresTargetRun,
    const std::string& tmpFilePrefix) {
  if (!tmpFilePrefix.empty()) {
    std::string fileName = tmpFilePrefix + "/features."  + boost::lexical_cast<std::string>(filePair.fileIdx1) + ".dat";
    featuresQueryRun.loadFromFile(fileName);

    bool withIdxMap = true;
    fileName = tmpFilePrefix + "/features."  + boost::lexical_cast<std::string>(filePair.fileIdx2) + ".dat";
    featuresTargetRun.loadFromFile(fileName, withIdxMap);
  }

  std::vector<double> rTimesQueryRun, predictedRTimesTargetRun;
  DinosaurFeatureList::const_iterator ftIt;
  for (ftIt = featuresQueryRun.begin(); ftIt != featuresQueryRun.end(); ++ftIt) {
    rTimesQueryRun.push_back(ftIt->rTime);
  }
  alignment.predict(rTimesQueryRun, predictedRTimesTargetRun);
  float rTimeStdev = alignment.getRmse();
  DinosaurFeatureList candidateFeaturesTargetRun;

  std::string percolatorOutputFile = percolatorOutputFileBaseFN_ + "/link_" +
    boost::lexical_cast<std::string>(filePair.fileIdx1) + "_to_" +
    boost::lexical_cast<std::string>(filePair.fileIdx2) + ".psms";
  if (Globals::fileIsEmpty(percolatorOutputFile)) {
    matchFeatures(percolatorOutputFile, featuresQueryRun, featuresTargetRun,
                 predictedRTimesTargetRun, rTimeStdev);
  }
  addFeatureLinks(percolatorOutputFile, candidateFeaturesTargetRun,
                  featuresTargetRun, featureMatches_[filePair]);

  if (linkPEPMbrSearchThreshold_ < 1.0) {
    std::string percolatorMbrOutputFile = percolatorOutputFileBaseFN_ +
      "/search_and_link_" +
      boost::lexical_cast<std::string>(filePair.fileIdx1) + "_to_" +
      boost::lexical_cast<std::string>(filePair.fileIdx2) + ".psms.pout";
    mbrMatchFeatures(featureMatches_[filePair],
        percolatorMbrOutputFile, targetMzMLFile,
        featuresQueryRun, featuresTargetRun, predictedRTimesTargetRun,
        rTimeStdev, candidateFeaturesTargetRun);

    addFeatureLinks(percolatorMbrOutputFile, candidateFeaturesTargetRun,
        featuresTargetRun, featureMatches_[filePair]);
  }

  insertPlaceholderFeatures(featuresQueryRun, predictedRTimesTargetRun,
      featuresTargetRun, featureMatches_[filePair]);

  featuresTargetRun.sortByPrecMz();

  if (!tmpFilePrefix.empty()) {
    std::string fileName = tmpFilePrefix + "/features."  + boost::lexical_cast<std::string>(filePair.fileIdx2) + ".dat";
    bool append = false;
    featuresTargetRun.saveToFile(fileName, append);

    featuresQueryRun.clear();
    featuresTargetRun.clear();

    std::string matchesFileName = tmpFilePrefix + "/matches."  +
        boost::lexical_cast<std::string>(filePair.fileIdx1) + "." +
        boost::lexical_cast<std::string>(filePair.fileIdx2) + ".dat";
    saveToFile(matchesFileName, featureMatches_[filePair], append);
    featureMatches_[filePair].clear();
  }
}

void FeatureAlignment::insertPlaceholderFeatures(
    const DinosaurFeatureList& featuresQueryRun,
    const std::vector<double>& predictedRTimesTargetRun,
    DinosaurFeatureList& featuresTargetRun,
    std::map<int, FeatureIdxMatch>& precursorLinks) {
  DinosaurFeatureList::const_iterator queryIt;
  std::vector<double>::const_iterator predRTimeIt;
  int targetFileIdx = featuresTargetRun.begin()->fileIdx;
  size_t placeholdersAdded = 0u;
  for (queryIt = featuresQueryRun.begin(), predRTimeIt = predictedRTimesTargetRun.begin();
       queryIt != featuresQueryRun.end(); ++queryIt, ++predRTimeIt) {
    if (precursorLinks[queryIt->featureIdx].posteriorErrorProb > linkPEPThreshold_) {
      DinosaurFeature placeholderFt = *queryIt;
      placeholderFt.fileIdx = targetFileIdx;
      placeholderFt.featureIdx = featuresTargetRun.size();
      placeholderFt.intensity = 0.0;
      placeholderFt.rTime = *predRTimeIt;
      featuresTargetRun.push_back(placeholderFt);
      precursorLinks[queryIt->featureIdx] =
          FeatureIdxMatch(placeholderFt.featureIdx, 1.0, placeholderFt.intensity);
      ++placeholdersAdded;
    }
  }

  if (Globals::VERB > 1) {
    std::cerr << "Added " << placeholdersAdded << " placeholders." << std::endl;
  }
}

void FeatureAlignment::matchFeatures(
    const std::string& percolatorOutputFile,
    const DinosaurFeatureList& featuresQueryRun,
    const DinosaurFeatureList& featuresTargetRun,
    const std::vector<double>& predictedRTimesTargetRun,
    float rTimeStdev) {
  float rTimeTol = rTimeStdevTol_ * rTimeStdev;

  std::vector<std::string> percolatorArgs = percolatorArgs_;

  percolatorArgs.push_back("--results-psms");
  percolatorArgs.push_back(percolatorOutputFile);
  percolatorArgs.push_back("--decoy-results-psms");
  percolatorArgs.push_back(percolatorOutputFile + ".decoys");
  /*
  percolatorArgs.push_back("--tab-out");
  percolatorArgs.push_back(percolatorOutputFile + ".pin");
  */

  PercolatorAdapter percolatorAdapter;
  percolatorAdapter.parseOptions(percolatorArgs);
  percolatorAdapter.init(kLinkFeatureNames);

  DinosaurFeatureList::const_iterator queryIt, targetIt;
  std::vector<double>::const_iterator predRTimeIt;
  for (queryIt = featuresQueryRun.begin(), predRTimeIt = predictedRTimesTargetRun.begin();
       queryIt != featuresQueryRun.end(); ++queryIt, ++predRTimeIt) {
    std::vector<FeatureFeatureMatch> topTargets;

    DinosaurFeature queryFeature = *queryIt;
    queryFeature.rTime = *predRTimeIt;
    int label = 1;
    findMatches(label, rTimeTol, featuresTargetRun, queryFeature, topTargets);

    queryFeature.precMz += decoyOffset_;
    label = -1;
    findMatches(label, rTimeTol, featuresTargetRun, queryFeature, topTargets);

    std::sort(topTargets.begin(), topTargets.end());

    int numAdded = 0;
    std::vector<FeatureFeatureMatch>::const_iterator ffmIt;
    for (ffmIt = topTargets.begin();
          ffmIt != topTargets.end() && numAdded < maxFeatureCandidates_;
          ++ffmIt, ++numAdded) {
      addLinkPsm(ffmIt->label, ffmIt->queryFeature, ffmIt->targetFeature,
                 percolatorAdapter);
    }
  }

  percolatorAdapter.process();
}

void FeatureAlignment::addFeatureLinks(const std::string& percolatorOutputFile,
    DinosaurFeatureList& candidateFeaturesTargetRun,
    DinosaurFeatureList& featuresTargetRun,
    std::map<int, FeatureIdxMatch>& precursorLinks) {
  std::ifstream dataStream(percolatorOutputFile.c_str(), ios::in);

  std::cerr << "Links before "<< precursorLinks.size() << std::endl;

  int targetFileIdx = featuresTargetRun.begin()->fileIdx;
  std::string psmLine;
  getline(dataStream, psmLine); /* skip header */
  while (getline(dataStream, psmLine)) {
    TabReader reader(psmLine);

    std::string querySpecId = reader.readString();
    DinosaurFeature queryFt = parsePsmIdAsFeature(querySpecId);

    reader.readDouble(); /* score */
    double qval = reader.readDouble();
    double posteriorErrorProb = reader.readDouble();

    if (posteriorErrorProb > linkPEPThreshold_) break;

    if (posteriorErrorProb < precursorLinks[queryFt.featureIdx].posteriorErrorProb) {
      std::string peptide = reader.readString();
      std::string targetSpecId = peptide.substr(2, peptide.size() - 4);
      DinosaurFeature targetFt = parsePsmIdAsFeature(targetSpecId);
      if (targetFt.featureIdx < 0) {
        DinosaurFeature& candFt = candidateFeaturesTargetRun.at(
            getCandidatePos(targetFt.featureIdx));
        if (candFt.featureIdx < 0) {
          candFt.featureIdx = featuresTargetRun.size();
          candFt.fileIdx = targetFileIdx;
          featuresTargetRun.push_back(candFt);
        }
        targetFt = candFt;
      }
      precursorLinks[queryFt.featureIdx] = FeatureIdxMatch(targetFt.featureIdx,
          posteriorErrorProb, targetFt.intensity);
    }
  }

  std::cerr << "Links after "<< precursorLinks.size() << std::endl;
}

void FeatureAlignment::findMatches(int label, double rTimeTol,
    const DinosaurFeatureList& featuresTargetRun,
    const DinosaurFeature& queryFeature,
    std::vector<FeatureFeatureMatch>& topTargets) {
  DinosaurFeatureList::const_iterator lowerBound =
      featuresTargetRun.getPrecMzIterator(queryFeature.precMz*(1 - ppmTol_*1e-6));
  DinosaurFeatureList::const_iterator upperBound =
      featuresTargetRun.getPrecMzIterator(queryFeature.precMz*(1 + ppmTol_*1e-6));
  DinosaurFeatureList::const_iterator targetIt;
  for (targetIt = lowerBound; targetIt != upperBound; ++targetIt) {
    float rTimeDiff = std::abs(targetIt->rTime - queryFeature.rTime);
    if (targetIt->charge == queryFeature.charge && rTimeDiff <= rTimeTol) {
      DinosaurFeature targetFeature = *targetIt;
      float ppmDiff = getPpmDiff(targetIt->precMz, queryFeature.precMz);
      float score = rTimeDiff / rTimeTol + ppmDiff / ppmTol_; /* score <= 1.0 */
      /* put placeholder features in the back of the queue */
      if (targetIt->intensity == 0.0) score += 1.0;

      topTargets.push_back(FeatureFeatureMatch(score, queryFeature, targetFeature, label));
    }
  }
}

void FeatureAlignment::addLinkPsm(int label,
    const DinosaurFeature& queryFeature,
    const DinosaurFeature& targetFeature,
    PercolatorAdapter& percolatorAdapter) {
  int scannr = queryFeature.featureIdx;
  std::string querySpecId = convertFeatureToPsmId(queryFeature);
  std::string targetSpecId = convertFeatureToPsmId(targetFeature);
  std::string peptide = "A." + targetSpecId + ".A";

  double ppmDiff = getPpmDiff(targetFeature.precMz, queryFeature.precMz);
  double rTimeDiff = std::abs(queryFeature.rTime - targetFeature.rTime);
  /* double xicRateDiff = getXicRateDiff(targetFeature, queryFeature); */
  int queryIsPlaceHolder = (queryFeature.intensity == 0.0) ? 1 : 0;
  int targetIsPlaceHolder = (targetFeature.intensity == 0.0) ? 1 : 0;

  std::vector<double> features;
  features.push_back(ppmDiff);
  features.push_back(rTimeDiff);
  features.push_back(targetFeature.precMz);
  features.push_back(targetFeature.rTime);
  /* features.push_back(xicRateDiff); */
  features.push_back(queryIsPlaceHolder);
  features.push_back(targetIsPlaceHolder);
  for (int charge = 1; charge <= 4; ++charge) {
    int isCharge = (queryFeature.charge == charge ||
                    (queryFeature.charge > 4 && charge == 4)) ? 1 : 0;
    features.push_back(isCharge);
  }

  percolatorAdapter.addPsm(querySpecId, label, scannr, peptide, features);
}

double FeatureAlignment::getXicRateDiff(const DinosaurFeature& queryFt,
    const DinosaurFeature& targetFt) {
  double queryIntensity = (queryFt.intensity == 0.0) ? targetFt.intensity : queryFt.intensity;
  double targetIntensity = (targetFt.intensity == 0.0) ? queryFt.intensity : targetFt.intensity;
  if (queryIntensity * targetIntensity == 0.0) {
    queryIntensity = targetIntensity = 1e6;
  }
  double targetXicRate = getXicRate(targetIntensity, targetFt.rtStart, targetFt.rtEnd);
  double queryXicRate = getXicRate(queryIntensity, queryFt.rtStart, queryFt.rtEnd);
  return std::abs(queryXicRate - targetXicRate);
}

double FeatureAlignment::getXicRate(const double intensity,
    const double rtStart, const double rtEnd) {
  return log(intensity) / ((rtEnd - rtStart)*60.0);
}

void FeatureAlignment::mbrMatchFeatures(
    const std::map<int, FeatureIdxMatch>& precursorLinks,
    const std::string& percolatorOutputFile,
    const std::string& targetMzMLFile,
    const DinosaurFeatureList& featuresQueryRun,
    const DinosaurFeatureList& featuresTargetRun,
    const std::vector<double>& predictedRTimesTargetRun,
    float rTimeStdev,
    DinosaurFeatureList& candidateFeaturesTargetRun) {
  float rTimeTol = rTimeStdevTol_ * rTimeStdev;

  /* TODO: put these in a separate folder */
  std::string targetFile = percolatorOutputFile + ".dinosaur_targets.tsv";
  if (Globals::fileIsEmpty(targetFile)) {
    std::ofstream targetFileStream(targetFile.c_str(), ios::out);
    targetFileStream << "mz\tcharge\tmzDiff\trtStart\trtEnd\tminApexInt\tid" << std::endl;

    DinosaurFeatureList::const_iterator queryIt;
    std::vector<double>::const_iterator predRTimeIt;
    int targetFileIdx = featuresTargetRun.begin()->fileIdx;
    size_t placeholdersAdded = 0u;
    for (queryIt = featuresQueryRun.begin(), predRTimeIt = predictedRTimesTargetRun.begin();
         queryIt != featuresQueryRun.end(); ++queryIt, ++predRTimeIt) {
      if (precursorLinks.find(queryIt->featureIdx) == precursorLinks.end() ||
          precursorLinks.find(queryIt->featureIdx)->second.posteriorErrorProb >= linkPEPMbrSearchThreshold_) {
        std::ostringstream commonStream;
        commonStream << queryIt->charge << "\t" <<
                        queryIt->precMz * ppmTol_ * 1e-6 << "\t" <<
                        *predRTimeIt - rTimeTol << "\t" <<
                        *predRTimeIt + rTimeTol << "\t" <<
                        10000 << "\t" <<
                        queryIt->featureIdx << std::endl;

        targetFileStream << queryIt->precMz << "\t" << commonStream.str() <<
            queryIt->precMz + decoyOffset_ << "\t" << commonStream.str();
      }
    }
    targetFileStream.close();
  }

  boost::filesystem::path targetMzMLFilePath(targetMzMLFile);
  std::string outputDir = percolatorOutputFile + "_dinosaur"; /* TODO: put these in a separate folder */
  std::string dinosaurOutputFile = outputDir + "/" +
      targetMzMLFilePath.stem().string() + ".targets.csv";
  if (Globals::fileIsEmpty(dinosaurOutputFile)) {
    boost::filesystem::path dinosaurOutputDir(outputDir);
    boost::system::error_code returnedError;
    boost::filesystem::create_directories(dinosaurOutputDir, returnedError);
    if (!boost::filesystem::exists(dinosaurOutputDir)) {
      std::ostringstream oss;
      oss << "Error: could not create output directory at " <<
             dinosaurOutputDir.string() << std::endl;
      throw MyException(oss.str());
    }

    int rc = DinosaurIO::runDinosaurTargeted(outputDir, targetMzMLFile, targetFile);
    if (rc != EXIT_SUCCESS) {
      std::ostringstream oss;
      oss << "Dinosaur failed with exit code " << rc <<
             ". Terminating.." << std::endl;
      throw MyException(oss.str());
    }
  }

  processDinosaurTargets(precursorLinks, dinosaurOutputFile,
      percolatorOutputFile, featuresQueryRun, featuresTargetRun,
      predictedRTimesTargetRun, candidateFeaturesTargetRun);
}

void FeatureAlignment::processDinosaurTargets(
    const std::map<int, FeatureIdxMatch>& precursorLinks,
    const std::string& dinosaurTargetOutputFile,
    const std::string& percolatorOutputFile,
    const DinosaurFeatureList& featuresQueryRun,
    const DinosaurFeatureList& featuresTargetRun,
    const std::vector<double>& predictedRTimesTargetRun,
    DinosaurFeatureList& candidateFeaturesTargetRun) {
  std::vector<std::string> percolatorArgs = percolatorArgs_;

  PercolatorAdapter percolatorAdapter;
  bool runPercolator = false;
  if (Globals::fileIsEmpty(percolatorOutputFile)) {
    runPercolator = true;
    percolatorArgs.push_back("--results-psms");
    percolatorArgs.push_back(percolatorOutputFile);
    percolatorArgs.push_back("--decoy-results-psms");
    percolatorArgs.push_back(percolatorOutputFile + ".decoys");
    /*
    percolatorArgs.push_back("--tab-out");
    percolatorArgs.push_back(percolatorOutputFile + ".pin");
    */

    percolatorAdapter.parseOptions(percolatorArgs);
    percolatorAdapter.init(kLinkFeatureNames);
  }

  std::ifstream dinosaurOutputStream(dinosaurTargetOutputFile.c_str(), ios::in);

  std::string targetLine;
  getline(dinosaurOutputStream, targetLine); /* skip header */

  DinosaurFeatureList::const_iterator queryIt;
  std::vector<double>::const_iterator predRTimeIt;
  for (queryIt = featuresQueryRun.begin(), predRTimeIt = predictedRTimesTargetRun.begin();
       queryIt != featuresQueryRun.end(); ++queryIt, ++predRTimeIt) {
    if (precursorLinks.find(queryIt->featureIdx) == precursorLinks.end() ||
        precursorLinks.find(queryIt->featureIdx)->second.posteriorErrorProb >= linkPEPMbrSearchThreshold_) {
      getline(dinosaurOutputStream, targetLine);
      DinosaurFeature targetFeature = DinosaurIO::parseDinosaurFeatureRow(targetLine);
      targetFeature.featureIdx = featuresTargetRun.getFeatureIdx(targetFeature);
      targetFeature.fileIdx = -1;
      if (targetFeature.intensity > 0.0 && targetFeature.featureIdx == -1) {
        targetFeature.featureIdx = candidateFeaturesTargetRun.getFeatureIdx(targetFeature);
        if (targetFeature.featureIdx == -1) {
          targetFeature.featureIdx = getCandidateIdx(candidateFeaturesTargetRun.size());
          candidateFeaturesTargetRun.push_back(targetFeature);
        }
      }

      getline(dinosaurOutputStream, targetLine);
      if (runPercolator) {
        DinosaurFeature decoyFeature = DinosaurIO::parseDinosaurFeatureRow(targetLine);
        decoyFeature.featureIdx = -1;
        decoyFeature.fileIdx = -1;

        DinosaurFeature queryFeature = *queryIt;
        queryFeature.rTime = *predRTimeIt;
        if (targetFeature.intensity > 0.0)
          addLinkPsm(1, queryFeature, targetFeature, percolatorAdapter);

        queryFeature.precMz += decoyOffset_;
        if (decoyFeature.intensity > 0.0)
          addLinkPsm(-1, queryFeature, decoyFeature, percolatorAdapter);
      }
    }
  }

  if (runPercolator) {
    percolatorAdapter.process();
  }
}

std::string FeatureAlignment::convertFeatureToPsmId(const DinosaurFeature& feature) {
  return boost::lexical_cast<std::string>(feature.featureIdx) + "_" +
         boost::lexical_cast<std::string>(feature.precMz) + "_" +
         boost::lexical_cast<std::string>(feature.rTime) + "_" +
         boost::lexical_cast<std::string>(feature.intensity) + "_" +
         boost::lexical_cast<std::string>(feature.charge);
}

DinosaurFeature FeatureAlignment::parsePsmIdAsFeature(const std::string& specId) {
  DinosaurFeature ft;

  size_t prev = 0u;
  size_t next = specId.find_first_of("_", prev);
  ft.featureIdx = boost::lexical_cast<int>(specId.substr(prev, next - prev));
  prev = next+1;

  next = specId.find_first_of("_", prev);
  ft.precMz = boost::lexical_cast<float>(specId.substr(prev, next - prev));
  prev = next+1;

  next = specId.find_first_of("_", prev);
  ft.rTime = boost::lexical_cast<float>(specId.substr(prev, next - prev));
  prev = next+1;

  next = specId.find_first_of("_", prev);
  ft.intensity = boost::lexical_cast<float>(specId.substr(prev, next - prev));
  prev = next+1;

  next = specId.find_first_of("_", prev);
  ft.charge = boost::lexical_cast<int>(specId.substr(prev, next - prev));

  return ft;
}

} /* namespace quandenser */


/**
 * algorithm.cpp
 *
 * MIT License
 *
 * Copyright (c) 2018 LandZERO
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "algorithm.h"

#include <dlib/opencv.h>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgproc.hpp>

// down sample ratio
#define DSRATIO 4

class Detection {
public:
  Detection(dlib::full_object_detection *rawDet) : _rawDet(rawDet) {}

  cv::Point2d operator[](size_t index) { return cv::Point2d(_rawDet->part(index).x(), _rawDet->part(index).y()); }

private:
  dlib::full_object_detection *_rawDet;
};

bool _cameraPointsConsideredSame(std::vector<cv::Point2d> &last, std::vector<cv::Point2d> &current) {
  if (last.empty() || (last.size() != current.size()))
    return false;
  double base = cv::norm(current[0] - current[1]);
  if (base == 0)
    return false;
  double total = 0;
  for (size_t i = 0; i < last.size(); i++) {
    total += cv::norm(current[i] - last[i]);
  }
  return (total / base) < 0.1;
}

bool _compareRectangleArea(dlib::rectangle &lhs, dlib::rectangle &rhs) { return lhs.area() < rhs.area(); }

altego::Algorithm::Algorithm() {
  // initialize detector
  _detector = dlib::get_frontal_face_detector();

  // initialize reference points
  // The first must be (0,0,0) while using POSIT
  _referencePoints.emplace_back(0.0f, 0.0f, 0.0f);          // 30
  _referencePoints.emplace_back(0.0f, -330.0f, -65.0f);     // 8
  _referencePoints.emplace_back(-225.0f, 170.0f, -135.0f);  // 36
  _referencePoints.emplace_back(225.0f, 170.0f, -135.0f);   // 45
  _referencePoints.emplace_back(-150.0f, -150.0f, -125.0f); // 48
  _referencePoints.emplace_back(150.0f, -150.0f, -125.0f);  // 54

  // initialize distCoeffs
  _distCoeffs = cv::Mat::zeros(4, 1, cv::DataType<double>::type);
}

bool altego::Algorithm::ResolveAndAnnotate(cv::Mat &im, altego::Result &res) {
  // down sample for face detection
  cv::Mat imSmall;
  cv::resize(im, imSmall, cv::Size(), 1.0 / DSRATIO, 1.0 / DSRATIO);
  // convert type with zero copy
  dlib::cv_image<dlib::bgr_pixel> dim(im);
  dlib::cv_image<dlib::bgr_pixel> dimSmall(imSmall);
  // detect faces
  auto faces = _detector(dimSmall);
  if (faces.empty())
    return false;
  // find largest face
  auto face = *std::max_element(faces.begin(), faces.end(), _compareRectangleArea).base();
  // upscale
  face.left() *= DSRATIO;
  face.top() *= DSRATIO;
  face.right() *= DSRATIO;
  face.bottom() *= DSRATIO;
  // detection
  auto rawDet = _predictor(dim, face);
  // check num_parts()
  if (rawDet.num_parts() != 68)
    return false;
  // draw detection
  for (size_t i = 0; i < rawDet.num_parts(); i++) {
    dlib::point p = rawDet.part(i);
    // check part valid
    if (p == dlib::OBJECT_PART_NOT_PRESENT)
      return false;
    cv::circle(im, cv::Point(static_cast<int>(p.x()), static_cast<int>(p.y())), 2, cv::Scalar(255, 0, 72), -1);
  }

  // wrap dlib::full_object_detection
  Detection det(&rawDet);

  // cameraPoints
  std::vector<cv::Point2d> cp;
  // nose tip
  cp.push_back(det[30]);
  // chin
  cp.push_back(det[8]);
  // left eye left corner
  cp.push_back(det[36]);
  // right eye right corner
  cp.push_back(det[45]);
  // left Mouth corner
  cp.push_back(det[48]);
  // right mouth corner
  cp.push_back(det[54]);

  // stabilize
  if (_cameraPointsConsideredSame(_lastCameraPoints, cp))
    return false;

  // update lastCameraPoints
  _lastCameraPoints = cp;

  // camera matrix
  cv::Mat_<double> cameraMatrix(3, 3);
  cameraMatrix << im.cols, 0, im.cols / 2.f, 0, im.cols, im.rows / 2.f, 0, 0, 1;

  // rotation vector, translation vector
  cv::Mat rv, tv;

  // solve
  cv::solvePnP(_referencePoints, cp, cameraMatrix, _distCoeffs, rv, tv);

  // set rv, tv to result
  res.r1 = rv.at<double>(0, 1);
  res.r2 = rv.at<double>(0, 2);

  return true;
}

void altego::Algorithm::LoadModelFile(std::string &modelFile) { dlib::deserialize(modelFile) >> _predictor; }


// Copyright (c) 2020 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE CTCLossCodeletTest
#include <iomanip>
#include <random>

#include <poplar/Engine.hpp>
#include <poplar/Graph.hpp>
#include <poplar/IPUModel.hpp>
#include <poplibs_support/LogArithmetic.hpp>
#include <poplibs_support/TestDevice.hpp>
#include <poplibs_test/CTCLoss.hpp>
#include <poplibs_test/CTCUtil.hpp>
#include <poplibs_test/Embedding.hpp>
#include <poplibs_test/MatrixTransforms.hpp>
#include <poplibs_test/Util.hpp>
#include <popnn/codelets.hpp>
#include <poputil/VertexTemplates.hpp>
#include <poputil/exceptions.hpp>

#include <boost/multi_array.hpp>
#include <boost/program_options.hpp>
#include <boost/test/tools/floating_point_comparison.hpp>

namespace po = boost::program_options;

using namespace poplar;
using namespace poplar::program;
using namespace poplibs_test::ctc;
using namespace poplibs_test;
using namespace poplibs_test::matrix;
using namespace poplibs_test::util;
using namespace poplibs_support;
using namespace poputil;

// Default tolerances used in tests
#define FLOAT_REL_TOL 0.01
#define HALF_REL_TOL 0.1
#define FLOAT_ABS_TOL 1e-6
#define HALF_ABS_TOL 1e-5

enum class TestType { ALPHA, BETA, GRAD_GIVEN_ALPHA, GRAD_GIVEN_BETA };

std::ostream &operator<<(std::ostream &os, const TestType &test) {
  if (test == TestType::ALPHA)
    return os << "alpha";
  else if (test == TestType::BETA)
    return os << "beta";
  else if (test == TestType::GRAD_GIVEN_ALPHA)
    return os << "gradGivenAlpha";
  return os << "gradGivenBeta";
}

std::istream &operator>>(std::istream &is, TestType &test) {
  std::string token;
  is >> token;
  if (token == "alpha")
    test = TestType::ALPHA;
  else if (token == "beta")
    test = TestType::BETA;
  else if (token == "gradGivenAlpha")
    test = TestType::GRAD_GIVEN_ALPHA;
  else if (token == "gradGivenBeta")
    test = TestType::GRAD_GIVEN_BETA;
  else
    throw poputil::poplibs_error("Unknown test type");
  return is;
}

boost::multi_array<double, 2>
maskResults(const boost::multi_array<double, 2> &in) {
  auto out = in;
  const auto symbols = out.shape()[0];
  const auto timeSteps = out.shape()[1];
  for (unsigned sym = 0; sym < symbols; sym++) {
    if (sym > 1) {
      out[sym][0] = 0;
    }
    if (sym < symbols - 2) {
      out[sym][timeSteps - 1] = 0;
    }
  }
  return out;
}

boost::multi_array<double, 2>
maskTimeSteps(const boost::multi_array<double, 2> &in, unsigned timeStep,
              bool invertMask) {
  auto out = in;
  const auto symbols = out.shape()[0];
  const auto timeSteps = out.shape()[1];
  for (unsigned time = 0; time < timeSteps; time++) {
    if ((invertMask && time == timeStep) || (!invertMask && time != timeStep)) {
      for (unsigned sym = 0; sym < symbols; sym++) {
        out[sym][time] = 0;
      }
    }
  }
  return out;
}

// Print a sequence, inserting `-` for the blank symbol
void print(const std::string &prefix, const std::vector<unsigned> &idx,
           unsigned blank, bool verbose = true) {
  if (!verbose) {
    return;
  }
  std::cout << "\n" << prefix << " ";
  for (auto &i : idx) {
    if (i == blank) {
      std::cout << "- ";
    } else {
      std::cout << i << " ";
    }
  }
  std::cout << "\n";
}

// Print the matrix `in`, using `paddedSequence` as row labels
template <typename FPType>
void print(const std::string &prefix, const boost::multi_array<FPType, 2> &in,
           const std::vector<unsigned> &paddedSequence, unsigned blank,
           bool verbose = true) {
  if (!verbose) {
    return;
  }
  std::cout << "\n" << prefix << "\n          ";
  for (unsigned i = 0; i < in[0].size(); i++) {
    std::cout << "         t" << i;
  }

  for (unsigned i = 0; i < in.size(); i++) {
    std::cout << "\nIndex:" << i << "  ";
    if (paddedSequence[i] == blank) {
      std::cout << "- ";
    } else {
      std::cout << paddedSequence[i] << " ";
    }
    for (unsigned j = 0; j < in[i].size(); j++) {
      std::cout << std::setw(10) << std::setprecision(4) << in[i][j] << ",";
    }
  }
  std::cout << "\n";
}

// Print the matrix `in`
template <typename FPType>
void print(const std::string &prefix, const boost::multi_array<FPType, 2> &in,
           unsigned blank, bool verbose = true) {
  if (!verbose) {
    return;
  }
  std::cout << "\n" << prefix << "\n        ";
  for (unsigned i = 0; i < in[0].size(); i++) {
    std::cout << "         t" << i;
  }

  for (unsigned i = 0; i < in.size(); i++) {
    if (i == blank) {
      std::cout << "\nIndex:-  ";
    } else {
      std::cout << "\nIndex:" << i << "  ";
    }
    for (unsigned j = 0; j < in[i].size(); j++) {
      std::cout << std::setw(10) << std::setprecision(4) << in[i][j] << ",";
    }
  }
  std::cout << "\n";
}

// Struct and function to return the test inputs
template <typename FPType> struct InputSequence {
  boost::multi_array<FPType, 2> input;
  std::vector<unsigned> idx;
  unsigned alphabetSizeIncBlank;
};

template <typename FPType>
boost::multi_array<FPType, 2>
gradReference(const InputSequence<FPType> &test, unsigned blankClass,
              TestType vertexToTest, bool verbose) {

  auto paddedSequence = extendedLabels(test.idx, blankClass);
  auto validTimesteps = test.input.shape()[1];

  print("Log Softmax in", test.input, blankClass, verbose);
  boost::multi_array<FPType, 2> logSequence(
      boost::extents[paddedSequence.size()][test.input.shape()[1]]);
  poplibs_test::embedding::multiSlice(test.input, paddedSequence, logSequence);

  print("Reference sequence", logSequence, paddedSequence, blankClass, verbose);

  auto alphaLog =
      alpha(logSequence, paddedSequence, blankClass, validTimesteps);
  if (vertexToTest == TestType::ALPHA) {
    print("Reference alphas", alphaLog, paddedSequence, blankClass, verbose);
    return alphaLog;
  }

  auto betaLog = beta(logSequence, paddedSequence, blankClass, validTimesteps);
  if (vertexToTest == TestType::BETA) {
    print("Reference betas", betaLog, paddedSequence, blankClass, verbose);
    return betaLog;
  }
  auto expandedGradient =
      expandedGrad(logSequence, alphaLog, betaLog, paddedSequence, blankClass,
                   validTimesteps);
  print("Expanded Reference gradient", expandedGradient, paddedSequence,
        blankClass, verbose);

  auto gradient =
      ctcGrad(logSequence, alphaLog, betaLog, paddedSequence,
              test.alphabetSizeIncBlank, blankClass, validTimesteps);
  print("Reference gradient", gradient, blankClass, verbose);

  std::cout << "\n";
  return gradient;
}

boost::multi_array<double, 2>
gradIPU(const InputSequence<double> &input, unsigned timeStep,
        unsigned blankClass, const boost::multi_array<double, 2> &initialValues,
        TestType vertexToTest, Type inType, Type outType,
        const DeviceType &deviceType, bool profile) {

  auto device = createTestDevice(deviceType, 1, 1);
  const auto &target = device.getTarget();
  Graph graph(target);
  popnn::addCodelets(graph);
  Sequence prog;

  const auto labelLen = input.idx.size();
  const auto extendedLabelLen = 2 * labelLen + 1;
  const auto maxT = input.input.shape()[1];
  const auto numClasses = input.input.shape()[0];

  const auto resultIsGrad = (vertexToTest == TestType::GRAD_GIVEN_ALPHA ||
                             vertexToTest == TestType::GRAD_GIVEN_BETA);
  const auto findingAlpha = (vertexToTest == TestType::ALPHA ||
                             vertexToTest == TestType::GRAD_GIVEN_BETA);

  auto probabilities =
      graph.addVariable(inType, {maxT, numClasses}, "probabilities");
  auto labels = graph.addVariable(UNSIGNED_INT, {labelLen}, "label");
  auto result = graph.addVariable(
      outType, {maxT, resultIsGrad ? numClasses : extendedLabelLen}, "result");
  auto prevTime = graph.addVariable(
      outType, {resultIsGrad ? 2u : 1u, extendedLabelLen}, "prevTime");
  graph.setTileMapping(prevTime, 0);
  auto prevLabel =
      graph.addVariable(outType, {findingAlpha ? 1u : 2u, maxT}, "prevLabel");
  graph.setTileMapping(prevLabel, 0);
  auto prevSymbol = graph.addConstant<unsigned>(UNSIGNED_INT, {}, input.idx[0]);
  graph.setTileMapping(prevSymbol, 0);
  Tensor initialAlphaOrBeta;
  if (resultIsGrad) {
    initialAlphaOrBeta = graph.addVariable(outType, {maxT, extendedLabelLen},
                                           "initialAlphaOrBeta");
    graph.setTileMapping(initialAlphaOrBeta, 0);
  }
  Tensor loss;
  if (findingAlpha) {
    loss = graph.addVariable(outType, {}, "loss");
    graph.setTileMapping(loss, 0);
  }
  auto cs = graph.addComputeSet("cs");
  std::string vertexName;
  if (vertexToTest == TestType::ALPHA) {
    vertexName =
        templateVertex("popnn::CTCAlpha", inType, outType, UNSIGNED_INT, true);
  } else if (vertexToTest == TestType::BETA) {
    vertexName =
        templateVertex("popnn::CTCBeta", inType, outType, UNSIGNED_INT, true);
  } else if (vertexToTest == TestType::GRAD_GIVEN_ALPHA) {
    vertexName = templateVertex("popnn::CTCGradGivenAlpha", inType, outType,
                                UNSIGNED_INT, true);
  } else if (vertexToTest == TestType::GRAD_GIVEN_BETA) {
    vertexName = templateVertex("popnn::CTCGradGivenBeta", inType, outType,
                                UNSIGNED_INT, true);
  }
  auto vertex = graph.addVertex(cs, vertexName);

  // TODO - Vertex features to support < maxT and < labelsLen sized
  // inputs, and accepting data from the previous label inputs
  // are only tested in the context of the whole IPU implementation.
  // Suggest that when optimising the vertices this test is improved.
  auto validLabel = graph.addConstant(UNSIGNED_INT, {}, labelLen);
  auto validTime = graph.addConstant(UNSIGNED_INT, {}, maxT);
  auto initialCount = graph.addConstant(UNSIGNED_INT, {}, timeStep);
  auto count = graph.addVariable(UNSIGNED_INT, {}, "count");
  prog.add(Copy(initialCount, count));
  graph.setTileMapping(validLabel, 0);
  graph.setTileMapping(validTime, 0);
  graph.setTileMapping(initialCount, 0);
  graph.setTileMapping(count, 0);
  graph.connect(vertex["validLabel"], validLabel);
  graph.connect(vertex["validTime"], validTime);
  graph.connect(vertex["count"], count);

  graph.setInitialValue(vertex["maxT"], maxT);
  graph.setInitialValue(vertex["numClasses"], numClasses);
  graph.setInitialValue(vertex["blankClass"], blankClass);
  graph.setInitialValue(vertex["labelOffset"], 0);
  graph.setInitialValue(vertex["timeOffset"], 0);

  auto labelWithPreviousOrNext = findingAlpha
                                     ? concat(prevSymbol.reshape({1}), labels)
                                     : concat(labels, prevSymbol.reshape({1}));
  graph.connect(vertex["label"], labelWithPreviousOrNext);
  graph.connect(vertex["probabilities"], probabilities.flatten());

  if (vertexToTest == TestType::ALPHA) {
    graph.connect(vertex["alphas"], result.flatten());
    graph.connect(vertex["alphaPrevTime"], prevTime.flatten());
    graph.connect(vertex["alphaPrevLabel"], prevLabel.flatten());
    graph.connect(vertex["loss"], loss);

    graph.connect(vertex["alphaPrevLabelOut"], prevLabel.flatten());
    graph.connect(vertex["alphaPrevLabelTime"], prevLabel.flatten());
  } else if (vertexToTest == TestType::BETA) {
    graph.connect(vertex["betas"], result.flatten());
    graph.connect(vertex["betaPrevTime"], prevTime.flatten());
    graph.connect(vertex["betaPrevLabel"], prevLabel.flatten());

    graph.connect(vertex["betaPrevLabelOut"], prevLabel.flatten());
    graph.connect(vertex["betaPrevLabelTime"], prevLabel.flatten());
  } else if (vertexToTest == TestType::GRAD_GIVEN_ALPHA) {
    graph.connect(vertex["grads"], result.flatten());
    graph.connect(vertex["alphas"], initialAlphaOrBeta.flatten());
    graph.connect(vertex["betaPrevTime"], prevTime.flatten());
    graph.connect(vertex["betaPrevLabel"], prevLabel.flatten());

    graph.connect(vertex["betaPrevPartition"], prevTime.flatten());
    graph.connect(vertex["betaPrevLabelOut"], prevLabel.flatten());
    graph.connect(vertex["betaPrevLabelTime"], prevLabel.flatten());
  } else if (vertexToTest == TestType::GRAD_GIVEN_BETA) {
    graph.connect(vertex["grads"], result.flatten());
    graph.connect(vertex["betas"], initialAlphaOrBeta.flatten());
    graph.connect(vertex["alphaPrevTime"], prevTime.flatten());
    graph.connect(vertex["alphaPrevLabel"], prevLabel.flatten());
    graph.connect(vertex["loss"], loss);

    graph.connect(vertex["alphaPrevPartition"], prevTime.flatten());
    graph.connect(vertex["alphaPrevLabelOut"], prevLabel.flatten());
    graph.connect(vertex["alphaPrevLabelTime"], prevLabel.flatten());
  }

  graph.setTileMapping(probabilities, 0);
  graph.setTileMapping(labels, 0);
  graph.setTileMapping(result, 0);
  graph.setTileMapping(vertex, 0);

  Sequence uploadProg, downloadProg;
  std::vector<std::pair<std::string, char *>> tmap;
  std::unique_ptr<char[]> rawProbabilities, rawResult, rawLabels,
      rawInitialAlphaOrBeta, rawPrevTime, rawPrevLabel;

  rawProbabilities = allocateHostMemoryForTensor(
      probabilities, "probabilities", graph, uploadProg, downloadProg, tmap);
  rawLabels = allocateHostMemoryForTensor(labels, "labels", graph, uploadProg,
                                          downloadProg, tmap);
  rawResult = allocateHostMemoryForTensor(result, "result", graph, uploadProg,
                                          downloadProg, tmap);

  copy(target, transpose(input.input), inType, rawProbabilities.get());
  copy(target, input.idx.data(), input.idx.size(), labels.elementType(),
       rawLabels.get());

  // Initialise alpha or beta if needed by the test, otherwise uninitialised
  if (resultIsGrad) {
    rawInitialAlphaOrBeta =
        allocateHostMemoryForTensor(initialAlphaOrBeta, "initialAlphaOrBeta",
                                    graph, uploadProg, downloadProg, tmap);
  } else {
    rawInitialAlphaOrBeta = allocateHostMemoryForTensor(
        result, "initialResult", graph, uploadProg, downloadProg, tmap);
  }
  copy(target, transpose(initialValues), outType, rawInitialAlphaOrBeta.get());
  boost::multi_array<double, 2> zeroResult(
      boost::extents[result.dim(0)][result.dim(1)]);
  std::fill(zeroResult.data(), zeroResult.data() + zeroResult.num_elements(),
            log::probabilityZero);
  copy(target, zeroResult, outType, rawResult.get());
  // Initialise the first Timeslice input of the vertex - in practice this could
  // be "carried over" from a previous vertex alpha or beta calculation
  rawPrevTime = allocateHostMemoryForTensor(prevTime, "prevTime", graph,
                                            uploadProg, downloadProg, tmap);
  boost::multi_array<double, 2> prevTimeInit(
      boost::extents[prevTime.dim(0)][prevTime.dim(1)]);
  std::fill(prevTimeInit.data(),
            prevTimeInit.data() + prevTimeInit.num_elements(),
            log::probabilityZero);

  if (findingAlpha) {
    // 1st symbol probability=1
    prevTimeInit[0][0] = log::probabilityOne;
    copy(target, prevTimeInit, outType, rawPrevTime.get());
  } else {
    // last symbol probability=1
    prevTimeInit[0][extendedLabelLen - 1] = log::probabilityOne;
    copy(target, prevTimeInit, outType, rawPrevTime.get());
  }

  rawPrevLabel = allocateHostMemoryForTensor(prevLabel, "prevLabel", graph,
                                             uploadProg, downloadProg, tmap);
  boost::multi_array<double, 2> prevLabelInit(
      boost::extents[prevLabel.dim(0)][prevLabel.dim(1)]);
  std::fill(prevLabelInit.data(),
            prevLabelInit.data() + prevLabelInit.num_elements(),
            log::probabilityZero);
  copy(target, prevLabelInit, outType, rawPrevLabel.get());

  prog.add(Execute(cs));
  OptionFlags engineOptions;
  if (profile) {
    engineOptions.set("debug.instrumentCompute", "true");
  }
  Engine engine(graph, Sequence{uploadProg, prog, downloadProg}, engineOptions);
  attachStreams(engine, tmap);
  device.bind([&](const Device &d) {
    engine.load(d);
    engine.run();
  });
  boost::multi_array<double, 2> output(
      boost::extents[result.dim(0)][result.dim(1)]);
  copy(target, outType, rawResult.get(), output);

  if (profile && deviceType != DeviceType::Cpu) {
    engine.printProfileSummary(std::cout,
                               OptionFlags{{"showExecutionSteps", "true"}});
  }
  return transpose(output);
}

int main(int argc, char **argv) {
  // Default input parameters.
  DeviceType deviceType = DeviceType::IpuModel2;
  bool verbose = false;
  bool profile = false;
  unsigned blankClass = 0;
  unsigned testTime = 15;
  unsigned testSymbols = 3;
  unsigned numClasses = 4;
  unsigned timeStep = 0;
  TestType vertexToTest = TestType::ALPHA;
  Type inType = FLOAT;
  Type outType = FLOAT;

  po::options_description desc("Options");
  // clang-format off
  desc.add_options()
    ("help", "Produce help message")
    ("device-type",
     po::value<DeviceType>(&deviceType)->default_value(deviceType),
     deviceTypeHelp)
    ("in-type", po::value(&inType)->default_value(inType),
     "Vertex input data type")
    ("out-type", po::value(&outType)->default_value(outType),
     "Vertex output data type")
    ("profile", po::value(&profile)->default_value(profile),
     "Show profile report")
    ("test", po::value(&vertexToTest)->default_value(vertexToTest),
     "Test: alpha, beta, gradGivenAlpha, gradGivenBeta")
    ("blank-class", po::value(&blankClass)->default_value(blankClass),
     "Index of the blank symbol. Range 0 to (num-classes-1)")
    ("test-symbols", po::value(&testSymbols)->default_value(testSymbols),
     "Test length (symbols)")
    ("time", po::value(&testTime)->default_value(testTime),
     "Test length (time)")
    ("num-classes", po::value(&numClasses)->default_value(numClasses),
     "Classes in the alphabet including blank")
    ("time-step", po::value(&timeStep)->default_value(timeStep),
     "The timestep (loop count) to process")
    ("verbose", po::value(&verbose)->default_value(verbose),
     "Provide debug printout");
  // clang-format on

  po::variables_map vm;
  try {
    po::store(po::parse_command_line(argc, argv, desc), vm);
    if (vm.count("help")) {
      std::cout << desc << "\n\n";
      return 1;
    }

  } catch (std::exception &e) {
    std::cerr << "error parsing command line: " << e.what() << "\n";
    return 1;
  }
  // Needed to set default arguments.
  po::notify(vm);
  if (testTime < testSymbols) {
    throw poputil::poplibs_error("The test time must be >= sequence symbols");
  }
  if (blankClass >= numClasses) {
    throw poputil::poplibs_error("The blank class must be in the range 0 to "
                                 "(number of classes - 1)");
  }

  RandomUtil rand{42};
  auto [input, label] = getRandomTestInput<double>(
      testTime, testTime, testSymbols, numClasses, blankClass, true, rand);
  auto test = InputSequence<double>{transpose(input), label, numClasses};

  print("Test sequence:", test.idx, blankClass, verbose);

  // Produce a sensible input by calling the model.
  // This will contain all alpha/beta inputs for a gradGivenAlpha/Beta vertex
  // test, or all but the timeStep to be tested for alpha/beta vertices
  auto initialOutputType = (vertexToTest == TestType::ALPHA ||
                            vertexToTest == TestType::GRAD_GIVEN_ALPHA)
                               ? TestType::ALPHA
                               : TestType::BETA;
  auto initialOutput =
      gradReference<double>(test, blankClass, initialOutputType, false);
  if (vertexToTest == TestType::ALPHA || vertexToTest == TestType::BETA) {
    // For alpha, beta vertices an effective test can by made by masking out
    // the one timeslice that the vertex should be calculating
    initialOutput = maskTimeSteps(initialOutput, timeStep, true);
  }
  auto reference =
      gradReference<double>(test, blankClass, vertexToTest, verbose);
  auto output = gradIPU(test, timeStep, blankClass, initialOutput, vertexToTest,
                        inType, outType, deviceType, profile);

  double relativeTolerance = inType == FLOAT ? FLOAT_REL_TOL : HALF_REL_TOL;
  double absoluteTolerance = inType == FLOAT ? FLOAT_ABS_TOL : HALF_ABS_TOL;

  // When finding alpha, beta some results aren't relevant. Mask them out
  if (vertexToTest == TestType::ALPHA || vertexToTest == TestType::BETA) {
    print("IPU result:", output, extendedLabels(test.idx, blankClass),
          blankClass, verbose);
    reference = maskResults(reference);
    output = maskResults(output);
  } else {
    print("IPU result:", output, blankClass, verbose);
    // Mask out all but the timestep processed.
    // TODO - This is a bit of a cheat, only the 1st (alpha) or last (beta)
    // timestep works at the moment, plus we should verify the whole output
    // is untouched.  Suggest doing this when making assembler vertices
    output = maskTimeSteps(output, timeStep, false);
    reference = maskTimeSteps(reference, timeStep, false);
  }

  bool success = checkIsClose("result", output, reference, relativeTolerance,
                              absoluteTolerance);
  if (!success) {
    std::cerr << "Data mismatch\n";
  }
  return !success;
}


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "bempp/common/config_ahmed.hpp"
#include "bempp/common/config_trilinos.hpp"

#include "hmat_global_assembler.hpp"

#include "assembly_options.hpp"
#include "context.hpp"
#include "evaluation_options.hpp"
#include "discrete_boundary_operator_composition.hpp"
#include "discrete_sparse_boundary_operator.hpp"
#include "weak_form_hmat_assembly_helper.hpp"
#include "discrete_hmat_boundary_operator.hpp"

#include "../common/armadillo_fwd.hpp"
#include "../common/auto_timer.hpp"
#include "../common/chunk_statistics.hpp"
#include "../common/to_string.hpp"
#include "../fiber/explicit_instantiation.hpp"
#include "../fiber/local_assembler_for_integral_operators.hpp"
#include "../fiber/scalar_traits.hpp"
#include "../fiber/shared_ptr.hpp"
#include "../space/space.hpp"
#include "../common/bounding_box.hpp"

#include "../hmat/block_cluster_tree.hpp"
#include "../hmat/geometry_interface.hpp"
#include "../hmat/geometry_data_type.hpp"
#include "../hmat/geometry.hpp"
#include "../hmat/hmatrix.hpp"
#include "../hmat/data_accessor.hpp"
#include "../hmat/hmatrix_dense_compressor.hpp"
#include "../hmat/hmatrix_aca_compressor.hpp"

#include <stdexcept>
#include <fstream>
#include <iostream>

#include <boost/type_traits/is_complex.hpp>

#include <tbb/atomic.h>
#include <tbb/parallel_for.h>
#include <tbb/task_scheduler_init.h>
#include <tbb/concurrent_queue.h>

#include <Teuchos_ParameterList.hpp>

namespace Bempp {

namespace {

template <typename BasisFunctionType>
class SpaceHMatGeometryInterface : public hmat::GeometryInterface {

public:
  typedef typename Fiber::ScalarTraits<BasisFunctionType>::RealType
  CoordinateType;
  SpaceHMatGeometryInterface(const Space<BasisFunctionType> &space)
      : m_counter(0) {
    space.getGlobalDofBoundingBoxes(m_bemppBoundingBoxes);
  }
  shared_ptr<const hmat::GeometryDataType> next() override {

    if (m_counter == m_bemppBoundingBoxes.size())
      return shared_ptr<hmat::GeometryDataType>();

    auto lbound = m_bemppBoundingBoxes[m_counter].lbound;
    auto ubound = m_bemppBoundingBoxes[m_counter].ubound;
    auto center = m_bemppBoundingBoxes[m_counter].reference;
    m_counter++;
    return shared_ptr<hmat::GeometryDataType>(new hmat::GeometryDataType(
        hmat::BoundingBox(lbound.x, ubound.x, lbound.y, ubound.y, lbound.z,
                          ubound.z),
        std::array<double, 3>({{center.x, center.y, center.z}})));
  }

  std::size_t numberOfEntities() const override {
    return m_bemppBoundingBoxes.size();
  }
  void reset() override { m_counter = 0; }

private:
  std::size_t m_counter;
  std::vector<BoundingBox<CoordinateType>> m_bemppBoundingBoxes;
};

template <typename BasisFunctionType>
shared_ptr<hmat::DefaultBlockClusterTreeType>
generateBlockClusterTree(const Space<BasisFunctionType> &testSpace,
                         const Space<BasisFunctionType> &trialSpace,
                         int minBlockSize, int maxBlockSize, double eta) {

  hmat::Geometry testGeometry;
  hmat::Geometry trialGeometry;

  auto testSpaceGeometryInterface = shared_ptr<hmat::GeometryInterface>(
      new SpaceHMatGeometryInterface<BasisFunctionType>(testSpace));

  auto trialSpaceGeometryInterface = shared_ptr<hmat::GeometryInterface>(
      new SpaceHMatGeometryInterface<BasisFunctionType>(trialSpace));

  hmat::fillGeometry(testGeometry, *testSpaceGeometryInterface);
  hmat::fillGeometry(trialGeometry, *trialSpaceGeometryInterface);

  auto testClusterTree = shared_ptr<hmat::DefaultClusterTreeType>(
      new hmat::DefaultClusterTreeType(testGeometry, minBlockSize));

  auto trialClusterTree = shared_ptr<hmat::DefaultClusterTreeType>(
      new hmat::DefaultClusterTreeType(trialGeometry, minBlockSize));

  shared_ptr<hmat::DefaultBlockClusterTreeType> blockClusterTree(
      new hmat::DefaultBlockClusterTreeType(testClusterTree, trialClusterTree,
                                            maxBlockSize,
                                            hmat::StandardAdmissibility(eta)));

  return blockClusterTree;
}
} // end anonymous namespace
template <typename BasisFunctionType, typename ResultType>
std::unique_ptr<DiscreteBoundaryOperator<ResultType>>
HMatGlobalAssembler<BasisFunctionType, ResultType>::assembleDetachedWeakForm(
    const Space<BasisFunctionType> &testSpace,
    const Space<BasisFunctionType> &trialSpace,
    const std::vector<LocalAssemblerForIntegralOperators *> &localAssemblers,
    const std::vector<LocalAssemblerForIntegralOperators *> &
        localAssemblersForAdmissibleBlocks,
    const std::vector<const DiscreteBndOp *> &sparseTermsToAdd,
    const std::vector<ResultType> &denseTermMultipliers,
    const std::vector<ResultType> &sparseTermMultipliers,
    const Context<BasisFunctionType, ResultType> &context, int symmetry) {

  const AssemblyOptions &options = context.assemblyOptions();
  const auto hMatParameterList =
      context.globalParameterList().sublist("HMatParameters");
  const bool indexWithGlobalDofs =
      (hMatParameterList.template get<std::string>("HMatAssemblyMode") ==
       "GlobalAssembly");
  const bool verbosityAtLeastDefault =
      (options.verbosityLevel() >= VerbosityLevel::DEFAULT);
  const bool verbosityAtLeastHigh =
      (options.verbosityLevel() >= VerbosityLevel::HIGH);

  auto testSpacePointer = Fiber::make_shared_from_const_ref(testSpace);
  auto trialSpacePointer = Fiber::make_shared_from_const_ref(trialSpace);

  shared_ptr<const Space<BasisFunctionType>> actualTestSpace;
  shared_ptr<const Space<BasisFunctionType>> actualTrialSpace;
  if (indexWithGlobalDofs) {
    actualTestSpace = testSpacePointer->discontinuousSpace(testSpacePointer);
    actualTrialSpace = trialSpacePointer->discontinuousSpace(trialSpacePointer);
  } else {
    actualTestSpace = testSpacePointer;
    actualTrialSpace = trialSpacePointer;
  }

  auto minBlockSize =
      hMatParameterList.template get<unsigned int>("minBlockSize");
  auto maxBlockSize =
      hMatParameterList.template get<unsigned int>("maxBlockSize");
  auto eta = hMatParameterList.template get<double>("eta");

  auto blockClusterTree = generateBlockClusterTree(
      *actualTestSpace, *actualTrialSpace, minBlockSize, maxBlockSize, eta);

  // blockClusterTree->writeToPdfFile("tree.pdf", 1024, 1024);

  WeakFormHMatAssemblyHelper<BasisFunctionType, ResultType> helper(
      *actualTestSpace, *actualTrialSpace, blockClusterTree, localAssemblers,
      sparseTermsToAdd, denseTermMultipliers, sparseTermMultipliers);

  // hmat::HMatrixDenseCompressor<ResultType, 2> compressor(helper);
  // shared_ptr<hmat::CompressedMatrix<ResultType>> hMatrix(
  //    new hmat::DefaultHMatrixType<ResultType>(blockClusterTree, compressor));

  hmat::HMatrixAcaCompressor<ResultType, 2> compressor(helper, 1E-3, 30);
  shared_ptr<hmat::CompressedMatrix<ResultType>> hMatrix(
      new hmat::DefaultHMatrixType<ResultType>(blockClusterTree, compressor));

  return std::unique_ptr<DiscreteBoundaryOperator<ResultType>>(
      new DiscreteHMatBoundaryOperator<ResultType>(hMatrix));

  // return std::unique_ptr<DiscreteBoundaryOperator<ResultType>>();
}

template <typename BasisFunctionType, typename ResultType>
std::unique_ptr<DiscreteBoundaryOperator<ResultType>>
HMatGlobalAssembler<BasisFunctionType, ResultType>::assembleDetachedWeakForm(
    const Space<BasisFunctionType> &testSpace,
    const Space<BasisFunctionType> &trialSpace,
    LocalAssemblerForIntegralOperators &localAssembler,
    LocalAssemblerForIntegralOperators &localAssemblerForAdmissibleBlocks,
    const Context<BasisFunctionType, ResultType> &context, int symmetry) {
  typedef LocalAssemblerForIntegralOperators Assembler;
  std::vector<Assembler *> localAssemblers(1, &localAssembler);
  std::vector<Assembler *> localAssemblersForAdmissibleBlocks(
      1, &localAssemblerForAdmissibleBlocks);
  std::vector<const DiscreteBndOp *> sparseTermsToAdd;
  std::vector<ResultType> denseTermsMultipliers(1, 1.0);
  std::vector<ResultType> sparseTermsMultipliers;

  return assembleDetachedWeakForm(testSpace, trialSpace, localAssemblers,
                                  localAssemblersForAdmissibleBlocks,
                                  sparseTermsToAdd, denseTermsMultipliers,
                                  sparseTermsMultipliers, context, symmetry);
}

FIBER_INSTANTIATE_CLASS_TEMPLATED_ON_BASIS_AND_RESULT(HMatGlobalAssembler);

} // namespace Bempp


#ifndef EXTRACTION_HELPER_FUNCTIONS_HPP
#define EXTRACTION_HELPER_FUNCTIONS_HPP

#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>

#include <limits>
#include <string>

#include "extractor/guidance/toolkit.hpp"

namespace osrm
{
namespace extractor
{

namespace detail
{

namespace qi = boost::spirit::qi;

template <typename Iterator> struct iso_8601_grammar : qi::grammar<Iterator, unsigned()>
{
    iso_8601_grammar() : iso_8601_grammar::base_type(root)

    {
        using qi::_1;
        using qi::_a;
        using qi::_b;
        using qi::_c;
        using qi::_pass;
        using qi::_val;
        using qi::eoi;
        using qi::eps;
        using qi::uint_;
        using qi::char_;

        hh = uint2_p[_pass = bind([](unsigned x) { return x < 24; }, _1), _val = _1];
        mm = uint2_p[_pass = bind([](unsigned x) { return x < 60; }, _1), _val = _1];
        ss = uint2_p[_pass = bind([](unsigned x) { return x < 60; }, _1), _val = _1];

        osm_time = (uint_p[_a = _1] >> eoi)[_val = _a * 60] |
                   (uint_p[_a = _1] >> ':' >> uint_p[_b = _1] >> eoi)[_val = _a * 3600 + _b * 60] |
                   (uint_p[_a = _1] >> ':' >> uint_p[_b = _1] >> ':' >> uint_p[_c = _1] >>
                    eoi)[_val = _a * 3600 + _b * 60 + _c];

        alternative_time =
            ('T' >> hh[_a = _1] >> mm[_b = _1] >> ss[_c = _1])[_val = _a * 3600 + _b * 60 + _c];

        extended_time = ('T' >> hh[_a = _1] >> ':' >> mm[_b = _1] >> ':' >>
                         ss[_c = _1])[_val = _a * 3600 + _b * 60 + _c];

        standard_time =
            ('T' >> -(uint_ >> char_("Hh"))[_a = _1] >> -(uint_ >> char_("Mm"))[_b = _1] >>
             -(uint_ >> char_("Ss"))[_c = _1])[_val = _a * 3600 + _b * 60 + _c];

        standard_date = (uint_ >> char_("Dd"))[_val = _1 * 86400];

        standard_week = (uint_ >> char_("Ww"))[_val = _1 * 604800];

        iso_period =
            osm_time[_val = _1] | ('P' >> standard_week >> eoi)[_val = _1] |
            ('P' >> (alternative_time[_a = 0, _b = _1] | extended_time[_a = 0, _b = _1] |
                     (eps[_a = 0, _b = 0] >> -standard_date[_a = _1] >> -standard_time[_b = _1])) >>
             eoi)[_val = _a + _b];

        root = iso_period;
    }

    qi::rule<Iterator, unsigned()> root;
    qi::rule<Iterator, unsigned(), qi::locals<unsigned, unsigned>> iso_period;
    qi::rule<Iterator, unsigned(), qi::locals<unsigned, unsigned, unsigned>> osm_time,
        standard_time, alternative_time, extended_time;
    qi::rule<Iterator, unsigned()> standard_date, standard_week;
    qi::rule<Iterator, unsigned()> hh, mm, ss;

    qi::uint_parser<unsigned, 10, 1, 2> uint_p;
    qi::uint_parser<unsigned, 10, 2, 2> uint2_p;
};
}

inline bool durationIsValid(const std::string &s)
{
    static detail::iso_8601_grammar<std::string::const_iterator> const iso_8601_grammar;

    std::string::const_iterator iter = s.begin();
    unsigned duration = 0;
    boost::spirit::qi::parse(iter, s.end(), iso_8601_grammar, duration);

    return !s.empty() && iter == s.end();
}

inline unsigned parseDuration(const std::string &s)
{
    static detail::iso_8601_grammar<std::string::const_iterator> const iso_8601_grammar;

    std::string::const_iterator iter = s.begin();
    unsigned duration = 0;
    boost::spirit::qi::parse(iter, s.end(), iso_8601_grammar, duration);

    return !s.empty() && iter == s.end() ? duration : std::numeric_limits<unsigned>::max();
}

inline std::string
trimLaneString(std::string lane_string, std::int32_t count_left, std::int32_t count_right)
{
    return extractor::guidance::trimLaneString(std::move(lane_string), count_left, count_right);
}

inline std::string applyAccessTokens(const std::string &lane_string,
                                     const std::string &access_tokens)
{
    return extractor::guidance::applyAccessTokens(lane_string, access_tokens);
}
}
}

#endif // EXTRACTION_HELPER_FUNCTIONS_HPP


#include <opencv2/imgproc/imgproc.hpp>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing.h>
#include <dlib/opencv.h>

#include "openbr/plugins/openbr_internal.h"

#include <QTemporaryFile>

using namespace std;
using namespace dlib;

namespace br
{

/*!
 * \ingroup transforms
 * \brief Wrapper to dlib's landmarker.
 * \author Scott Klum \cite sklum
 */
class DLibShapeResourceMaker : public ResourceMaker<shape_predictor>
{

private:
    shape_predictor *make() const
    {
        shape_predictor *sp = new shape_predictor();
        dlib::deserialize(qPrintable(Globals->sdkPath + "/share/openbr/models/dlib/shape_predictor_68_face_landmarks.dat")) >> *sp;
        return sp;
    }
};

class DLandmarkerTransform : public UntrainableTransform
{
    Q_OBJECT

private:
    Resource<shape_predictor> shapeResource;

    void init()
    {
        shapeResource.setResourceMaker(new DLibShapeResourceMaker());
        shapeResource.release(shapeResource.acquire()); // Pre-load one instance of the model
    }

    QPointF averagePoints(const QList<QPointF> &points, int rangeBegin, int rangeEnd) const
    {
        QPointF point;
        for (int i=rangeBegin; i<rangeEnd; i++)
            point += points[i];
        point /= (rangeEnd-rangeBegin);
        return point;
    }

    void setFacePoints(Template &dst) const
    {
        const QList<QPointF> points = dst.file.points();
        dst.file.set("RightEye", averagePoints(points, 36, 42));
        dst.file.set("LeftEye" , averagePoints(points, 42, 48));
        dst.file.set("Chin", points[8]);
    }

    void project(const Template &src, Template &dst) const
    {
        dst = src;

        shape_predictor *const sp = shapeResource.acquire();

        cv::Mat cvImage = src.m();
        if (cvImage.channels() == 3)
            cv::cvtColor(cvImage, cvImage, CV_BGR2GRAY);

        cv_image<unsigned char> cimg(cvImage);
        array2d<unsigned char> image;
        assign_image(image,cimg);

        rectangle r;
        if (src.file.rects().isEmpty()) { // If the image has no rects assume the whole image is a face
            r = rectangle(0, 0, cvImage.cols, cvImage.rows);
        } else { // Crop the image on the first rect
            const QRectF rect = src.file.rects().first();
            r = rectangle(rect.left(), rect.top(), rect.right(), rect.bottom());
        }

        full_object_detection shape = (*sp)(image, r);
        QList<QPointF> points;
        for (size_t i=0; i<shape.num_parts(); i++)
            points.append(QPointF(shape.part(i)(0), shape.part(i)(1)));
        dst.file.setPoints(points);
        setFacePoints(dst);

        shapeResource.release(sp);
    }
};

BR_REGISTER(Transform, DLandmarkerTransform)

/*!
 * \ingroup transforms
 * \brief Wrapper to dlib's trainable object detector.
 * \author Scott Klum \cite sklum
 */
class DObjectDetectorTransform : public Transform
{
    Q_OBJECT

    Q_PROPERTY(int winSize READ get_winSize WRITE set_winSize RESET reset_winSize STORED true)
    Q_PROPERTY(float C READ get_C WRITE set_C RESET reset_C STORED true)
    Q_PROPERTY(float epsilon READ get_epsilon WRITE set_epsilon RESET reset_epsilon STORED true)
    BR_PROPERTY(int, winSize, 80)
    BR_PROPERTY(float, C, 1)
    BR_PROPERTY(float, epsilon, .01)

private:
    typedef scan_fhog_pyramid<pyramid_down<6> > image_scanner_type;
    mutable object_detector<image_scanner_type> detector;
    mutable QMutex mutex;

    void train(const TemplateList &data)
    {
        dlib::array<array2d<unsigned char> > samples;
        std::vector<std::vector<rectangle> > boxes;

        foreach (const Template &t, data) {
            if (!t.file.rects().isEmpty()) {
                cv_image<unsigned char> cimg(t.m());

                array2d<unsigned char> image;
                assign_image(image,cimg);

                samples.push_back(image);

                std::vector<rectangle> b;
                foreach (const QRectF &r, t.file.rects())
                    b.push_back(rectangle(r.left(),r.top(),r.right(),r.bottom()));

                boxes.push_back(b);
            }
        }

        if (samples.size() == 0)
            qFatal("Training data has no bounding boxes.");

        image_scanner_type scanner;

        scanner.set_detection_window_size(winSize, winSize);
        structural_object_detection_trainer<image_scanner_type> trainer(scanner);
        trainer.set_num_threads(max(1,QThread::idealThreadCount()));
        trainer.set_c(C);
        trainer.set_epsilon(epsilon);

        if (Globals->verbose)
            trainer.be_verbose();

        detector = trainer.train(samples, boxes);
    }

    void project(const Template &src, Template &dst) const
   {
        dst = src;
        cv_image<unsigned char> cimg(src.m());
        array2d<unsigned char> image;
        assign_image(image,cimg);

        QMutexLocker locker(&mutex);
        std::vector<rectangle> dets = detector(image);
        locker.unlock();

        for (size_t i=0; i<dets.size(); i++)
            dst.file.appendRect(QRectF(QPointF(dets[i].left(),dets[i].top()),QPointF(dets[i].right(),dets[i].bottom())));
    }

    void store(QDataStream &stream) const
    {
        // Create local file
        QTemporaryFile tempFile;
        tempFile.open();
        tempFile.close();

        dlib::serialize(qPrintable(tempFile.fileName())) << detector;

        // Copy local file contents to stream
        tempFile.open();
        QByteArray data = tempFile.readAll();
        tempFile.close();
        stream << data;
    }

    void load(QDataStream &stream)
    {
        // Copy local file contents from stream
        QByteArray data;
        stream >> data;

        // Create local file
        QTemporaryFile tempFile(QDir::tempPath()+"/model");
        tempFile.open();
        tempFile.write(data);
        tempFile.close();

        // Load MLP from local file
        dlib::deserialize(qPrintable(tempFile.fileName())) >> detector;
    }
};

BR_REGISTER(Transform, DObjectDetectorTransform)

} // namespace br

#include "dlib.moc"


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2013   MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_OPTIMIZE_FMS_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_OPTIMIZE_FMS_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/fms.hpp>
#include <boost/simd/operator/functions/unary_minus.hpp>
#include <boost/simd/include/functions/fnma.hpp>
#include <boost/dispatch/dsl/category.hpp>
#include <boost/dispatch/functor/preprocessor/call.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT         ( fms_ , tag::formal_, (D)(A0)(A1)(A2)
                                   , ((node_<A0, boost::simd::tag::unary_minus_, mpl::long_<3> , D>))
                                     (unspecified_<A1>)
                                     (unspecified_<A2>)
                                   )
  {
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      fnma(boost::proto::child_c<0>(a0), a1, a2)
    )
  };

  BOOST_DISPATCH_IMPLEMENT         ( fms_ , tag::formal_, (D)(A0)(A1)(A2)
                                   , (unspecified_<A0>)
                                     ((node_<A1, boost::simd::tag::unary_minus_, mpl::long_<3> , D>))
                                     (unspecified_<A2>)
                                   )
  {
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      fnma(boost::proto::child_c<0>(a1), a0, a2)
    )
  };

  BOOST_DISPATCH_IMPLEMENT         ( fms_ , tag::formal_, (D)(A0)(A1)(A2)
                                   , ((node_<A0, boost::simd::tag::unary_minus_, mpl::long_<3> , D>))
                                     ((node_<A1, boost::simd::tag::unary_minus_, mpl::long_<3> , D>))
                                     (unspecified_<A2>)
                                   )
  {
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      fms(boost::proto::child_c<0>(a0), boost::proto::child_c<0>(a1), a2)
    )
  };
} } }

#endif