/* -*- c++ -*- */
/*
 * Copyright 2013 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "fading_model_impl.h"
#include <gnuradio/io_signature.h>
#include <iostream>

#include <boost/format.hpp>
#include <boost/random.hpp>

#include <gnuradio/fxpt.h>
#include <sincostable.h>

// FASTSINCOS:  0 = slow native,  1 = gr::fxpt impl,  2 = sincostable.h
#define FASTSINCOS  2

namespace gr {
  namespace channels {

    fading_model::sptr
    fading_model::make( unsigned int N, float fDTs, bool LOS, float K, int seed )
    {
      return gnuradio::get_initial_sptr
	(new fading_model_impl( N, fDTs, LOS, K, seed));
    }

    // Block constructor
    fading_model_impl::fading_model_impl( unsigned int N, float fDTs, bool LOS, float K, int seed )
      : sync_block("fading_model",
		       io_signature::make(1, 1, sizeof(gr_complex)),
		       io_signature::make(1, 1, sizeof(gr_complex))),
        d_fader(N,fDTs, LOS, K, seed)
    {
    }

    fading_model_impl::~fading_model_impl()
    {
    }

    void
    fading_model_impl::setup_rpc()
    {
#ifdef GR_CTRLPORT
    add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_get<fading_model, float >(
            alias(), "fDTs",
            &fading_model::fDTs,
            pmt::mp(0), pmt::mp(1), pmt::mp(0.01),
            "Hz*Sec", "normalized maximum doppler frequency (fD*Ts)",
            RPC_PRIVLVL_MIN, DISPTIME | DISPOPTSTRIP)));
    add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_set<fading_model, float >(
            alias(), "fDTs",
            &fading_model::set_fDTs,
            pmt::mp(0), pmt::mp(1), pmt::mp(0.01),
            "Hz*Sec", "normalized maximum doppler frequency (fD*Ts)",
            RPC_PRIVLVL_MIN, DISPTIME | DISPOPTSTRIP)));

    add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_get<fading_model, float >(
            alias(), "K",
            &fading_model::K,
            pmt::mp(0), pmt::mp(8), pmt::mp(4),
            "Ratio", "Rician factor (ratio of the specular power to the scattered power)",
            RPC_PRIVLVL_MIN, DISPTIME | DISPOPTSTRIP)));
    add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_set<fading_model, float >(
            alias(), "K",
            &fading_model::set_K,
            pmt::mp(0), pmt::mp(8), pmt::mp(4),
            "Ratio", "Rician factor (ratio of the specular power to the scattered power)",
            RPC_PRIVLVL_MIN, DISPTIME | DISPOPTSTRIP)));

    add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_get<fading_model, float >(
            alias(), "step",
            &fading_model::step,
            pmt::mp(0), pmt::mp(8), pmt::mp(4),
            "radians", "Maximum step size for random walk angle per sample",
            RPC_PRIVLVL_MIN, DISPTIME | DISPOPTSTRIP)));
    add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_set<fading_model, float >(
            alias(), "step",
            &fading_model::set_step,
            pmt::mp(0), pmt::mp(1), pmt::mp(0.00001),
            "radians", "Maximum step size for random walk angle per sample",
            RPC_PRIVLVL_MIN, DISPTIME | DISPOPTSTRIP)));
#endif /* GR_CTRLPORT */
    }

    int
    fading_model_impl::work (int noutput_items,
        gr_vector_const_void_star &input_items,
        gr_vector_void_star &output_items)
    {
        const gr_complex* in = (const gr_complex*) input_items[0];
        gr_complex* out = (gr_complex*) output_items[0];
        for(int i=0; i<noutput_items; i++){
            out[i] = in[i] * d_fader.next_sample();
        }
        return noutput_items;
    }

  } /* namespace channels */
} /* namespace gr */


#include "generate_data/single_triangle/region_setter.h"

#include "generate_data/constants.h"
#include "generate_data/get_points_from_subset.h"
#include "generate_data/remap_large.h"
#include "generate_data/to_tensor.h"
#include "generate_data/value_adder.h"
#include "lib/projection.h"
#include "meta/array_cat.h"

#include <ATen/ATen.h>
#include <Eigen/Dense>
#include <boost/geometry.hpp>
#include <boost/hana/ext/std/array.hpp>
#include <boost/hana/unpack.hpp>
#include <boost/multi_array.hpp>

#include <cmath>

namespace generate_data {
namespace single_triangle {
template <unsigned n_prior_dims> struct RegionSetter<n_prior_dims>::Impl {
  boost::multi_array<VectorT<float>, n_prior_dims> point_values;
  boost::multi_array<float, n_prior_dims + 1> overall_features;
  boost::multi_array<TorchIdxT, n_prior_dims> counts;
};

template <unsigned n_prior_dims>
RegionSetter<n_prior_dims>::RegionSetter() = default;
template <unsigned n_prior_dims>
RegionSetter<n_prior_dims>::~RegionSetter() = default;
template <unsigned n_prior_dims>
RegionSetter<n_prior_dims>::RegionSetter(RegionSetter &&) = default;
template <unsigned n_prior_dims>
RegionSetter<n_prior_dims> &
RegionSetter<n_prior_dims>::operator=(RegionSetter &&) = default;

template <unsigned n_prior_dims>
RegionSetter<n_prior_dims>::RegionSetter(
    const std::array<TorchIdxT, n_prior_dims> &prior_dims) {

  std::array<TorchIdxT, n_prior_dims + 1> feature_dims;
  std::copy(prior_dims.begin(), prior_dims.end(), feature_dims.begin());
  feature_dims[n_prior_dims] = constants.n_poly_feature_values;

  impl_ = std::make_unique<Impl>(Impl{
      .point_values{prior_dims},
      .overall_features{feature_dims},
      .counts{prior_dims},
  });
}

template <unsigned n_prior_dims>
double RegionSetter<n_prior_dims>::set_region(
    const std::array<TorchIdxT, n_prior_dims> &prior_idxs,
    const TriangleSubset &region, const Triangle &tri) {
  return region.visit_tagged([&](auto tag, const auto &value) {
    auto &im = *impl_;
    auto &item = im.point_values(prior_idxs);

    double area_3d = 0.;
    if constexpr (tag == TriangleSubsetType::None) {
      im.counts(prior_idxs) = 0;
      item.clear();
    } else {
      const auto pb = get_points_from_subset_with_baryo(tri, region);

      auto feature_idxs = array_cat(prior_idxs, std::array{TorchIdxT(0)});

      auto feature_adder = make_value_adder([&](float v, int idx) {
        feature_idxs[n_prior_dims] = idx;
        im.overall_features(feature_idxs) = v;
      });

      Eigen::Vector3d centroid3d = Eigen::Vector3d::Zero();
      Eigen::Vector2d centroid2d = Eigen::Vector2d::Zero();
      for (const auto &p : pb.points) {
        centroid3d += p;
      }
      for (const auto &p : pb.baryo) {
        centroid2d += baryo_to_eigen(p);
      }
      centroid3d /= pb.points.size();
      centroid2d /= pb.points.size();

      feature_adder.add_remap_all_values(centroid3d);
      feature_adder.add_values(centroid2d);

      im.counts(prior_idxs) = pb.baryo.size();

      // get areas in baryo and in 3d
      const TriPolygon *baryo_poly;
      if constexpr (tag == TriangleSubsetType::All) {
        baryo_poly = &full_triangle;
      } else {
        static_assert(tag == TriangleSubsetType::Some);

        baryo_poly = &value;
      }
      auto rot = find_rotate_vector_to_vector(
          tri.normal(), UnitVectorGen<double>::new_normalize({0., 0., 1.}));
      Eigen::Vector2d vec0 =
          (rot * (tri.vertices[1] - tri.vertices[0])).head(2);
      Eigen::Vector2d vec1 =
          (rot * (tri.vertices[2] - tri.vertices[0])).head(2);
      TriPolygon poly_distorted;
      auto add_point_to_distorted = [&](const BaryoPoint &p) {
        Eigen::Vector2d new_p = p.x() * vec0 + p.y() * vec1;
        boost::geometry::append(poly_distorted,
                                BaryoPoint{new_p.x(), new_p.y()});
      };
      for (const auto &p : pb.baryo) {
        add_point_to_distorted(p);
      }
      add_point_to_distorted(pb.baryo[0]);

      feature_adder.add_value(boost::geometry::area(*baryo_poly));
      area_3d = boost::geometry::area(poly_distorted);
      feature_adder.add_remap_all_value(area_3d);

      item.resize(pb.baryo.size() * constants.n_poly_point_values);

      auto value_adder =
          make_value_adder([&](float v, int idx) { item[idx] = v; });
      for (unsigned i = 0; i < pb.baryo.size(); ++i) {
        auto add_values_for_points = [&](const auto &prev, const auto &value,
                                         const auto &next, bool add_dotted,
                                         bool add_remapped) {
          value_adder.add_values(value);
          if (add_remapped) {
            value_adder.add_remap_multiscale_values(value);
          }

          // unnormalized edges can be trivially computed by net,
          // so no need to add them here
          auto edge_l = (prev - value).eval();
          double norm_l = edge_l.norm();
          value_adder.add_value(norm_l);
          if (add_remapped) {
            value_adder.add_remap_multiscale_value(norm_l);
          }
          auto normalized_l = edge_l.normalized().eval();
          value_adder.add_values(normalized_l);

          auto edge_r = (next - value).eval();
          double norm_r = edge_r.norm();
          value_adder.add_value(norm_r);
          if (add_remapped) {
            value_adder.add_remap_multiscale_value(norm_r);
          }
          auto normalized_r = edge_r.normalized().eval();
          value_adder.add_values(normalized_r);

          // should be identical for value and 3d (numerics aside)
          if (add_dotted) {
            double dotted = normalized_l.dot(normalized_r);

            value_adder.add_value(dotted);
            value_adder.add_value(std::acos(dotted));
          }
        };

        unsigned i_prev = (i == 0) ? pb.baryo.size() - 1 : i - 1;
        unsigned i_next = (i + 1) % pb.baryo.size();

        add_values_for_points(baryo_to_eigen(pb.baryo[i_prev]),
                              baryo_to_eigen(pb.baryo[i]),
                              baryo_to_eigen(pb.baryo[i_next]), true, false);
        add_values_for_points(pb.points[i_prev], pb.points[i],
                              pb.points[i_next], false, true);
      }
      debug_assert(value_adder.idx == int(item.size()));
      debug_assert(feature_adder.idx == constants.n_poly_feature_values);
    }

    return area_3d;
  });
}

template <unsigned n_prior_dims>
ATTR_NO_DISCARD_PURE PolygonInput RegionSetter<n_prior_dims>::as_poly_input() {
  TorchIdxT total_size = 0;
  auto &im = *impl_;
  for (unsigned i = 0; i < im.point_values.num_elements(); ++i) {
    const auto &vec = im.point_values.data()[i];
    debug_assert(vec.size() % constants.n_poly_point_values == 0);
    unsigned size = vec.size() / constants.n_poly_point_values;

    debug_assert(im.counts.data()[i] == TorchIdxT(size));
    total_size += size;
  }

  boost::multi_array<float, 2> point_values_out{
      boost::extents[total_size][constants.n_poly_point_values]};
  boost::multi_array<TorchIdxT, 1> item_to_left_idxs{std::array{total_size}};
  boost::multi_array<TorchIdxT, 1> item_to_right_idxs{std::array{total_size}};

  unsigned running_total = 0;
  for (unsigned i = 0; i < im.point_values.num_elements(); ++i) {
    const auto &vec = im.point_values.data()[i];

    unsigned size = vec.size() / constants.n_poly_point_values;
    unsigned running_total_prev = running_total;
    for (unsigned j = 0; j < size; ++j) {
      item_to_left_idxs[running_total] =
          (j == 0 ? size - 1 : j - 1) + running_total_prev;
      item_to_right_idxs[running_total] = (j + 1) % size + running_total_prev;
      for (int k = 0; k < constants.n_poly_point_values; ++k) {
        point_values_out[running_total][k] =
            vec[j * unsigned(constants.n_poly_point_values) + k];
      }
      ++running_total;
    }
  }
  always_assert(running_total == total_size);

  auto counts_out = to_tensor(im.counts);

  auto prefix_sum_counts =
      at::cat({at::tensor({TorchIdxT(0)}), counts_out.flatten().cumsum(0)});
  unsigned actual =
      prefix_sum_counts.index({-1}).item().template to<TorchIdxT>();
  always_assert(actual == total_size);

  return {
      .point_values = to_tensor(point_values_out),
      .overall_features = to_tensor(im.overall_features),
      .counts = counts_out,
      .prefix_sum_counts = prefix_sum_counts,
      .item_to_left_idxs = to_tensor(item_to_left_idxs),
      .item_to_right_idxs = to_tensor(item_to_right_idxs),
  };
}

template class RegionSetter<1>;
template class RegionSetter<2>;
template class RegionSetter<3>;
} // namespace single_triangle
} // namespace generate_data


/*******************************************************************************
*
*  Filename    : PlotSimFitVal.cc
*  Description : Plotting Pull distribution and others
*  Author      : Yi-Mu "Enoch" Chen [ ensc@hep1.phys.ntu.edu.tw ]
*
*******************************************************************************/
#include "TstarAnalysis/LimitCalc/interface/Common.hpp"
#include "TstarAnalysis/LimitCalc/interface/SimFitVal.hpp"

#include "ManagerUtils/Maths/interface/RooFitExt.hpp"
#include "ManagerUtils/PlotUtils/interface/Common.hpp"
#include "ManagerUtils/PlotUtils/interface/RooFitUtils.hpp"
#include "ManagerUtils/SampleMgr/interface/SampleMgr.hpp"
#include "TstarAnalysis/Common/interface/PlotStyle.hpp"

#include <algorithm>
#include <boost/format.hpp>
#include <fstream>

#include "RooDataSet.h"
#include "RooGaussian.h"
#include "RooRealVar.h"
#include "TGraphAsymmErrors.h"
#include "TMultiGraph.h"

using namespace std;
using namespace mgr;

/*******************************************************************************
*   Declaring global static variables
*******************************************************************************/
static RooRealVar p( "p", "p", 0, -7, 7 );

/*******************************************************************************
*   Defining datatype
*******************************************************************************/
const unsigned BKG_IDX = 0;
const unsigned SIG_IDX = 1;
const unsigned P1_IDX  = 2;
const unsigned P2_IDX  = 3;

/*******************************************************************************
*   Defining main control flows
*******************************************************************************/
void
PlotGenFit( const vector<string>& masslist )
{

  map<int, PullResult> pullresults;

  p.setRange( "reduce", -2, 2 );

  for( const auto& masspoint : masslist ){
    const int mass = GetInt( masspoint );
    pullresults[mass] = PlotSingleGenFit( masspoint );
    cout << mass << endl;
  }

  MakePullComparePlot( pullresults, BKG_IDX, "bkg"    );
  MakePullComparePlot( pullresults, SIG_IDX, "sig"    );
  // MakePullComparePlot( pullresults, P1_IDX ,"param1" );
  // MakePullComparePlot( pullresults, P2_IDX ,"param2" );

}

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

PullResult
PlotSingleGenFit( const std::string& masstag )
{
  const string strgthtag = SigStrengthTag();
  const string rhotag    = RhoTag();
  const string filename  = limnamer.TextFileName( "valsimfit", masstag, rhotag, strgthtag );
  ifstream result( filename );

  RooDataSet bkgset( "bkg", "bkg", RooArgSet( p ) );
  RooDataSet sigset( "sig", "sig", RooArgSet( p ) );
  RooDataSet p1set( "p1", "p1", RooArgSet( p ) );
  RooDataSet p2set( "p2", "p2", RooArgSet( p ) );
  RooDataSet signormset( "signorm", "signorm", RooArgSet( p ) );
  double bkg_real, sig_real, p1_real, p2_real;
  double bkg_fit, bkg_fiterr;
  double sig_fit, sig_fiterr;
  double p1_fit, p1_fiterr;
  double p2_fit, p2_fiterr;

  PullResult ans;

  // Begin reading files
  cout << "Reading file " << filename << endl;
  result >> bkg_real >> sig_real >> p1_real >> p2_real;

  while( result >> bkg_fit >>  bkg_fiterr >> sig_fit >>  sig_fiterr >> p1_fit >>  p1_fiterr >> p2_fit >>  p2_fiterr ){

    // Making pull distribution
    double bkgpull = ( bkg_fit - bkg_real ) / bkg_fiterr;
    double sigpull = ( sig_fit - sig_real ) / sig_fiterr;
    double p1pull  = ( p1_fit  - p1_real  ) / p1_fiterr;
    double p2pull  = ( p2_fit  - p2_real  ) / p2_fiterr;
    double signorm = ( sig_fit - sig_real ) / sig_real;

    if(
      bkgpull < -7 || bkgpull > +7 ||
      sigpull < -7 || sigpull > +7 ||
      p1pull  < -7 || p1pull  > +7 ||
      p2pull  < -7 || p2pull  > +7
      ){ continue; }

    p = bkgpull;
    bkgset.add( RooArgSet( p ) );
    p = sigpull;
    sigset.add( RooArgSet( p ) );
    p = p1pull;
    p1set.add( RooArgSet( p ) );
    p = p2pull;
    p2set.add( RooArgSet( p ) );
    p = signorm;
    signormset.add( RooArgSet( p ) );
  }

  ans.fitparm[BKG_IDX] = MakePullPlot( bkgset, masstag, "bkg"    );
  ans.fitparm[SIG_IDX] = MakePullPlot( sigset, masstag, "sig"    );
  ans.fitparm[P1_IDX]  = MakePullPlot( p1set,  masstag, "param1" );
  ans.fitparm[P2_IDX]  = MakePullPlot( p2set,  masstag, "param2" );

  return ans;
}

/*******************************************************************************
*   Plotting functions
*******************************************************************************/
pair<Parameter, Parameter>
MakePullPlot( RooDataSet& set, const string& masstag, const string& tag )
{
  RooRealVar mean( "m", "m", 0, -5, 5 );
  RooRealVar sigma( "s", "s", 1,  0, 5 );
  RooGaussian pullfit( "pull", "pull", p, mean, sigma );
  RooRealVar smean( "sm", "sm", 0, -5, 5 );
  RooRealVar ssigma( "ss", "ss", 1,  0, 5 );
  RooGaussian spullfit( "spull", "spull", p, smean, ssigma );
  p.setRange( "reduce", -2, 2 );

  TCanvas* c = mgr::NewCanvas();

  pullfit.fitTo( set,
    RooFit::Minimizer( "Minuit", "Migrad" ),
    RooFit::Minos( kTRUE ),
    RooFit::Verbose( kFALSE ),
    RooFit::PrintLevel( -1 ),
    RooFit::PrintEvalErrors( -1 ),
    RooFit::Warnings( kFALSE )
    );

  spullfit.fitTo( set,
    RooFit::Minimizer( "Minuit", "Migrad" ),
    RooFit::Minos( kTRUE ),
    RooFit::Verbose( kFALSE ),
    RooFit::PrintLevel( -1 ),
    RooFit::PrintEvalErrors( -1 ),
    RooFit::Warnings( kFALSE ),
    RooFit::Range( "reduce" )// Fitting to sub-range
    );

  RooPlot* frame  = p.frame();
  TGraph* setplot = mgr::PlotOn( frame, &set,
    RooFit::Binning( 39, p.getMin(), p.getMax() ),
    RooFit::DrawOption( PGS_DATA )
    );
  TGraph* fitplot = mgr::PlotOn( frame, &pullfit );
  TGraph* subplot = mgr::PlotOn( frame, &spullfit, RooFit::Range( "reduce" ) );
  frame->Draw();

  // Styling
  tstar::SetDataStyle( setplot );
  fitplot->SetLineColor( KBLUE );
  subplot->SetLineColor( KRED  );

  const double ymax = mgr::GetYmax( setplot, fitplot, subplot );
  frame->SetMaximum( ymax * 1.6 );
  frame->GetYaxis()->SetTitle("Events");

  // Calculation Kolmogorov-Smirnov Goodness-of-Fit Test
  const double ksres  = KSTest( set, pullfit, p );
  const double sksres = KSTest( set, spullfit, p, RooFit::CutRange( "reduce" ) );

  // Styling plots
  mgr::SetFrame( frame );
  mgr::DrawCMSLabelOuter( SIMULATION );
  mgr::DrawLuminosity( mgr::SampleMgr::TotalLuminosity() );

  // Title Formats
  boost::format xtitlefmt( "pull_{%s}" );
  boost::format signalfmt( "Sig. mass = %d GeV/c^{2}" );
  boost::format fitfmt(  "#mu = %.2lf_{#pm%.3lf}  #sigma = %.3lf_{#pm%.3lf}"  );
  boost::format sfitfmt( "#mu' = %.2lf_{#pm%.3lf} #sigma' = %.3lf_{#pm%.3lf}"  );
  boost::format ksfmt( "K = %.3lf" );
  boost::format injectfmt( "signal events %1% %2%" );

  const string xtitle_s = str( xtitlefmt % tag );
  const string signal_s = str( signalfmt % GetInt( masstag ) );
  const string fit_s    = str( fitfmt    % mean.getVal() % mean.getError() % sigma.getVal() % sigma.getError() );
  const string sfit_s   = str( sfitfmt   % smean.getVal()% smean.getError()%ssigma.getVal() %ssigma.getError() );
  const string inject_s = limnamer.CheckInput( "relmag" ) ? boost::str( injectfmt % 'x' % limnamer.GetInput<double>( "relmag" ) ) :
                          boost::str( injectfmt % '=' % limnamer.GetInput<double>( "absmag" ) );

  // Making titles
  frame->SetTitle( "" );
  frame->GetXaxis()->SetTitle( xtitle_s.c_str() );

  // Legend for marking
  TLegend* tl = mgr::NewLegend( 0.5, 0.6 );
  tl->AddEntry( fitplot,        fit_s.c_str(),                     "l" );
  tl->AddEntry( (TObject*)NULL, boost::str( ksfmt%ksres ).c_str(),  "" );
  tl->AddEntry( subplot,       sfit_s.c_str(),                     "l" );
  tl->AddEntry( (TObject*)NULL, boost::str( ksfmt%sksres ).c_str(), "" );
  tl->Draw();

  LatexMgr latex;
  latex.SetOrigin( PLOT_X_TEXT_MIN, PLOT_Y_TEXT_MAX, TOP_LEFT );
  latex.WriteLine( limnamer.GetChannelEXT( "Root Name" ) );
  latex.WriteLine( limnamer.GetExt<string>( "fitfunc", "Full Name" ) );
  latex.WriteLine( signal_s );
  latex.WriteLine( inject_s );

  mgr::SaveToPDF( c, limnamer.PlotFileName( "valpulldist", RhoTag(), masstag,  tag,  SigStrengthTag() ) );

  delete frame;
  delete c;

  return pair<Parameter, Parameter>( mean, sigma );
}

void
MakePullComparePlot(
  const map<int, PullResult>& pullresultlist,
  const unsigned idx,
  const string& tag
  )
{
  TCanvas* c = mgr::NewCanvas();
  c->SetLeftMargin( PLOT_X_MIN );
  c->SetRightMargin( 1 - PLOT_X_MAX );
  c->SetBottomMargin( PLOT_Y_MIN );
  c->SetTopMargin( 1 - PLOT_Y_MAX );

  TGraphAsymmErrors* graph   = new TGraphAsymmErrors( pullresultlist.size() );
  TGraphAsymmErrors* meanerr = new TGraphAsymmErrors( pullresultlist.size() );
  TGraphAsymmErrors* uperr   = new TGraphAsymmErrors( pullresultlist.size() );
  TGraphAsymmErrors* lowerr  = new TGraphAsymmErrors( pullresultlist.size() );

  unsigned i = 0;

  for( const auto& resultpair : pullresultlist ){
    const int mass          = resultpair.first;
    const PullResult result = resultpair.second;
    const Parameter fitmean = result.fitparm[idx].first;
    const Parameter fitsig  = result.fitparm[idx].second;

    graph->SetPoint( i, mass, fitmean.CentralValue() );
    meanerr->SetPoint( i, mass, fitmean.CentralValue() );
    uperr->SetPoint( i, mass, fitmean.CentralValue() + fitsig.CentralValue() );
    lowerr->SetPoint( i, mass, fitmean.CentralValue() - fitsig.CentralValue() );

    graph->SetPointError( i, 0, 0, fitsig.CentralValue(), fitsig.CentralValue() );
    meanerr->SetPointError( i, 0, 0, fabs( fitmean.AbsLowerError() ), fabs( fitmean.AbsUpperError() ) );
    uperr->SetPointError( i, 0, 0, fabs( fitsig.AbsLowerError() ), fabs( fitsig.AbsUpperError() ) );
    lowerr->SetPointError( i, 0, 0, fabs( fitsig.AbsUpperError() ), fabs( fitsig.AbsLowerError() ) );
    ++i;
  }

  TMultiGraph* mg = new TMultiGraph();
  mg->Add( meanerr, "A3" );
  mg->Add( uperr,   "A3" );
  mg->Add( lowerr,  "A3" );
  mg->Add( graph,   "LP" );

  mg->Draw( "A" );
  mg->SetTitle( "" );
  mg->GetXaxis()->SetTitle( "Signal mass (GeV/c^{2})" );
  mg->GetYaxis()->SetTitle( ( "pull_{"+tag+"}" ).c_str() );
  mg->SetMaximum( 3.25 );
  mg->SetMinimum( -1.75 );
  mgr::SetAxis( mg );
  mgr::DrawCMSLabel( SIMULATION );
  mgr::DrawLuminosity( mgr::SampleMgr::TotalLuminosity() );

  // Styling graphs
  graph->SetMarkerStyle( 21 );
  meanerr->SetFillStyle( 1001 );
  meanerr->SetFillColor( kBlue );
  meanerr->SetLineColor( kBlue );
  uperr->SetFillStyle( 1001 );
  uperr->SetFillColor( kCyan );
  uperr->SetFillColor( kCyan );
  lowerr->SetFillStyle( 1001 );
  lowerr->SetFillColor( kCyan );
  lowerr->SetFillColor( kCyan );

  const double xmin = mg->GetXaxis()->GetXmin();
  const double xmax = mg->GetXaxis()->GetXmax();
  TLine z( xmin, 0, xmax, 0 );
  TLine hi( xmin, 1, xmax, 1 );
  TLine lo( xmin, -1, xmax, -1 );
  z.SetLineColor( kRed );
  z.SetLineWidth( 3 );
  z.SetLineStyle( 2 );
  z.Draw();
  hi.SetLineColor( kBlue );
  hi.SetLineWidth( 2 );
  hi.SetLineStyle( 3 );
  hi.Draw();
  lo.SetLineColor( kBlue );
  lo.SetLineWidth( 2 );
  lo.SetLineStyle( 3 );
  lo.Draw();

  boost::format injectfmt( "signal events %1% %2%" );
  const string inject_s
    = limnamer.CheckInput( "relmag" ) ? boost::str( injectfmt % 'x' % limnamer.GetInput<double>( "relmag" ) ) :
      boost::str( injectfmt % '=' % limnamer.GetInput<double>( "absmag" ) );

  mgr::LatexMgr latex;
  latex.SetOrigin( PLOT_X_MIN, PLOT_Y_MAX+TEXT_MARGIN/2, BOTTOM_LEFT )
  .WriteLine( limnamer.GetChannelEXT( "Root Name" ) )
  .SetOrigin( PLOT_X_TEXT_MAX, PLOT_Y_TEXT_MAX, TOP_RIGHT )
  .WriteLine( limnamer.GetExt<string>( "fitfunc", "Full Name" ) )
  .WriteLine( inject_s );

  mgr::SaveToPDF( c, limnamer.PlotFileName( "pullvmass", RhoTag(), tag,  SigStrengthTag() ) );

  delete graph;
  delete meanerr;
  delete uperr;
  delete lowerr;
  delete mg;
  delete c;
}


#include <algorithm>
#include <array>
#include <map>
#include <numeric>
#include <stdexcept>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include <boost/functional/hash.hpp>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Kernel/global_functions.h>
#include <Euclid/Util/Assert.h>

namespace Euclid
{

namespace _impl
{

/** Convert face index order to a canonical form such that
 *  the first index is the smallest.*/
template<typename T, int N>
std::array<T, N> to_canonical(const std::array<T, N>& face)
{
    auto iter = std::min_element(face.begin(), face.end());
    std::array<T, N> canonical;
    for (size_t i = 0; i < N; ++i) {
        canonical[i] = *iter++;
        if (iter == face.end())
            iter = face.begin();
    }
    return canonical;
}

} // namespace _impl

template<typename T>
size_t remove_duplicate_vertices(std::vector<T>& positions)
{
    if (positions.empty()) {
        EWARNING("positions is empty.");
        return 0;
    }
    if (positions.size() % 3 != 0) {
        throw std::runtime_error("Input position size is not divisible by 3");
    }
    using Point = std::array<T, 3>;

    // Find duplicate points
    std::vector<size_t> marks;
    std::unordered_set<Point, boost::hash<Point>> unique_points;
    for (size_t i = 0; i < positions.size(); i += 3) {
        Point p{ { positions[i], positions[i + 1], positions[i + 2] } };
        auto [dummy, is_unique] = unique_points.insert(std::move(p));
        if (!is_unique) {
            marks.push_back(i);
        }
    }

    // Move elements in the back into slots to be removed
    size_t idx = positions.size() - 3;
    for (auto iter = marks.rbegin(); iter != marks.rend(); ++iter) {
        positions[*iter] = positions[idx];
        positions[*iter + 1] = positions[idx + 1];
        positions[*iter + 2] = positions[idx + 2];
        idx -= 3;
    }

    // Now drop the end
    positions.erase(positions.begin() + idx + 3, positions.end());
    positions.shrink_to_fit();

    return marks.size();
}

template<int N, typename T1, typename T2>
size_t remove_duplicate_vertices(std::vector<T1>& positions,
                                 std::vector<T2>& indices)
{
    static_assert(N >= 3);
    if (positions.empty()) {
        EWARNING("positions is empty.");
        return 0;
    }
    if (indices.empty()) {
        remove_duplicate_vertices(positions);
    }
    if (positions.size() % 3 != 0) {
        throw std::runtime_error("Input position size is not divisible by 3");
    }
    if (indices.size() % N != 0) {
        std::string err_str("Input index size is not divisible by ");
        err_str.append(std::to_string(N));
        throw(err_str);
    }
    if (*std::max_element(indices.begin(), indices.end()) >=
        static_cast<T2>(positions.size() / 3)) {
        throw std::runtime_error(
            "Input indices is out of range of the position vector");
    }
    using Point = std::array<T1, 3>;

    // marks[duplicate] = first
    // duplicate and first are indices into the position vector
    std::map<size_t, size_t> marks;

    // unique_points[point] = index
    // index refers to the position vector
    std::unordered_map<Point, size_t, boost::hash<Point>> unique_points;

    // Map all the duplicate positions to their first appearance
    for (size_t i = 0; i < positions.size(); i += 3) {
        Point p{ { positions[i], positions[i + 1], positions[i + 2] } };
        auto [iter, is_unique] = unique_points.try_emplace(std::move(p), i);
        if (!is_unique) {
            EASSERT(p == iter->first);
            marks[i] = iter->second;
        }
    }

    size_t idx = positions.size() - 3;

    // values in index_swap refers to the index of a Point
    std::vector<size_t> index_swap(positions.size() / 3);
    std::iota(index_swap.begin(), index_swap.end(), 0);

    for (auto iter = marks.rbegin(); iter != marks.rend(); ++iter, idx -= 3) {
        // Replace positions in marks with positions in the back of the vector
        auto pos = iter->first;
        positions[pos] = positions[idx];
        positions[pos + 1] = positions[idx + 1];
        positions[pos + 2] = positions[idx + 2];

        // Swap indices accordingly
        std::swap(index_swap[pos / 3], index_swap[idx / 3]);
    }

    // index_map[old] = new
    // old and new are indices of Point
    std::vector<size_t> index_map(positions.size() / 3);

    for (size_t i = 0; i < index_swap.size() - marks.size(); ++i) {
        EASSERT(marks.find(index_swap[i] * 3) == marks.end());
        index_map[index_swap[i]] = i;
    }
    for (size_t i = index_swap.size() - marks.size(); i < index_swap.size();
         ++i) {
        EASSERT(marks.find(index_swap[i] * 3) != marks.end());
        index_map[index_swap[i]] = index_map[marks[index_swap[i] * 3] / 3];
    }

    // Now fix indices
    for (auto& i : indices) {
        auto new_idx = index_map[i];
        EASSERT(new_idx < (positions.size() / 3 - marks.size()));
        i = static_cast<T2>(new_idx);
    }

    // Now erase garbage values at the tail of the vector
    positions.erase(positions.begin() + idx + 3, positions.end());
    positions.shrink_to_fit();

    return marks.size();
}

template<int N, typename T>
size_t remove_duplicate_faces(std::vector<T>& indices)
{
    static_assert(N >= 3);
    if (indices.empty()) {
        EWARNING("indices is empty.");
        return 0;
    }
    if (indices.size() % N != 0) {
        std::string err_str("Input index size is not divisible by ");
        err_str.append(std::to_string(N));
        throw(err_str);
    }
    using Face = std::array<T, N>;

    std::unordered_set<Face, boost::hash<Face>> unique_faces;
    std::vector<size_t> marks;
    for (size_t i = 0; i < indices.size(); i += N) {
        Face f;
        for (size_t j = 0; j < N; ++j) {
            f[j] = indices[i + j];
        }
        auto cf = _impl::to_canonical<T, N>(f);
        auto [iter, is_unique] = unique_faces.insert(std::move(cf));
        if (!is_unique) {
            marks.push_back(i);
        }
    }

    size_t idx = indices.size() - N;
    for (auto iter = marks.rbegin(); iter != marks.rend(); ++iter, idx -= N) {
        for (size_t i = 0; i < N; ++i) {
            indices[*iter + i] = indices[idx + i];
        }
    }

    indices.erase(indices.begin() + idx + N, indices.end());
    indices.shrink_to_fit();

    return marks.size();
}

template<int N, typename T1, typename T2>
size_t remove_unreferenced_vertices(std::vector<T1>& positions,
                                    std::vector<T2>& indices)
{
    static_assert(N >= 3);
    if (positions.empty()) {
        EWARNING("positions is empty.");
        return 0;
    }
    if (indices.empty()) {
        EWARNING("indices is empty.");
        return 0;
    }
    if (positions.size() % 3 != 0) {
        throw std::runtime_error("Input position size is not divisible by 3");
    }
    if (indices.size() % N != 0) {
        std::string err_str("Input index size is not divisible by ");
        err_str.append(std::to_string(N));
        throw(err_str);
    }
    if (*std::max_element(indices.begin(), indices.end()) >=
        static_cast<T2>(positions.size() / 3)) {
        throw std::runtime_error(
            "Input indices is out of range of the position vector");
    }

    std::vector<int> ref_count(positions.size() / 3, 0);
    for (auto i : indices) {
        ++ref_count[i];
    }

    size_t idx = ref_count.size() - 1;
    std::vector<T2> index_swap(ref_count.size());
    std::iota(index_swap.begin(), index_swap.end(), 0);
    for (int i = static_cast<int>(ref_count.size()) - 1; i >= 0; --i) {
        if (ref_count[i] == 0) {
            for (auto j = 0; j < 3; ++j) {
                positions[i * 3 + j] = positions[idx * 3 + j];
            }
            std::swap(index_swap[i], index_swap[idx--]);
        }
    }

    std::unordered_map<T2, size_t> index_map;
    for (size_t i = 0; i <= idx; ++i) {
        index_map[index_swap[i]] = i;
    }

    for (auto& i : indices) {
        i = static_cast<T2>(index_map[i]);
    }

    positions.erase(positions.begin() + (idx + 1) * 3, positions.end());
    positions.shrink_to_fit();

    return ref_count.size() - idx - 1;
}

template<int N, typename T1, typename T2>
size_t remove_degenerate_faces(const std::vector<T1>& positions,
                               std::vector<T2>& indices)
{
    static_assert(N >= 3);
    if (positions.empty()) {
        EWARNING("position is empty.");
        return 0;
    }
    if (indices.empty()) {
        EWARNING("indices is empty.");
        return 0;
    }
    if (positions.size() % 3 != 0) {
        throw std::runtime_error("Input position size is not divisible by 3");
    }
    if (indices.size() % N != 0) {
        std::string err_str("Input index size is not divisible by ");
        err_str.append(std::to_string(N));
        throw(err_str);
    }
    if (*std::max_element(indices.begin(), indices.end()) >=
        static_cast<T2>(positions.size() / 3)) {
        throw std::runtime_error(
            "Input indices is out of range of the position vector");
    }
    using Kernel = CGAL::Simple_cartesian<T1>;
    using Point_3 = typename Kernel::Point_3;

    std::vector<size_t> marks;
    for (size_t i = 0; i < indices.size(); i += N) {
        for (size_t j = 0; j < N - 1; ++j) {
            auto p0 = indices[i + j] * 3;
            auto p1 = indices[i + j + 1] * 3;
            auto p2 = (j == N - 2 ? indices[i] : indices[i + j + 2]) * 3;
            auto x0 = positions[p0];
            auto y0 = positions[p0 + 1];
            auto z0 = positions[p0 + 2];
            auto x1 = positions[p1];
            auto y1 = positions[p1 + 1];
            auto z1 = positions[p1 + 2];
            auto x2 = positions[p2];
            auto y2 = positions[p2 + 1];
            auto z2 = positions[p2 + 2];
            if (CGAL::collinear<Kernel>(Point_3{ x0, y0, z0 },
                                        Point_3{ x1, y1, z1 },
                                        Point_3{ x2, y2, z2 })) {
                marks.push_back(i);
                break;
            }
        }
    }

    auto idx = indices.size() - N;
    for (auto iter = marks.rbegin(); iter != marks.rend(); ++iter, idx -= N) {
        for (auto i = 0; i < N; ++i) {
            indices[*iter + i] = indices[idx + i];
        }
    }

    indices.erase(indices.begin() + idx + N, indices.end());
    indices.shrink_to_fit();

    return marks.size();
}

} // namespace Euclid


// Boost.Range 2.0 Extension library
// via PStade Oven Library
//
// Copyright Akira Takahashi 2011.
// Copyright Shunsuke Sogame 2005-2007.
// 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)

#include <iostream>
#include <boost/detail/lightweight_test.hpp>
#include <boost/range/algorithm/equal.hpp>

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

#include <boost/range/adaptor/regular_extension/filtered.hpp>
#include <boost/range/adaptor/dropped.hpp>
#include <boost/range/access/front.hpp>
#include <boost/range/single.hpp>
#include <boost/range/any_range.hpp>
#include <boost/range/join.hpp>
#include <boost/lambda/lambda.hpp>

typedef
    boost::any_range<int, boost::forward_traversal_tag, int&, std::ptrdiff_t>
range;

using boost::lambda::_1;
using namespace boost::adaptors;
using boost::range::access::front;
using boost::range::single;

range quick_sort(range rng)
{
    if (!rng)
        return rng;
    else {
        int& x = rng | front;
        range xs = rng | dropped(1);

        return
            boost::join(
                boost::join(
                    quick_sort(xs |+ filtered(_1 < x)),
                    single(x)
                ),
                quick_sort(xs |+ filtered(_1 >= x))
            );
    }
}

int main()
{
    std::vector<int> v = boost::assign::list_of(3)(2)(5)(4)(6)(1);

    std::cout << quick_sort(range(v)) << std::endl;

    BOOST_TEST(boost::equal(
        quick_sort(range(v)),
        boost::assign::list_of(1)(2)(3)(4)(5)(6)
    ));

    return boost::report_errors();
}


/*
  [auto_generated]
  boost/numeric/odeint/iterator/detail/const_step_time_iterator_impl.hpp

  [begin_description]
  tba.
  [end_description]

  Copyright 2009-2012 Karsten Ahnert
  Copyright 2009-2012 Mario Mulansky

  Distributed under 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_NUMERIC_ODEINT_ITERATOR_DETAIL_CONST_STEP_TIME_ITERATOR_IMPL_HPP_DEFINED
#define BOOST_NUMERIC_ODEINT_ITERATOR_DETAIL_CONST_STEP_TIME_ITERATOR_IMPL_HPP_DEFINED

#include <boost/numeric/odeint/util/unit_helper.hpp>
#include <boost/numeric/odeint/iterator/detail/ode_time_iterator_base.hpp>

namespace boost {
namespace numeric {
namespace odeint {

    /*
     * Specilization for steppers and error steppers
     */
    /**
     * \brief ODE Iterator with constant step size. The value type of this iterator is a pair of state type and
     * time type of the stepper.
     *
     * Implements an ODE iterator which solves the ODE with constant step size. The stepper must fulfill the Stepper
     * concept. const_step_time_iterator is a model of single-pass iterator.
     *
     * The value type of this iterator is a pair of state type and time type of the stepper.
     *
     * \tparam Stepper The stepper type which should be used during the iteration.
     * \tparam System The type of the system function (ODE) which should be solved.
     */
    template< class Stepper , class System >
    class const_step_time_iterator< Stepper , System , stepper_tag > : public detail::ode_time_iterator_base
    <
        const_step_time_iterator< Stepper , System , stepper_tag > ,
        Stepper , System
    >
    {
    private:

        typedef Stepper stepper_type;
        typedef System system_type;
        typedef typename boost::numeric::odeint::unwrap_reference< stepper_type >::type unwrapped_stepper_type;
        typedef typename unwrapped_stepper_type::state_type state_type;
        typedef typename unwrapped_stepper_type::time_type time_type;
        typedef typename unwrapped_stepper_type::value_type ode_value_type;
        typedef detail::ode_time_iterator_base<
            const_step_time_iterator< Stepper , System , stepper_tag > ,
            Stepper , System > base_type;

    public:

        /**
         * \brief Constructs a const_step_time_iterator. This constructor should be used to construct the begin iterator.
         *
         * \param stepper The stepper to use during the iteration.
         * \param sys The system function (ODE) to solve.
         * \param s The initial state. const_step_time_iterator stores a reference of s and changes its value during the iteration.
         * \param t The initial time.
         * \param t_end The end time, at which the iteration should stop.
         * \param dt The initial time step.
         */
        const_step_time_iterator( stepper_type stepper , system_type sys , state_type &s ,
                                  time_type t , time_type t_end , time_type dt )
            : base_type( stepper , sys , s , t , t_end , dt ) {}

        /**
         * \brief Constructs a const_step_time_iterator. This constructor should be used to construct the end iterator.
         *
         * \param stepper The stepper to use during the iteration.
         * \param sys The system function (ODE) to solve.
         * \param s The initial state. const_step_time_iterator stores a reference of s and changes its value during the iteration.
         */
        const_step_time_iterator( stepper_type stepper , system_type sys , state_type &s )
            : base_type( stepper , sys , s ) {}

    protected:

        friend class boost::iterator_core_access;

        void increment()
        {
            unwrapped_stepper_type &stepper = this->m_stepper;
            stepper.do_step( this->m_system , *this->m_state , this->m_t , this->m_dt );
            this->m_t += this->m_dt;
            this->check_end();
        }

    };

} // namespace odeint
} // namespace numeric
} // namespace boost

#endif // BOOST_NUMERIC_ODEINT_ITERATOR_DETAIL_CONST_STEP_TIME_ITERATOR_IMPL_HPP_DEFINED


#include <algorithm>
#include <cassert>
#include <sstream>

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

#include "Scalar.h"
#include "ScalarSum.h"

template <typename Archive>
void ScalarSum::serialize(Archive & ar, unsigned int const version) {
  if (version > 0) {
  } else {
  }
  ar & scalars;
}

template void ScalarSum::serialize<boost::archive::text_oarchive>(boost::archive::text_oarchive&, unsigned int const);
template void ScalarSum::serialize<boost::archive::text_iarchive>(boost::archive::text_iarchive&, unsigned int const);

ScalarSum::ScalarSum() : scalars(std::make_unique<std::vector<std::unique_ptr<Scalar>>>()) { }
ScalarSum::ScalarSum(Scalar const & scalar) : scalars(std::make_unique<std::vector<std::unique_ptr<Scalar>>>()) { scalars->push_back(std::make_unique<Scalar>(scalar)); }

void ScalarSum::Negate() { for (auto & scalar : *scalars) { scalar->Negate(); } }
void ScalarSum::DivideByTwo() { for (auto & scalar : *scalars) { scalar->DivideByTwo(); } }

void ScalarSum::AddScalar(Scalar const & other) { if (!other.IsZero()) { scalars->push_back(std::make_unique<Scalar>(other));} }
void ScalarSum::MergeWithOther(ScalarSum const & other) { scalars->insert(scalars->end(), std::make_move_iterator(other.scalars->begin()), std::make_move_iterator(other.scalars->end())); }
ScalarSum::ScalarSum(ScalarSum const & other) : scalars(std::make_unique<std::vector<std::unique_ptr<Scalar>>>()) {
  for (auto & scalar : *(other.scalars)) {
    auto scalar_new = std::make_unique<Scalar>(*scalar);
    scalars->push_back(std::make_unique<Scalar>());
    std::swap(*(--scalars->end()), scalar_new);
  }
}

std::string ScalarSum::ToString(std::string base_name, bool plus_sign) const {
  if (IsZero()) {
    if (plus_sign) {
      return "+ 0";
    } else{
      return "0";
    }
  }

  if (scalars->size() == 1) {
    return scalars->at(0)->ToString(base_name, plus_sign);
  }

  std::stringstream ss;
  if (plus_sign) {
    ss << "+ ";
  }
  ss << "(";
  bool first = true;
  for (auto & scalar : *scalars) {
    if (!first) {
      ss << " ";
    }
    ss << scalar->ToString(base_name, !first);
    first = false;
  }
  ss << ")";
  return ss.str();
}

void ScalarSum::EliminateVariable(size_t const variable) {
  scalars->erase(std::remove_if(scalars->begin(), scalars->end(),
    [variable] (auto const & a) {
      return a->VariableNumber() == variable;
    }), scalars->end());
}

void ScalarSum::SubstituteVariable(size_t const variable, ScalarSum const & scalar_sum_new) {
  auto scalars_new = std::make_unique<std::vector<std::unique_ptr<Scalar>>>();
  std::for_each(scalars->cbegin(), scalars->cend(),
    [variable,&scalars_new,&scalar_sum_new] (auto & a) {
      if (!a->ContainsVariable(variable)) {
        scalars_new->push_back(std::make_unique<Scalar>(*a));
      } else {
        size_t count = a->RemoveVariable(variable);
        ScalarSum sum_tmp (*a);
        for (size_t counter = 0; counter < count; ++counter) {
          sum_tmp = sum_tmp.MultiplyOther(scalar_sum_new);
        }
        sum_tmp.Sort();
        sum_tmp.Collect();
        std::for_each(sum_tmp.scalars->begin(), sum_tmp.scalars->end(),
          [&scalars_new] (auto const & b) {
            scalars_new->push_back(std::make_unique<Scalar>(*b));
          });
      }
    });

  std::swap(scalars, scalars_new);
}

void ScalarSum::SubstituteVariables(std::map<size_t, ScalarSum> substitution_map) {
  ScalarSum scalar_sum_new;
  std::for_each(scalars->cbegin(), scalars->cend(),
    [substitution_map,&scalar_sum_new] (auto const & a) {
      ScalarSum substituted;
      if (a->Order() == 0) {
        substituted.AddScalar(*a);
      } else {
        substituted.AddScalar(Scalar(a->get_coefficient()));
        auto variables = a->get_variables();
        std::for_each(variables.begin(), variables.end(),
          [&substituted,substitution_map] (auto v) {
            ScalarSum sum_tmp;
            sum_tmp.AddScalar(v);
            auto it = substitution_map.find(v);
            if (it != substitution_map.end()) {
              sum_tmp.SubstituteVariable(it->first, it->second);
            }
            substituted = substituted.MultiplyOther(sum_tmp);
          });
      }
      scalar_sum_new.MergeWithOther(substituted);
    });
  std::swap(scalars, scalar_sum_new.scalars);
}

void ScalarSum::SubstituteVariables(std::map<size_t, size_t> const & substitution_map) {
  std::for_each(scalars->begin(), scalars->end(),
    [&substitution_map] (auto & a) {
      a->SubstituteVariables(substitution_map);
    });
}

void ScalarSum::Sort() {
  std::sort(scalars->begin(), scalars->end(),
    [](auto & a, auto & b) {
      return *a < *b;
    });
}

void ScalarSum::Collect() {
  if (scalars->size() < 2) {
    return;
  }

  auto scalars_new = std::make_unique<std::vector<std::unique_ptr<Scalar>>>();

  auto last = scalars->begin();
  for (auto it = scalars->begin() + 1; it < scalars->end(); ++it) {
    if ((*last)->CompareVariables(**it)) {
      (*last)->AddOther(**it);
      if (std::distance(it, scalars->end()) == 1 && !(*last)->IsZero()) {
        scalars_new->push_back(std::make_unique<Scalar>());
        std::swap(*(--(scalars_new->end())), *last);
      }
    } else {
        if (!(*last)->IsZero()) {
          scalars_new->push_back(std::make_unique<Scalar>());
          std::swap(*(--(scalars_new->end())), *last);
        }
      if (std::distance(it, scalars->end()) == 1) {
        scalars_new->push_back(std::make_unique<Scalar>());
        std::swap(*(--(scalars_new->end())), *it);
      } else {
        last = it;
      }
    }
  }

  std::swap(scalars, scalars_new);
}

bool ScalarSum::IsZero() const {
  if (scalars->empty()) {
    return true;
  }

  bool zero = true;
  for (auto & scalar : *scalars) {
    if (!(scalar->IsZero())) {
      zero = false;
    }
  }

  return zero;
}

void ScalarSum::DivideCoefficient(Rational const & coeff) {
  std::for_each(scalars->begin(), scalars->end(),
    [&coeff](auto & a) {
      a->DivideCoefficient(coeff);
    });
}

void ScalarSum::MultiplyCoefficient(Rational const & coeff) {
  if (coeff.IsZero()) {
    auto scalars_new = std::make_unique<std::vector<std::unique_ptr<Scalar>>>();
    std::swap(scalars, scalars_new);
  } else {
  std::for_each(scalars->begin(), scalars->end(),
    [&coeff](auto & a) {
      a->MultiplyCoefficient(coeff);
    });
  }
}

Rational ScalarSum::Ratio(ScalarSum const & other) const {
  return Rational (scalars->front()->get_coefficient().DivideOther(other.scalars->front()->get_coefficient()));
}

std::set<size_t> ScalarSum::CoefficientSet() const {
  std::set<size_t> ret;
  std::for_each(scalars->begin(), scalars->end(),
    [&ret](auto & a) {
      ret.insert(a->VariableNumber());
    });

  return ret;
}

std::vector<Rational> ScalarSum::CoefficientVector(std::map<size_t, size_t> const & coefficient_map) const {
  std::vector<Rational> ret(coefficient_map.size());
  std::for_each(scalars->begin(), scalars->end(),
    [&ret,&coefficient_map](auto & a) {
      size_t var_number = coefficient_map.at(a->VariableNumber());
      ret[var_number] = ret[var_number].AddOther(a->get_coefficient());
    });

  return ret;
}

ScalarSum ScalarSum::MultiplyOther(ScalarSum const & other) const {
  if (scalars->empty() || other.scalars->empty()) {
    return ScalarSum();
  }

  ScalarSum ret;

  std::for_each(scalars->cbegin(), scalars->cend(),
    [&ret, &other] (auto const & a) {
      std::for_each(other.scalars->cbegin(), other.scalars->cend(),
        [&ret, &a] (auto const & b) {
          ret.AddScalar(a->MultiplyOther(*b));
        });
      });

  ret.Sort();
  ret.Collect();

  return ret;
}

ScalarSum & ScalarSum::operator=(ScalarSum const & other) {
  ScalarSum tmp(other);
  std::swap(tmp.scalars, this->scalars);

  return *this;
}

bool ScalarSum::operator==(ScalarSum const & other) const {
  ScalarSum this_copy (*this);
  ScalarSum other_copy (other);

  assert(this->ToString() == this_copy.ToString());

  assert(other.ToString() == other_copy.ToString());

  this_copy.Sort();
  other_copy.Sort();

  this_copy.Collect();
  other_copy.Collect();

  bool this_zero = this_copy.IsZero();
  bool other_zero = other_copy.IsZero();

  if (this_zero && other_zero) {
    return true;
  }

  if (this_zero != other_zero) {
    return false;
  }

  Rational ratio (this_copy.scalars->front()->get_coefficient().DivideOther(other_copy.scalars->front()->get_coefficient()));
  return std::equal(this_copy.scalars->cbegin(), this_copy.scalars->cend(), other_copy.scalars->cbegin(), other_copy.scalars->cend(),
    [&ratio](auto & a, auto & b) {
      Scalar b_tmp = *b;
      b_tmp.MultiplyCoefficient(ratio);
      return *a == b_tmp;
    });
}

bool ScalarSum::operator!=(ScalarSum const & other) const {
  return !(*this == other);
}

bool ScalarSum::operator<(ScalarSum const & other) const {
  ScalarSum this_copy (*this);
  ScalarSum other_copy (other);

  assert(this->ToString() == this_copy.ToString());

  assert(other.ToString() == other_copy.ToString());

  this_copy.Sort();
  other_copy.Sort();

  this_copy.Collect();
  other_copy.Collect();

  return std::lexicographical_compare(this_copy.scalars->cbegin(), this_copy.scalars->cend(), other_copy.scalars->cbegin(), other_copy.scalars->cend(),
    [] (auto & a, auto & b) {
      return *a < *b;
    });
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright 2015 Noblis                                                     *
 *                                                                           *
 * 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 <opencv2/imgproc/imgproc.hpp>
#include "openbr/plugins/openbr_internal.h"
#include "openbr/core/qtutils.h"
#include "openbr/core/opencvutils.h"
#include "openbr/core/eigenutils.h"
#include "openbr/core/common.h"
#include <QString>
#include <Eigen/SVD>
#include <Eigen/Dense>

using namespace std;
using namespace cv;
using namespace Eigen;

namespace br
{

// SynthesizePointsTransform helper class
struct TriangleIndicies
{
    int indicies[3];

    TriangleIndicies()
    {
        indicies[0] = 0;
        indicies[1] = 0;
        indicies[2] = 0;
    }

    TriangleIndicies(QList<int> indexList)
    {
        assert(indexList.size() == 3);
        qSort(indexList);
        indicies[0] = indexList[0];
        indicies[1] = indexList[1];
        indicies[2] = indexList[2];
    }
};

inline bool operator==(const TriangleIndicies &a, const TriangleIndicies &b)
{
    return (a.indicies[0] == b.indicies[0]) && (a.indicies[1] == b.indicies[1]) && (a.indicies[2] == b.indicies[2]);
}

inline uint qHash(const TriangleIndicies &key)
{
    return ::qHash(key.indicies[0]) ^ ::qHash(key.indicies[1]) ^ ::qHash(key.indicies[2]);
}

QDataStream &operator<<(QDataStream &stream, const TriangleIndicies &ti)
{
    return stream << ti.indicies[0] << ti.indicies[1] << ti.indicies[2];
}

QDataStream &operator>>(QDataStream &stream, TriangleIndicies &ti)
{
    return stream >> ti.indicies[0] >> ti.indicies[1] >> ti.indicies[2];
}

/*!
 * \ingroup transforms
 * \brief Synthesize additional points via triangulation.
 * \author Josh Klontz \cite jklontz
 */
class SynthesizePointsTransform : public MetadataTransform
{
    Q_OBJECT
    Q_PROPERTY(float minRelativeDistance READ get_minRelativeDistance WRITE set_minRelativeDistance RESET reset_minRelativeDistance STORED false)
    BR_PROPERTY(float, minRelativeDistance, 0) // [0, 1] range controlling whether or not to nearby synthetic points.
                                               // 0 = keep all points, 1 = keep only the most distance point.

    QList<TriangleIndicies> triangles;

    static QRectF getBounds(const QList<QPointF> &points, int dstPadding)
    {
        float srcMinX = FLT_MAX;
        float srcMinY = FLT_MAX;
        float srcMaxX = -FLT_MAX;
        float srcMaxY = -FLT_MAX;
        foreach (const QPointF &point, points) {
            if (point.x() < srcMinX) srcMinX = point.x();
            if (point.y() < srcMinY) srcMinY = point.y();
            if (point.x() > srcMaxX) srcMaxX = point.x();
            if (point.y() > srcMaxY) srcMaxY = point.y();
        }

        const float padding = (srcMaxX - srcMinX) / 80 * dstPadding;
        return QRectF(qRound(srcMinX - padding), qRound(srcMinY - padding), qRound(srcMaxX - srcMinX + 2 * padding), qRound(srcMaxY - srcMinY + 2 * padding));
    }

    static int getVertexIndex(const QPointF &trianglePts, const QList<QPointF> &pts)
    {
        for (int i = 0; i < pts.size(); i++)
            // Check points using single precision accuracy to avoid potential rounding error
            if ((float(trianglePts.x()) == float(pts[i].x())) && (float(trianglePts.y()) == float(pts[i].y())))
                return i;
        qFatal("Couldn't identify index of requested point!");
        return -1;
    }

    static QList<QList<int> > getTriangulation(const QList<QPointF> &points, const QRectF &bound)
    {
        Subdiv2D subdiv(OpenCVUtils::toRect(bound));
        foreach (const QPointF &point, points) {
            if (!bound.contains(point))
                return QList<QList<int> >();
            subdiv.insert(OpenCVUtils::toPoint(point));
        }

        vector<Vec6f> triangleList;
        subdiv.getTriangleList(triangleList);

        QList<QList<int> > triangleIndices;
        foreach (const Vec6f &triangle, triangleList) {
            bool valid = true;
            const QPointF vertices[3] = { QPointF(triangle[0], triangle[1]),
                                          QPointF(triangle[2], triangle[3]),
                                          QPointF(triangle[4], triangle[5]) };
            for (int j = 0; j < 3; j++)
                if (vertices[j].x() > bound.right() || vertices[j].y() > bound.bottom() || vertices[j].x() < bound.left() || vertices[j].y() < bound.top()) {
                    valid = false;
                    break;
                }

            if (valid) {
                QList<int> tri;
                for (int j = 0; j < 3; j++)
                    tri.append(getVertexIndex(vertices[j], points));
                triangleIndices.append(tri);
            }
        }

        return triangleIndices;
    }

    void train(const TemplateList &data)
    {
        // Because not all triangulations are the same, we have to decide on a canonical set of triangles at training time.
        QHash<TriangleIndicies, int> counts;
        foreach (const Template &datum, data) {
            const QList<QPointF> points = datum.file.points();
            if (points.size() <= 4)
                continue;

            const QList< QList<int> > triangulation = getTriangulation(points, getBounds(points, 10));
            if (triangulation.empty())
                continue;

            foreach (const QList<int> &indicies, triangulation)
                counts[TriangleIndicies(indicies)]++;
        }

        if (counts.empty())
            return;

        triangles.clear();
        QHash<TriangleIndicies, int>::const_iterator i = counts.constBegin();
        while (i != counts.constEnd()) {
            if (3 * i.value() > data.size())
                triangles.append(i.key()); // Keep triangles that occur in at least a third of the training instances
            ++i;
        }

        if (minRelativeDistance > 0) { // Discard relatively small triangles
            QVector<float> averageMinDistances(triangles.size());
            foreach (const Template &datum, data) {
                File dst;
                projectMetadata(datum.file, dst);
                const QList<QPointF> points = dst.points();

                QVector<float> minDistances(triangles.size());
                for (int i=0; i<triangles.size(); i++) {
                    // Work backwards so that we can also consider distances between synthetic points
                    const QPointF &point = points[points.size()-1-i];
                    float minDistance = std::numeric_limits<float>::max();
                    for (int j=0; j<points.size()-1-i; j++)
                        minDistance = min(minDistance, sqrtf(powf(point.x() - points[j].x(), 2.f) + powf(point.y() - points[j].y(), 2.f)));
                    minDistances[triangles.size()-1-i] = minDistance;
                }

                const float maxMinDistance = Common::Max(minDistances);
                for (int i=0; i<minDistances.size(); i++)
                    averageMinDistances[i] += (minDistances[i] / maxMinDistance);
            }

            const float maxAverageMinDistance = Common::Max(averageMinDistances);
            for (int i=averageMinDistances.size()-1; i>=0; i--)
                if (averageMinDistances[i] / maxAverageMinDistance < minRelativeDistance)
                    triangles.removeAt(i);
        }

        if (Globals->verbose)
            qDebug() << "Kept" << triangles.size() << "of" << counts.size() << "triangles.";
    }

    void projectMetadata(const File &src, File &dst) const
    {
        QList<QPointF> points = src.points();
        if (points.size() == 0) {
            dst.fte = true;
            return;
        }

        foreach (const TriangleIndicies &triangle, triangles) {
            const QPointF &p0 = points[triangle.indicies[0]];
            const QPointF &p1 = points[triangle.indicies[1]];
            const QPointF &p2 = points[triangle.indicies[2]];
            points.append((p0 + p1 + p2) / 3 /* append the centroid of the triangle */);
        }
        dst.setPoints(points);
    }

    void store(QDataStream &stream) const
    {
        stream << triangles;
    }

    void load(QDataStream &stream)
    {
        stream >> triangles;
    }
};

BR_REGISTER(Transform, SynthesizePointsTransform)

} // namespace br

#include "synthesizekeypoints.moc"


/**
 * @file penalty.hpp
 * @author Leonardo Arcari (leonardo1.arcari@gmail.com)
 * @version 1.0.0
 * @date 2018-10-28
 *
 * @copyright Copyright (c) 2018 Leonardo Arcari
 *
 * MIT Licence
 *
 * 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.
 *
 */

#ifndef BOOST_PENALTY_BASED_ALTERNATIVE_ROUTING_HPP
#define BOOST_PENALTY_BASED_ALTERNATIVE_ROUTING_HPP

#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/properties.hpp>

#include <arlib/terminators.hpp>
#include <arlib/type_traits.hpp>

#include <arlib/details/penalty_impl.hpp>

#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>

/**
 * An Alternative-Routing library for Boost.Graph
 */
namespace arlib {

/**
 * An implementation of Penalty method to compute alternative routes for
 * Boost::Graph.
 *
 * This implementation refers to the following publication:
 * Andreas Paraskevopoulos, Christos Zaroliagis. Improved Alternative Route
 * Planning. Daniele Frigioni and Sebastian Stiller. ATMOS - 13th Workshop on
 * Algorithmic Approaches for Transportation Modelling, Optimization, and
 * Systems - 2013, Sep 2013, Sophia Antipolis, France.
 *
 * @tparam Graph A Boost::VertexAndEdgeListGraph
 * @tparam WeightMap The weight or "length" of each edge in the graph. The
 *         weights must all be non-negative, and the algorithm will throw a
 *         negative_edge exception is one of the edges is negative. The type
 *         WeightMap must be a model of Readable Property Map. The edge
 *         descriptor type of the graph needs to be usable as the key type for
 *         the weight map. The value type for this map must be the same as the
 *         value type of the distance map.
 * @tparam MultiPredecessorMap The multi predecessor map records the edges in
 *         the alternative paths from @p s to @p t. Upon completion of the
 *         algorithm, for any vertex `v` on any alternative path `p`, multi
 *         predecessor map stores the predecessor of node `v` on path `p`. If no
 *         predecessor for a node `v'` is reported, then `v'` is not part of any
 *         alternative path or it is the source node @p s. The type of
 *         `MultiPredecessorMap` must be a model of `Read Property Map`. The
 *         vertex descriptor of the input graph @p G must be usable as a key
 *         type for the multi predecessor map. Whereas the value type must
 *         satisfy the `UnorderedAssociativeContainer` concept, where its key is
 *         an `int`, the index/number of alternative path for which it exists a
 *         predecessor of `v`, and where the value type is a vertex descriptor
 *         of input graph @p G.
 * @tparam Vertex The vertex descriptor.
 * @param G The input graph.
 * @param original_weight The weight map of @p G.
 * @param predecessors The multi predecessor map of @p G.
 * @param s The source node.
 * @param t The target node.
 * @param k The number of alternative paths to compute.
 * @param theta The similarity threshold.
 * @param p The penalty factor for edges in the candidate path.
 * @param r The penalty factor for edges incoming and outgoing to/from vertices
 *          of the candidate path.
 * @param max_nb_updates The maximum number of times an edge can be penalized.
 * @param max_nb_steps The maximum number of steps of the algorithm (timeout).
 * @param algorithm The routing kernel to employ. The default is
 *        routing_kernels::dijkstra
 *
 */
template <typename Graph, typename WeightMap, typename MultiPredecessorMap,
          typename Terminator = arlib::always_continue,
          typename Vertex = vertex_of_t<Graph>>
void penalty(const Graph &G, WeightMap const &original_weight,
             MultiPredecessorMap &predecessors, Vertex s, Vertex t, int k,
             double theta, double p, double r, int max_nb_updates,
             int max_nb_steps,
             routing_kernels algorithm = routing_kernels::dijkstra,
             Terminator &&terminator = Terminator{}) {
  using Length = length_of_t<Graph>;
  if (algorithm == routing_kernels::astar) {
    auto heuristic = details::distance_heuristic<Graph, Length>(G, t);
    auto routing_kernel = details::build_shortest_path_fn(
        algorithm, G, original_weight, heuristic);
    details::penalty(G, original_weight, predecessors, s, t, k, theta, p, r,
                     max_nb_updates, max_nb_steps, routing_kernel,
                     std::forward<Terminator>(terminator));
  } else {
    auto routing_kernel =
        details::build_shortest_path_fn(algorithm, G, original_weight);
    details::penalty(G, original_weight, predecessors, s, t, k, theta, p, r,
                     max_nb_updates, max_nb_steps, routing_kernel,
                     std::forward<Terminator>(terminator));
  }
}

/**
 * An implementation of Penalty method to compute alternative routes for
 * Boost::Graph.
 *
 * This overload takes an input graph modeling `PropertyGraph` concept having at
 * least one edge property with tag `boost::edge_weight_t`. Moreover it does not
 * require an explicit `WeightMap` parameter, because it is directly gathered
 * from the `PropertyGraph`.
 *
 * @see penalty(const Graph &G, WeightMap const &original_weight,
 *              MultiPredecessorMap &predecessors, Vertex s, Vertex t, int k,
 *              double theta, double p, double r, int max_nb_updates, int
 *              max_nb_steps, routing_kernels algorithm)
 *
 * @tparam PropertyGraph A Boost::PropertyGraph having at least one edge
 *         property with tag boost::edge_weight_t.
 */
template <typename PropertyGraph, typename MultiPredecessorMap,
          typename Terminator = arlib::always_continue,
          typename Vertex = vertex_of_t<PropertyGraph>>
void penalty(const PropertyGraph &G, MultiPredecessorMap &predecessors,
             Vertex s, Vertex t, int k, double theta, double p, double r,
             int max_nb_updates, int max_nb_steps,
             routing_kernels algorithm = routing_kernels::dijkstra,
             Terminator &&terminator = Terminator{}) {
  using namespace boost;
  using Edge = typename graph_traits<PropertyGraph>::edge_descriptor;

  BOOST_CONCEPT_ASSERT(
      (PropertyGraphConcept<PropertyGraph, Edge, edge_weight_t>));

  auto weight = get(edge_weight, G);
  penalty(G, weight, predecessors, s, t, k, theta, p, r, max_nb_updates,
          max_nb_steps, algorithm, std::forward<Terminator>(terminator));
}
} // namespace arlib

#endif


#include "RNGen.h"
#include "utils.h"
#include <boost/thread.hpp>

// Make sure each thread can have different values.
static boost::thread_specific_ptr<RNGen> thread_instance_;

// random seeding
int64_t cluster_seedgen(void) {
  int64_t s, seed, pid;
  FILE* f = fopen("/dev/urandom", "rb");
  if (f && fread(&seed, 1, sizeof(seed), f) == sizeof(seed)) {
    fclose(f);
    return seed;
  }

  if (f)
    fclose(f);

  pid = getpid();
  s = time(NULL);
  seed = std::abs(((s * 181) * ((pid - 83) * 359)) % 104729);
  return seed;
}

RNGen& RNGen::Get() {
  if (!thread_instance_.get()) {
    thread_instance_.reset(new RNGen());
  }
  return *(thread_instance_.get());
}

class RNGen::RNG::Generator {
public:
  Generator() : rng_(new rng_t(cluster_seedgen())) {}
  explicit Generator(unsigned int seed) : rng_(new rng_t(seed)) {}
  rng_t* rng() { return rng_.get(); }
private:
  shared_ptr<rng_t> rng_;
};

RNGen::RNG::RNG() : generator_(new Generator()) { }

RNGen::RNG::RNG(unsigned int seed) : generator_(new Generator(seed)) { }

RNGen::RNG& RNGen::RNG::operator=(const RNG& other) {
  generator_ = other.generator_;
  return *this;
}

void* RNGen::RNG::generator() {
  return static_cast<void*>(generator_->rng());
}


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

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// 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_ALGORITHMS_ASSIGN_VALUES_HPP
#define BOOST_GEOMETRY_ALGORITHMS_ASSIGN_VALUES_HPP

#include <cstddef>

#include <boost/concept/requires.hpp>
#include <boost/concept_check.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/if.hpp>
#include <boost/numeric/conversion/bounds.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/type_traits.hpp>

#include <boost/geometry/arithmetic/arithmetic.hpp>
#include <boost/geometry/algorithms/append.hpp>
#include <boost/geometry/algorithms/clear.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/exterior_ring.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/geometries/concepts/check.hpp>

#include <boost/geometry/util/for_each_coordinate.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace assign
{

template
<
    typename Box, std::size_t Index,
    std::size_t Dimension, std::size_t DimensionCount
>
struct initialize
{
    typedef typename coordinate_type<Box>::type coordinate_type;

    static inline void apply(Box& box, coordinate_type const& value)
    {
        geometry::set<Index, Dimension>(box, value);
        initialize<Box, Index, Dimension + 1, DimensionCount>::apply(box, value);
    }
};

template <typename Box, std::size_t Index, std::size_t DimensionCount>
struct initialize<Box, Index, DimensionCount, DimensionCount>
{
    typedef typename coordinate_type<Box>::type coordinate_type;

    static inline void apply(Box&,  coordinate_type const& )
    {}
};

template <typename Point>
struct assign_zero_point
{
    static inline void apply(Point& point)
    {
        geometry::assign_value(point, 0);
    }
};

template <typename BoxOrSegment>
struct assign_inverse_box_or_segment
{
    typedef typename point_type<BoxOrSegment>::type point_type;

    static inline void apply(BoxOrSegment& geometry)
    {
        typedef typename coordinate_type<point_type>::type bound_type;

        initialize
            <
                BoxOrSegment, 0, 0, dimension<BoxOrSegment>::type::value
            >::apply(
            geometry, boost::numeric::bounds<bound_type>::highest());
        initialize
            <
                BoxOrSegment, 1, 0, dimension<BoxOrSegment>::type::value
            >::apply(
            geometry, boost::numeric::bounds<bound_type>::lowest());
    }
};

template <typename BoxOrSegment>
struct assign_zero_box_or_segment
{
    static inline void apply(BoxOrSegment& geometry)
    {
        typedef typename coordinate_type<BoxOrSegment>::type coordinate_type;

        initialize
            <
                BoxOrSegment, 0, 0, dimension<BoxOrSegment>::type::value
            >::apply(geometry, coordinate_type());
        initialize
            <
                BoxOrSegment, 1, 0, dimension<BoxOrSegment>::type::value
            >::apply(geometry, coordinate_type());
    }
};

template
<
    std::size_t Corner1, std::size_t Corner2,
    typename Box, typename Point
>
inline void assign_box_2d_corner(Box const& box, Point& point)
{
    // Be sure both are 2-Dimensional
    assert_dimension<Box, 2>();
    assert_dimension<Point, 2>();

    // Copy coordinates
    typedef typename coordinate_type<Point>::type coordinate_type;

    geometry::set<0>(point, boost::numeric_cast<coordinate_type>(get<Corner1, 0>(box)));
    geometry::set<1>(point, boost::numeric_cast<coordinate_type>(get<Corner2, 1>(box)));
}

template
<
    typename Geometry, typename Point,
    std::size_t Index,
    std::size_t Dimension, std::size_t DimensionCount
>
struct assign_point_to_index
{

    static inline void apply(Point const& point, Geometry& geometry)
    {
        geometry::set<Index, Dimension>(geometry, boost::numeric_cast
            <
                typename coordinate_type<Geometry>::type
            >(geometry::get<Dimension>(point)));

        assign_point_to_index
            <
                Geometry, Point, Index, Dimension + 1, DimensionCount
            >::apply(point, geometry);
    }
};

template
<
    typename Geometry, typename Point,
    std::size_t Index,
    std::size_t DimensionCount
>
struct assign_point_to_index
    <
        Geometry, Point,
        Index,
        DimensionCount, DimensionCount
    >
{
    static inline void apply(Point const& , Geometry& )
    {
    }
};

template
<
    typename Geometry, typename Point,
    std::size_t Index,
    std::size_t Dimension, std::size_t DimensionCount
>
struct assign_point_from_index
{

    static inline void apply(Geometry const& geometry, Point& point)
    {
        geometry::set<Dimension>( point, boost::numeric_cast
            <
                typename coordinate_type<Point>::type
            >(geometry::get<Index, Dimension>(geometry)));

        assign_point_from_index
            <
                Geometry, Point, Index, Dimension + 1, DimensionCount
            >::apply(geometry, point);
    }
};

template
<
    typename Geometry, typename Point,
    std::size_t Index,
    std::size_t DimensionCount
>
struct assign_point_from_index
    <
        Geometry, Point,
        Index,
        DimensionCount, DimensionCount
    >
{
    static inline void apply(Geometry const&, Point&)
    {
    }
};

template <typename Geometry>
struct assign_2d_box_or_segment
{
    typedef typename coordinate_type<Geometry>::type coordinate_type;

    // Here we assign 4 coordinates to a box of segment
    // -> Most logical is: x1,y1,x2,y2
    // In case the user reverses x1/x2 or y1/y2, for a box, we could reverse them (THAT IS NOT IMPLEMENTED)

    template <typename Type>
    static inline void apply(Geometry& geometry,
                Type const& x1, Type const& y1, Type const& x2, Type const& y2)
    {
        geometry::set<0, 0>(geometry, boost::numeric_cast<coordinate_type>(x1));
        geometry::set<0, 1>(geometry, boost::numeric_cast<coordinate_type>(y1));
        geometry::set<1, 0>(geometry, boost::numeric_cast<coordinate_type>(x2));
        geometry::set<1, 1>(geometry, boost::numeric_cast<coordinate_type>(y2));
    }
};

}} // namespace detail::assign
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename GeometryTag, typename Geometry, std::size_t DimensionCount>
struct assign
{
    BOOST_MPL_ASSERT_MSG
        (
            false, NOT_OR_NOT_YET_IMPLEMENTED_FOR_THIS_GEOMETRY_TYPE
            , (types<Geometry>)
        );
};

template <typename Point>
struct assign<point_tag, Point, 2>
{
    typedef typename coordinate_type<Point>::type coordinate_type;

    template <typename T>
    static inline void apply(Point& point, T const& c1, T const& c2)
    {
        set<0>(point, boost::numeric_cast<coordinate_type>(c1));
        set<1>(point, boost::numeric_cast<coordinate_type>(c2));
    }
};

template <typename Point>
struct assign<point_tag, Point, 3>
{
    typedef typename coordinate_type<Point>::type coordinate_type;

    template <typename T>
    static inline void apply(Point& point, T const& c1, T const& c2, T const& c3)
    {
        set<0>(point, boost::numeric_cast<coordinate_type>(c1));
        set<1>(point, boost::numeric_cast<coordinate_type>(c2));
        set<2>(point, boost::numeric_cast<coordinate_type>(c3));
    }
};

template <typename Box>
struct assign<box_tag, Box, 2>
    : detail::assign::assign_2d_box_or_segment<Box>
{};

template <typename Segment>
struct assign<segment_tag, Segment, 2>
    : detail::assign::assign_2d_box_or_segment<Segment>
{};

template <typename GeometryTag, typename Geometry>
struct assign_zero {};

template <typename Point>
struct assign_zero<point_tag, Point>
    : detail::assign::assign_zero_point<Point>
{};

template <typename Box>
struct assign_zero<box_tag, Box>
    : detail::assign::assign_zero_box_or_segment<Box>
{};

template <typename Segment>
struct assign_zero<segment_tag, Segment>
    : detail::assign::assign_zero_box_or_segment<Segment>
{};

template <typename GeometryTag, typename Geometry>
struct assign_inverse {};

template <typename Box>
struct assign_inverse<box_tag, Box>
    : detail::assign::assign_inverse_box_or_segment<Box>
{};

template <typename Segment>
struct assign_inverse<segment_tag, Segment>
    : detail::assign::assign_inverse_box_or_segment<Segment>
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

/*!
\brief Assign two coordinates to a geometry (usually a 2D point)
\ingroup assign
\tparam Geometry \tparam_geometry
\tparam Type \tparam_numeric to specify the coordinates
\param geometry \param_geometry
\param c1 \param_x
\param c2 \param_y

\qbk{distinguish, 2 coordinate values}
\qbk{
[heading Example]
[assign_2d_point] [assign_2d_point_output]

[heading See also]
\* [link geometry.reference.algorithms.make.make_2_2_coordinate_values make]
}
 */
template <typename Geometry, typename Type>
inline void assign_values(Geometry& geometry, Type const& c1, Type const& c2)
{
    concept::check<Geometry>();

    dispatch::assign
        <
            typename tag<Geometry>::type,
            Geometry,
            geometry::dimension<Geometry>::type::value
        >::apply(geometry, c1, c2);
}

/*!
\brief Assign three values to a geometry (usually a 3D point)
\ingroup assign
\tparam Geometry \tparam_geometry
\tparam Type \tparam_numeric to specify the coordinates
\param geometry \param_geometry
\param c1 \param_x
\param c2 \param_y
\param c3 \param_z

\qbk{distinguish, 3 coordinate values}
\qbk{
[heading Example]
[assign_3d_point] [assign_3d_point_output]

[heading See also]
\* [link geometry.reference.algorithms.make.make_3_3_coordinate_values make]
}
 */
template <typename Geometry, typename Type>
inline void assign_values(Geometry& geometry,
            Type const& c1, Type const& c2, Type const& c3)
{
    concept::check<Geometry>();

    dispatch::assign
        <
            typename tag<Geometry>::type,
            Geometry,
            geometry::dimension<Geometry>::type::value
        >::apply(geometry, c1, c2, c3);
}

/*!
\brief Assign four values to a geometry (usually a box or segment)
\ingroup assign
\tparam Geometry \tparam_geometry
\tparam Type \tparam_numeric to specify the coordinates
\param geometry \param_geometry
\param c1 First coordinate (usually x1)
\param c2 Second coordinate (usually y1)
\param c3 Third coordinate (usually x2)
\param c4 Fourth coordinate (usually y2)

\qbk{distinguish, 4 coordinate values}
 */
template <typename Geometry, typename Type>
inline void assign_values(Geometry& geometry,
                Type const& c1, Type const& c2, Type const& c3, Type const& c4)
{
    concept::check<Geometry>();

    dispatch::assign
        <
            typename tag<Geometry>::type,
            Geometry,
            geometry::dimension<Geometry>::type::value
        >::apply(geometry, c1, c2, c3, c4);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_ASSIGN_VALUES_HPP


/*
 *  trajectory.hpp
 *
 *	Author(s): Tamas D. Nagy
 *	Created on: 2016-10-24
 *
 *  Generic class to store and manipulate trajectories
 *  of any corresponding data type.
 *
 */

#ifndef DVRK_TRAJECTORY_HPP_
#define DVRK_TRAJECTORY_HPP_

#include <iostream>
#include <ros/ros.h>
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <vector>
#include <fstream>
#include <string>
#include <stdexcept>
#include <algorithm>
#include <irob_utils/utils.hpp>
#include <irob_utils/tool_pose.hpp>
#include <irob_msgs/TrajectoryToolPose.h>

namespace saf {

template <class T>
class Trajectory
{
public:
  double dt;
private:
  std::vector<T> points;
public:
  Trajectory();
  Trajectory(double);
  Trajectory(std::string);
  Trajectory(const Trajectory<T>&);
  Trajectory(Trajectory<T>&&);
  Trajectory(const irob_msgs::TrajectoryToolPose&);
  void clear();
  void addPoint(T);
  T operator[](const int) const;
  void reverse();
  int size() const;
  double getLengthSec() const;
  void writeToFile(std::string) const;

  void swap(Trajectory<T>&);
  Trajectory<T> operator=(const Trajectory<T>&);

  Trajectory<T> operator+=(const Trajectory<T>&);
  Trajectory<T> operator+(const Trajectory<T>&);

  void transform(const Eigen::Affine3d&);

  void copyToRosTrajectory(irob_msgs::TrajectoryToolPose&);

  friend std::ostream& operator<<(std::ostream& os
                                  , const Trajectory<T>& tr)
  {
    //TODO fix
    os << "Trajectory:" << std::endl;
    for (int i = 0; i < tr.size(); i++)
      os << tr[i] << std::endl;
    return os;
  }

};

template <class T>
Trajectory<T>::Trajectory(): dt(0.1){}

template <class T>
Trajectory<T>::Trajectory(double dt): dt(dt){}

template <class T>
Trajectory<T>::Trajectory(std::string filename)
{
  std::ifstream logfile(filename.c_str());
  if (!logfile.is_open())
    throw std::runtime_error("Cannot open file " + filename);
  if (logfile.eof())
    throw std::runtime_error("Logfile " + filename + " is empty.");
  // The first line contains the dt
  logfile >> dt;

  while (!logfile.eof())
  {
    T v;
    logfile >> v;
    points.push_back(v);
    //std::cout << v << std::endl;
  }
  logfile.close();
}

template <class T>
Trajectory<T>::Trajectory(const Trajectory<T>& other): dt(other.dt)
{
  for (int i = 0; i < other.size(); i++)
    points.push_back(other[i]);
}

template <class T>
Trajectory<T>::Trajectory(Trajectory<T>&& other): dt(other.dt),
  points(std::move(other.points)) {}

template <class T>
void Trajectory<T>::swap(Trajectory<T>& other)
{
  Trajectory<T> tmp(*this);

  dt = other.dt;
  points = other.points;

  other.dt = tmp.dt;
  other.points = tmp.points;
}

template <class T>
Trajectory<T> Trajectory<T>::operator=(const Trajectory<T>& other)
{
  Trajectory<T> tmp(other);
  this->swap(tmp);
  return *this;
}

//Concat Trajectories
template <class T>
Trajectory<T> Trajectory<T>::operator+=(const Trajectory<T>& other)
{
  if (dt != other.dt)
    // TODO err
    for (T p : other.points)
      points.push_back(p);

  return *this;
}

//Concat Trajectories
template <class T>
Trajectory<T> Trajectory<T>::operator+(const Trajectory<T>& other)
{
  Trajectory<T> tmp(*this);
  tmp += other;

  return tmp;
}

// Transform trajectory in place
template <class T>
void Trajectory<T>::transform(const Eigen::Affine3d& G)
{
  for (T& p : points)
    p = G * p;
}

template <class T>
void Trajectory<T>::clear()
{
  points.clear();
}

template <class T>
void Trajectory<T>::addPoint(T p)
{
  points.push_back(p);
}

template <class T>
T Trajectory<T>::operator[](const int i) const
{
  return points[i];
}

template <class T>
void Trajectory<T>::reverse()
{
  std::reverse(points.begin(), points.end());
}

template <class T>
int Trajectory<T>::size() const
{
  return points.size();
}

template <class T>
double Trajectory<T>::getLengthSec() const
{
  return (points.size()-1)*dt;
}

template <class T>
void Trajectory<T>::writeToFile(std::string filename) const
{
  std::ofstream logfile;
  logfile.open (filename.c_str(), std::ofstream::out | std::ofstream::trunc);

  if (!logfile.is_open())
    throw std::runtime_error("Cannot open file " + filename);
  std::cout <<"Start logging to  " << filename << std::endl;

  // The first line is always the dt
  logfile << dt << std::endl;
  // One line contains one point in the trajectory
  for (T p : points)
    logfile << p << std::endl;

  logfile.flush();
  logfile.close();
  std::cout << "Trajectory of " << points.size()
            <<" points succesfully logged to " << filename << std::endl;
}

}

#endif


#define BOOST_TEST_MODULE cga_downsample
#include <boost/test/included/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/data/monomorphic.hpp>

#include "netvid/framebuffer.h"

#include "cga_downsample.h"
#include "bayer.h"

namespace bdata=boost::unit_test::data;

#define BOOST_TEST_INFO_VAR(var) \
	BOOST_TEST_INFO("With parameter " #var " = " << (var))

#define BOOST_TEST_INFO_VAR_HEX(var) \
	BOOST_TEST_INFO("With parameter " #var " = " << std::hex << (var))

namespace std
{
	template<class T, std::size_t n>
	std::ostream &operator<<(std::ostream &stream, const typename std::array<T, n> &a)
	{
		bool first=true;

		stream << "std::array<" << typeid(T).name() << ", " << n << ">{ ";

		for (const auto &v : a)
		{
			if (!first)
				stream << ", ";

			stream << v;

			first=false;
		}

		stream << " }";

		return stream;
	}
}

BOOST_AUTO_TEST_CASE(test_main)
{
	std::array<float, 3> srgb{ 173/255.0f, 170/255.0f, 173/255.0f };
	auto linear=to_linear(srgb);
	auto cga=eval_nearest_color(cga_palette(), linear);

	BOOST_TEST(cga==7);
}

BOOST_AUTO_TEST_CASE(palette_check)
{
	std::vector<std::array<float, 3>> palette;

	auto add_color=[&] (std::uint32_t color)
	{
		palette.push_back(to_float_srgb(fmt_a8r8g8b8, color));
	};
	auto add_colors=[&] (std::vector<std::uint32_t> colors)
	{
		for (auto color : colors)
			add_color(color);
	};

	add_colors(
			{
				0x000000,
				0x0000aa,
				0x00aa00,
				0x00aaaa,
				0xaa0000,
				0xaa00aa,
				0xaa5500,
				0xaaaaaa,
				0x555555,
				0x5555ff,
				0x55ff55,
				0x55ffff,
				0xff5555,
				0xff55ff,
				0xffff55,
				0xffffff,
			});

	for (std::size_t i=0; i<palette.size(); ++i)
	{
		auto srgb=palette[i];
		auto linear=to_linear(srgb);
		auto cga=eval_nearest_color(cga_palette(), linear);

		BOOST_TEST(cga==i);
	}

	for (std::size_t i=0; i<palette.size(); ++i)
	{
		auto srgb=palette[i];
		auto linear=to_linear(srgb);
		auto cga=eval_nearest_dithered_color(cga_palette(), allowed_dither, linear);
		bool left_ok=(cga.left_color==i && std::abs(cga.mix-0)<1e-3f);
		bool right_ok=(cga.right_color==i && std::abs(cga.mix-1)<1e-3f);
		bool ok=(left_ok || right_ok);

		BOOST_TEST(ok, int(cga.left_color) << "," << int(cga.right_color) << "," << cga.mix << " did not equal palette idx " << i);
	}

	dither_lut_t dither_lut(cga_palette(), [] (const std::array<float, 3> &target_color)
	{
		return eval_nearest_dithered_color(cga_palette(), allowed_dither, target_color);
	});

	for (std::size_t i=0; i<palette.size(); ++i)
	{
		auto srgb=palette[i];
		auto linear=to_linear(srgb);
		auto cga=dither_lut.get(linear);
		bool left_ok=(cga.left_color==i && std::fabs(0-cga.mix)<1e-6f);
		bool right_ok=(cga.right_color==i && std::fabs(1-cga.mix)<1e-6f);
		bool ok=(left_ok || right_ok);

		BOOST_TEST(ok, int(cga.left_color) << "," << int(cga.right_color) << "," << cga.mix << " did not equal palette idx " << i);
	}

	for (std::size_t i=0; i<palette.size(); ++i)
	{
		const auto tol=1-0.925f;
		auto srgb=palette[i];

		BOOST_TEST_INFO_VAR(i);
		BOOST_TEST_INFO_VAR(palette[i]);

		auto srgb_bits=from_float_srgb(fmt_r5g6b5, srgb);

		srgb=to_float_srgb(fmt_r5g6b5, srgb_bits);

		BOOST_TEST_INFO_VAR_HEX(srgb_bits);
		BOOST_TEST_INFO_VAR(srgb);

		auto linear=to_linear(srgb);
		auto cga=dither_lut.get(linear);
		bool left_ok=(cga.left_color==i && std::fabs(0-cga.mix)<tol);
		bool right_ok=(cga.right_color==i && std::fabs(1-cga.mix)<tol);
		bool ok=(left_ok || right_ok);

		BOOST_TEST_INFO_VAR(cga.left_color);
		BOOST_TEST_INFO_VAR(cga.right_color);
		BOOST_TEST_INFO_VAR(cga.mix);

		BOOST_TEST(ok);
	}
}

std::vector<std::tuple<int, int, int, int>> bayer_largest_pre_dataset()
{
	return
	{
		{
			4, 4, 2, 2
		},
		{
			6, 6, 3, 3
		},
		{
			4, 1, 2, 1
		}
	};
}

BOOST_DATA_TEST_CASE(bayer_largest_pre, bdata::make(bayer_largest_pre_dataset()), in_rows, in_cols, out_rows, out_cols)
{
	auto m=bayer::get_largest_predefined_map(in_rows, in_cols);

	BOOST_TEST(m.rows()==out_rows);
	BOOST_TEST(m.cols()==out_cols);
}

BOOST_AUTO_TEST_CASE(bayer_2x2)
{
	std::array<int, 2*2> values=
	{
		0, 2,
		3, 1
	};
	auto gen=bayer::generate(2, 2);

	BOOST_TEST(values==gen.values, boost::test_tools::per_element());
}

BOOST_AUTO_TEST_CASE(bayer_2x4)
{
	std::array<int, 2*4> values=
	{
		0, 4, 1, 5,
		6, 2, 7, 3
	};
	auto gen=bayer::generate(2, 4);

	BOOST_TEST(values==gen.values, boost::test_tools::per_element());
}

BOOST_AUTO_TEST_CASE(bayer_4x2)
{
	std::array<int, 4*2> values=
	{
		0, 4,
		6, 2,
		1, 5,
		7, 3
	};
	auto gen=bayer::generate(4, 2);

	BOOST_TEST(values==gen.values, boost::test_tools::per_element());
}

BOOST_AUTO_TEST_CASE(bayer_4x4)
{
	std::array<int, 4*4> values=
	{
		 0,  8,  2, 10,
		12,  4, 14,  6,
		 3, 11,  1,  9,
		15,  7, 13,  5,
	};
	auto gen=bayer::generate(4, 4);

	BOOST_TEST(values==gen.values, boost::test_tools::per_element());
}

BOOST_AUTO_TEST_CASE(bayer_8x8)
{
	static const int n=8;
	std::array<int, n*n> values=
	{
		0,32,8,40,2,34,10,42,
		48,16,56,24,50,18,58,26,
		12,44,4,36,14,46,6,38,
		60,28,52,20,62,30,54,22,
		3,35,11,43,1,33,9,41,
		51,19,59,27,49,17,57,25,
		15,47,7,39,13,45,5,37,
		63,31,55,23,61,29,53,21,
	};
	auto gen=bayer::generate(8, 8);

	BOOST_TEST(values==gen.values, boost::test_tools::per_element());
}

BOOST_AUTO_TEST_CASE(temporal_dither)
{
	typedef std::array<float, 3> s;
	const auto tol=1e-3f;
	auto linear_palette=cga_palette();
	//std::vector<int> indices;

	std::tie(linear_palette, /*indices*/std::ignore)=combine_palette(linear_palette);

	auto allowed_dither=[linear_palette] (int left, int right)
	{
		auto max_distance=.25f;
		auto left_color=linear_palette[left];
		auto right_color=linear_palette[right];

		return distance(left_color, right_color)<max_distance;
	};

	{
		auto target_color=s({ 1, 1, 1 });
		auto cga=eval_nearest_dithered_color(linear_palette, allowed_dither, target_color);
		//auto cga=dither_lut.get(c);
		bool left_ok=(linear_palette[cga.left_color]==target_color && std::fabs(0-cga.mix)<tol);
		bool right_ok=(linear_palette[cga.right_color]==target_color && std::fabs(1-cga.mix)<tol);
		bool ok=(left_ok || right_ok);

		BOOST_TEST_INFO_VAR(linear_palette[cga.left_color]);
		BOOST_TEST_INFO_VAR(linear_palette[cga.right_color]);
		BOOST_TEST_INFO_VAR(cga.mix);

		BOOST_TEST(ok);
	}

	return;

	auto dither_lut=dither_lut_t(linear_palette, [linear_palette, allowed_dither] (const std::array<float, 3> &target_color)
	{
		return eval_nearest_dithered_color(linear_palette, allowed_dither, target_color);
	});

	//auto b=dither_lut.get(s({ 0, 0, 0 }));
}