/*
 Copyright (C) 2017 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>
#include <ored/marketdata/marketimpl.hpp>
#include <ored/portfolio/builders/swap.hpp>
#include <ored/portfolio/builders/swaption.hpp>
#include <ored/portfolio/enginedata.hpp>
#include <ored/portfolio/swaption.hpp>
#include <ored/utilities/indexparser.hpp>
#include <ored/utilities/to_string.hpp>
#include <oret/toplevelfixture.hpp>
#include <ql/termstructures/volatility/swaption/swaptionconstantvol.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/time/calendars/target.hpp>
#include <ql/time/daycounters/actualactual.hpp>

using namespace QuantLib;
using namespace boost::unit_test_framework;
using namespace std;
using namespace ore::data;

namespace {

class TestMarket : public MarketImpl {
public:
    TestMarket() {
        asof_ = Date(2, January, 2017);

        // build discount
        yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")] = flatRateYts(0.03);

        // build swaption vols
        swaptionCurves_[make_pair(Market::defaultConfiguration, "EUR")] = flatSwaptionVol(0.30);

        Handle<IborIndex> h(parseIborIndex("EUR-EURIBOR-6M", flatRateYts(0.03)));
        iborIndices_[make_pair(Market::defaultConfiguration, "EUR-EURIBOR-6M")] = h;
    }

private:
    Handle<YieldTermStructure> flatRateYts(Real forward) {
        boost::shared_ptr<YieldTermStructure> yts(new FlatForward(0, NullCalendar(), forward, ActualActual()));
        return Handle<YieldTermStructure>(yts);
    }
    Handle<QuantLib::SwaptionVolatilityStructure>
    flatSwaptionVol(Volatility forward, VolatilityType type = ShiftedLognormal, Real shift = 0.0) {
        boost::shared_ptr<QuantLib::SwaptionVolatilityStructure> svs(
            new QuantLib::ConstantSwaptionVolatility(Settings::instance().evaluationDate(), NullCalendar(),
                                                     ModifiedFollowing, forward, ActualActual(), type, shift));
        return Handle<QuantLib::SwaptionVolatilityStructure>(svs);
    }
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(EuropeanSwaptionTests)

BOOST_AUTO_TEST_CASE(testEuropeanSwaptionPrice) {

    BOOST_TEST_MESSAGE("Testing Swaption Price...");

    Date today(3, Dec, 2018);
    Settings::instance().evaluationDate() = today;

    // build market
    boost::shared_ptr<Market> market = boost::make_shared<TestMarket>();
    Settings::instance().evaluationDate() = market->asofDate();

    // build Swaptions - expiry 5Y, term 10Y

    Calendar calendar = TARGET();
    Date qlStartDate = calendar.adjust(today + 5 * Years);
    Date qlEndDate = calendar.adjust(qlStartDate + 10 * Years);
    string startDate = ore::data::to_string(qlStartDate);
    string endDate = ore::data::to_string(qlEndDate);

    // schedules
    ScheduleData floatSchedule(ScheduleRules(startDate, endDate, "6M", "TARGET", "MF", "MF", "Forward"));
    ScheduleData fixedSchedule(ScheduleRules(startDate, endDate, "1Y", "TARGET", "MF", "MF", "Forward"));
    // fixed leg
    LegData fixedLeg(boost::make_shared<FixedLegData>(std::vector<Real>(1, 0.03)), true, "EUR", fixedSchedule, "30/360",
                     std::vector<Real>(1, 10000.0));
    // float leg
    LegData floatingLeg(boost::make_shared<FloatingLegData>("EUR-EURIBOR-6M", 2, false, std::vector<Real>(1, 0.0)),
                        false, "EUR", floatSchedule, "A360", std::vector<Real>(1, 10000.0));
    // leg vector
    vector<LegData> legs;
    legs.push_back(fixedLeg);
    legs.push_back(floatingLeg);

    Envelope env("CP1");
    OptionData optionData("Long", "Call", "European", true, vector<string>(1, startDate), "Cash");
    OptionData optionDataPhysical("Long", "Call", "European", true, vector<string>(1, startDate), "Physical");
    Real premium = 700.0;
    OptionData optionDataPremium("Long", "Call", "European", true, vector<string>(1, startDate), "Cash", "",
                                 PremiumData(premium, "EUR", qlStartDate));
    ore::data::Swaption swaptionCash(env, optionData, legs);
    ore::data::Swaption swaptionPhysical(env, optionDataPhysical, legs);
    ore::data::Swaption swaptionPremium(env, optionDataPremium, legs);

    Real expectedNpvCash = 565.19;
    Real premiumNpv = premium * market->discountCurve("EUR")->discount(calendar.adjust(qlStartDate));
    Real expectedNpvPremium = expectedNpvCash - premiumNpv;

    // Build and price
    boost::shared_ptr<EngineData> engineData = boost::make_shared<EngineData>();
    engineData->model("EuropeanSwaption") = "BlackBachelier";
    engineData->engine("EuropeanSwaption") = "BlackBachelierSwaptionEngine";
    engineData->model("Swap") = "DiscountedCashflows";
    engineData->engine("Swap") = "DiscountingSwapEngine";
    boost::shared_ptr<EngineFactory> engineFactory = boost::make_shared<EngineFactory>(engineData, market);
    engineFactory->registerBuilder(boost::make_shared<EuropeanSwaptionEngineBuilder>());
    engineFactory->registerBuilder(boost::make_shared<SwapEngineBuilder>());

    swaptionCash.build(engineFactory);
    swaptionPhysical.build(engineFactory);
    swaptionPremium.build(engineFactory);

    Real npvCash = swaptionCash.instrument()->NPV();
    Real npvPhysical = swaptionPhysical.instrument()->NPV();
    Real npvPremium = swaptionPremium.instrument()->NPV();

    BOOST_TEST_MESSAGE("Swaption, NPV Currency " << swaptionCash.npvCurrency());
    BOOST_TEST_MESSAGE("NPV Cash              = " << npvCash);
    BOOST_TEST_MESSAGE("NPV Physical          = " << npvPhysical);
    BOOST_TEST_MESSAGE("NPV Cash with premium = " << npvPremium);

    BOOST_CHECK_SMALL(npvCash - expectedNpvCash, 0.01);
    BOOST_CHECK_SMALL(npvPremium - expectedNpvPremium, 0.01);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2014 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_BOOLEAN_FUNCTIONS_SIMD_COMMON_IFNOTADD_HPP_INCLUDED
#define BOOST_SIMD_BOOLEAN_FUNCTIONS_SIMD_COMMON_IFNOTADD_HPP_INCLUDED

#include <boost/simd/boolean/functions/ifnotadd.hpp>
#include <boost/simd/include/functions/simd/if_zero_else.hpp>
#include <boost/simd/include/functions/simd/plus.hpp>
#include <boost/simd/sdk/meta/cardinal_of.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT_IF          ( ifnotadd_, tag::cpu_, (A0)(A1)(X)
                                       , (boost::mpl::equal_to < boost::simd::meta::cardinal_of<A0>
                                                               , boost::simd::meta::cardinal_of<A1>
                                                               >
                                         )
                                       , ((simd_<fundamental_<A0>,X>))
                                         ((simd_<fundamental_<A1>,X>))
                                         ((simd_<fundamental_<A1>,X>))
                                       )
  {
    typedef A1 result_type;
    BOOST_FORCEINLINE result_type
    operator()(A0 const& a0, A1 const& a1, A1 const& a2) const
    {
      return a1+if_zero_else(a0, a2);
    }
  };
} } }

#endif


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include "frame_field_deformer.h"

#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <vector>

#include <igl/cotmatrix_entries.h>
#include <igl/cotmatrix.h>
#include <igl/vertex_triangle_adjacency.h>

namespace igl
{

class Frame_field_deformer
{
public:

  IGL_INLINE Frame_field_deformer();
  IGL_INLINE ~Frame_field_deformer();

  // Initialize the optimizer
  IGL_INLINE void init(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F, const Eigen::MatrixXd& _D1, const Eigen::MatrixXd& _D2, double _Lambda, double _perturb_rotations, int _fixed = 1);

  // Run N optimization steps
  IGL_INLINE void optimize(int N, bool reset = false);

  // Reset optimization
  IGL_INLINE void reset_opt();

  // Precomputation of all components
  IGL_INLINE void precompute_opt();

  // Precomputation for deformation energy
  IGL_INLINE void precompute_ARAP(Eigen::SparseMatrix<double> & Lff, Eigen::MatrixXd & LfcVc);

  // Precomputation for regularization
  IGL_INLINE void precompute_SMOOTH(Eigen::SparseMatrix<double> & MS, Eigen::MatrixXd & bS);

  // extracts a r x c block from sparse matrix mat into sparse matrix m1
  // (r0,c0) is upper left entry of block
  IGL_INLINE void extractBlock(Eigen::SparseMatrix<double> & mat, int r0, int c0, int r, int c, Eigen::SparseMatrix<double> & m1);

  // computes optimal rotations for faces of m wrt current coords in mw.V
  // returns a 3x3 matrix
  IGL_INLINE void compute_optimal_rotations();

  // global optimization step - linear system
  IGL_INLINE void compute_optimal_positions();

  // compute the output XField from deformation gradient
  IGL_INLINE void computeXField(std::vector< Eigen::Matrix<double,3,2> > & XF);

  // computes in WW the ideal warp at each tri to make the frame field a cross
  IGL_INLINE void compute_idealWarp(std::vector< Eigen::Matrix<double,3,3> > & WW);

  // -------------------------------- Variables ----------------------------------------------------

  // Mesh I/O:

  Eigen::MatrixXd V;                         // Original mesh - vertices
  Eigen::MatrixXi F;                         // Original mesh - faces

  std::vector<std::vector<int> > VT;                   // Vertex to triangle topology
  std::vector<std::vector<int> > VTi;                  // Vertex to triangle topology

  Eigen::MatrixXd V_w;                       // Warped mesh - vertices

  std::vector< Eigen::Matrix<double,3,2> > FF;  	// frame field FF in 3D (parallel to m.F)
  std::vector< Eigen::Matrix<double,3,3> > WW;    // warping matrices to make a cross field (parallel to m.F)
  std::vector< Eigen::Matrix<double,3,2> > XF;  	// pseudo-cross field from solution (parallel to m.F)

  int fixed;

  double perturb_rotations; // perturbation to rotation matrices

  // Numerics
  int nfree,nconst;					          // number of free/constrained vertices in the mesh - default all-but-1/1
  Eigen::MatrixXd C;							            // cotangent matrix of m
  Eigen::SparseMatrix<double> L;					          // Laplacian matrix of m

  Eigen::SparseMatrix<double> M;					          // matrix for global optimization - pre-conditioned
  Eigen::MatrixXd RHS;						            // pre-computed part of known term in global optimization
  std::vector< Eigen::Matrix<double,3,3> > RW;    // optimal rotation-warping matrices (parallel to m.F) -- INCORPORATES WW
  Eigen::SimplicialCholesky<Eigen::SparseMatrix<double> > solver;   // solver for linear system in global opt.

  // Parameters
private:
  double Lambda = 0.1;				        // weight of energy regularization

};

  IGL_INLINE Frame_field_deformer::Frame_field_deformer() {}

  IGL_INLINE Frame_field_deformer::~Frame_field_deformer() {}

  IGL_INLINE void Frame_field_deformer::init(const Eigen::MatrixXd& _V,
                          const Eigen::MatrixXi& _F,
                          const Eigen::MatrixXd& _D1,
                          const Eigen::MatrixXd& _D2,
                          double _Lambda,
                          double _perturb_rotations,
                          int _fixed)
{
  V = _V;
  F = _F;

  assert(_D1.rows() == _D2.rows());

  FF.clear();
  for (unsigned i=0; i < _D1.rows(); ++i)
  {
    Eigen::Matrix<double,3,2> ff;
    ff.col(0) = _D1.row(i);
    ff.col(1) = _D2.row(i);
    FF.push_back(ff);
  }

  fixed = _fixed;
  Lambda = _Lambda;
  perturb_rotations = _perturb_rotations;

  reset_opt();
  precompute_opt();
}

IGL_INLINE void Frame_field_deformer::optimize(int N, bool reset)
{
  //Reset optimization
	if (reset)
    reset_opt();

	// Iterative Local/Global optimization
  for (int i=0; i<N;i++)
  {
    compute_optimal_rotations();
    compute_optimal_positions();
		computeXField(XF);
  }
}

IGL_INLINE void Frame_field_deformer::reset_opt()
{
  V_w = V;

  for (unsigned i=0; i<V_w.rows(); ++i)
    for (unsigned j=0; j<V_w.cols(); ++j)
      V_w(i,j) += (double(rand())/double(RAND_MAX))*10e-4*perturb_rotations;

}

// precomputation of all components
IGL_INLINE void Frame_field_deformer::precompute_opt()
{
  using namespace Eigen;
	nfree = V.rows() - fixed;						    // free vertices (at the beginning ov m.V) - global
  nconst = V.rows()-nfree;						// #constrained vertices
  igl::vertex_triangle_adjacency(V,F,VT,VTi);                // compute vertex to face relationship

  igl::cotmatrix_entries(V,F,C);							     // cotangent matrix for opt. rotations - global

  igl::cotmatrix(V,F,L);

	SparseMatrix<double> MA;						// internal matrix for ARAP-warping energy
	MatrixXd LfcVc;										  // RHS (partial) for ARAP-warping energy
	SparseMatrix<double> MS;						// internal matrix for smoothing energy
	MatrixXd bS;										    // RHS (full) for smoothing energy

	precompute_ARAP(MA,LfcVc);					// precompute terms for the ARAP-warp part
	precompute_SMOOTH(MS,bS);						// precompute terms for the smoothing part
	compute_idealWarp(WW);              // computes the ideal warps
  RW.resize(F.rows());								// init rotation matrices - global

  M =	  (1-Lambda)*MA + Lambda*MS;		// matrix for linear system - global

	RHS = (1-Lambda)*LfcVc + Lambda*bS;	// RHS (partial) for linear system - global
  solver.compute(M);									// system pre-conditioning
  if (solver.info()!=Eigen::Success) {fprintf(stderr,"Decomposition failed in pre-conditioning!\n"); exit(-1);}

	fprintf(stdout,"Preconditioning done.\n");

}

IGL_INLINE void Frame_field_deformer::precompute_ARAP(Eigen::SparseMatrix<double> & Lff, Eigen::MatrixXd & LfcVc)
{
  using namespace Eigen;
	fprintf(stdout,"Precomputing ARAP terms\n");
	SparseMatrix<double> LL = -4*L;
	Lff = SparseMatrix<double>(nfree,nfree);
  extractBlock(LL,0,0,nfree,nfree,Lff);
	SparseMatrix<double> Lfc = SparseMatrix<double>(nfree,nconst);
  extractBlock(LL,0,nfree,nfree,nconst,Lfc);
	LfcVc = - Lfc * V_w.block(nfree,0,nconst,3);
}

IGL_INLINE void Frame_field_deformer::precompute_SMOOTH(Eigen::SparseMatrix<double> & MS, Eigen::MatrixXd & bS)
{
  using namespace Eigen;
	fprintf(stdout,"Precomputing SMOOTH terms\n");

	SparseMatrix<double> LL = 4*L*L;

  // top-left
	MS = SparseMatrix<double>(nfree,nfree);
  extractBlock(LL,0,0,nfree,nfree,MS);

  // top-right
	SparseMatrix<double> Mfc = SparseMatrix<double>(nfree,nconst);
  extractBlock(LL,0,nfree,nfree,nconst,Mfc);

	MatrixXd MfcVc = Mfc * V_w.block(nfree,0,nconst,3);
	bS = (LL*V).block(0,0,nfree,3)-MfcVc;

}

  IGL_INLINE void Frame_field_deformer::extractBlock(Eigen::SparseMatrix<double> & mat, int r0, int c0, int r, int c, Eigen::SparseMatrix<double> & m1)
{
  std::vector<Eigen::Triplet<double> > tripletList;
  for (int k=c0; k<c0+c; ++k)
    for (Eigen::SparseMatrix<double>::InnerIterator it(mat,k); it; ++it)
    {
      if (it.row()>=r0 && it.row()<r0+r)
        tripletList.push_back(Eigen::Triplet<double>(it.row()-r0,it.col()-c0,it.value()));
    }
  m1.setFromTriplets(tripletList.begin(), tripletList.end());
}

IGL_INLINE void Frame_field_deformer::compute_optimal_rotations()
{
  using namespace Eigen;
  Matrix<double,3,3> r,S,P,PP,D;

  for (int i=0;i<F.rows();i++)
	{
		// input tri --- could be done once and saved in a matrix
		P.col(0) = (V.row(F(i,1))-V.row(F(i,0))).transpose();
		P.col(1) = (V.row(F(i,2))-V.row(F(i,1))).transpose();
		P.col(2) = (V.row(F(i,0))-V.row(F(i,2))).transpose();

		P = WW[i] * P;		// apply ideal warp

		// current tri
		PP.col(0) = (V_w.row(F(i,1))-V_w.row(F(i,0))).transpose();
		PP.col(1) = (V_w.row(F(i,2))-V_w.row(F(i,1))).transpose();
		PP.col(2) = (V_w.row(F(i,0))-V_w.row(F(i,2))).transpose();

		// cotangents
		D <<    C(i,2), 0,      0,
    0,      C(i,0), 0,
    0,      0,      C(i,1);

		S = PP*D*P.transpose();
		Eigen::JacobiSVD<Matrix<double,3,3> > svd(S, Eigen::ComputeFullU | Eigen::ComputeFullV );
		Matrix<double,3,3>  su = svd.matrixU();
		Matrix<double,3,3>  sv = svd.matrixV();
		r = su*sv.transpose();

		if (r.determinant()<0)  // correct reflections
		{
			su(0,2)=-su(0,2); su(1,2)=-su(1,2); su(2,2)=-su(2,2);
			r = su*sv.transpose();
		}
		RW[i] = r*WW[i];		// RW INCORPORATES IDEAL WARP WW!!!
	}
}

IGL_INLINE void Frame_field_deformer::compute_optimal_positions()
{
  using namespace Eigen;
	// compute variable RHS of ARAP-warp part of the system
  MatrixXd b(nfree,3);          // fx3 known term of the system
	MatrixXd X;										// result
  int t;		  									// triangles incident to edge (i,j)
	int vi,i1,i2;									// index of vertex i wrt tri t0

  for (int i=0;i<nfree;i++)
  {
    b.row(i) << 0.0, 0.0, 0.0;
    for (int k=0;k<(int)VT[i].size();k++)					// for all incident triangles
    {
      t = VT[i][k];												// incident tri
			vi = (i==F(t,0))?0:(i==F(t,1))?1:(i==F(t,2))?2:3;	// index of i in t
			assert(vi!=3);
			i1 = F(t,(vi+1)%3);
			i2 = F(t,(vi+2)%3);
			b.row(i)+=(C(t,(vi+2)%3)*RW[t]*(V.row(i1)-V.row(i)).transpose()).transpose();
			b.row(i)+=(C(t,(vi+1)%3)*RW[t]*(V.row(i2)-V.row(i)).transpose()).transpose();
    }
  }
  b/=2.0;
	b=-4*b;

	b*=(1-Lambda);		// blend

  b+=RHS;				// complete known term

	X = solver.solve(b);
	if (solver.info()!=Eigen::Success) {printf("Solving linear system failed!\n"); return;}

	// copy result to mw.V
  for (int i=0;i<nfree;i++)
    V_w.row(i)=X.row(i);

}

  IGL_INLINE void Frame_field_deformer::computeXField(std::vector< Eigen::Matrix<double,3,2> > & XF)
{
  using namespace Eigen;
  Matrix<double,3,3> P,PP,DG;
	XF.resize(F.rows());

  for (int i=0;i<F.rows();i++)
	{
		int i0,i1,i2;
		// indexes of vertices of face i
		i0 = F(i,0); i1 = F(i,1); i2 = F(i,2);

		// input frame
		P.col(0) = (V.row(i1)-V.row(i0)).transpose();
		P.col(1) = (V.row(i2)-V.row(i0)).transpose();
		P.col(2) = P.col(0).cross(P.col(1));

		// output triangle brought to origin
		PP.col(0) = (V_w.row(i1)-V_w.row(i0)).transpose();
		PP.col(1) = (V_w.row(i2)-V_w.row(i0)).transpose();
		PP.col(2) = PP.col(0).cross(PP.col(1));

		// deformation gradient
		DG = PP * P.inverse();
		XF[i] = DG * FF[i];
	}
}

// computes in WW the ideal warp at each tri to make the frame field a cross
  IGL_INLINE void Frame_field_deformer::compute_idealWarp(std::vector< Eigen::Matrix<double,3,3> > & WW)
{
  using namespace Eigen;

  WW.resize(F.rows());
	for (int i=0;i<(int)FF.size();i++)
	{
		Vector3d v0,v1,v2;
		v0 = FF[i].col(0);
		v1 = FF[i].col(1);
		v2=v0.cross(v1); v2.normalize();			// normal

		Matrix3d A,AI;								// compute affine map A that brings:
		A <<    v0[0], v1[0], v2[0],				//	first vector of FF to x unary vector
    v0[1], v1[1], v2[1],				//	second vector of FF to xy plane
    v0[2], v1[2], v2[2];				//	triangle normal to z unary vector
		AI = A.inverse();

		// polar decomposition to discard rotational component (unnecessary but makes it easier)
		Eigen::JacobiSVD<Matrix<double,3,3> > svd(AI, Eigen::ComputeFullU | Eigen::ComputeFullV );
		//Matrix<double,3,3>  au = svd.matrixU();
		Matrix<double,3,3>  av = svd.matrixV();
		DiagonalMatrix<double,3>	as(svd.singularValues());
		WW[i] = av*as*av.transpose();
	}
}

}

IGL_INLINE void igl::frame_field_deformer(
  const Eigen::MatrixXd& V,
  const Eigen::MatrixXi& F,
  const Eigen::MatrixXd& FF1,
  const Eigen::MatrixXd& FF2,
  Eigen::MatrixXd&       V_d,
  Eigen::MatrixXd&       FF1_d,
  Eigen::MatrixXd&       FF2_d,
  const int              iterations,
  const double           lambda,
  const bool             perturb_initial_guess)
{
  using namespace Eigen;
  // Solvers
  Frame_field_deformer deformer;

  // Init optimizer
  deformer.init(V, F, FF1, FF2, lambda, perturb_initial_guess ? 0.1 : 0);

  // Optimize
  deformer.optimize(iterations,true);

  // Copy positions
  V_d = deformer.V_w;

  // Allocate
  FF1_d.resize(F.rows(),3);
  FF2_d.resize(F.rows(),3);

  // Copy frame field
  for(unsigned i=0; i<deformer.XF.size(); ++i)
  {
    FF1_d.row(i) = deformer.XF[i].col(0);
    FF2_d.row(i) = deformer.XF[i].col(1);
  }
}

#ifdef IGL_STATIC_LIBRARY
// Explicit template specialization
#endif


#include <Eigen/Core>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/common/time.h>
#include <pcl/console/print.h>
#include <pcl/features/fpfh_omp.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/io/obj_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/visualization/pcl_visualizer.h>

#include <pcl/registration/super4pcs.h>

#include <gr/shared.h>
#include "../demo-utils.h"

// Types
typedef pcl::PointNormal PointNT;
typedef pcl::PointCloud<PointNT> PointCloudT;
typedef pcl::visualization::PointCloudColorHandlerCustom<PointNT> ColorHandlerT;

using namespace gr;

// Align a rigid object to a scene with clutter and occlusions
int
main (int argc, char **argv)
{
  // Point clouds
  PointCloudT::Ptr object (new PointCloudT);
  PointCloudT::Ptr object_aligned (new PointCloudT);
  PointCloudT::Ptr scene (new PointCloudT);

  // Get input object and scene
  if (argc < 4)
  {
    pcl::console::print_error ("Syntax is: %s scene.obj object.obj [PARAMS]\n", argv[0]);
    Demo::printParameterList();
    return (-1);
  }

  // Load object and scene
  pcl::console::print_highlight ("Loading point clouds...\n");
  if (pcl::io::loadOBJFile<PointNT> (argv[2], *object) < 0 ||
      pcl::io::loadOBJFile<PointNT> (argv[1], *scene) < 0)
  {
    pcl::console::print_error ("Error loading object/scene file!\n");
    return (-1);
  }

  if(int c = Demo::getArgs(argc, argv) != 0)
    {
      Demo::printUsage(argc, argv);
      exit(std::max(c,0));
    }

  pcl::Super4PCS<PointNT,PointNT> align;
  Demo::setOptionsFromArgs(align.options_);

  // Downsample
//  pcl::console::print_highlight ("Downsampling...\n");
//  pcl::VoxelGrid<PointNT> grid;
//  const float leaf = 0.005f;r
//  grid.setLeafSize (leaf, leaf, leaf);
//  grid.setInputCloud (object);
//  grid.filter (*object);
//  grid.setInputCloud (scene);
//  grid.filter (*scene);

  // Perform alignment
  pcl::console::print_highlight ("Starting alignment...\n");
  align.setInputSource (object);
  align.setInputTarget (scene);

  {
    pcl::ScopeTime t("Alignment");
    align.align (*object_aligned);
  }

  if (align.hasConverged ())
  {
    // Print results
    printf ("\n");
    Eigen::Matrix4f transformation = align.getFinalTransformation ();
    pcl::console::print_info ("    | %6.3f %6.3f %6.3f | \n", transformation (0,0), transformation (0,1), transformation (0,2));
    pcl::console::print_info ("R = | %6.3f %6.3f %6.3f | \n", transformation (1,0), transformation (1,1), transformation (1,2));
    pcl::console::print_info ("    | %6.3f %6.3f %6.3f | \n", transformation (2,0), transformation (2,1), transformation (2,2));
    pcl::console::print_info ("\n");
    pcl::console::print_info ("t = < %0.3f, %0.3f, %0.3f >\n", transformation (0,3), transformation (1,3), transformation (2,3));
    pcl::console::print_info ("\n");

    // Show alignment
    pcl::visualization::PCLVisualizer visu("Alignment - Super4PCS");
    visu.addPointCloud (scene, ColorHandlerT (scene, 0.0, 255.0, 0.0), "scene");
    visu.addPointCloud (object_aligned, ColorHandlerT (object_aligned, 0.0, 0.0, 255.0), "object_aligned");
    visu.spin ();
  }
  else
  {
    pcl::console::print_error ("Alignment failed!\n");
    return (-1);
  }

  return (0);
}


#include "CalibTracker/SiStripLorentzAngle/plugins/EnsembleCalibrationLA.h"
#include "CalibTracker/SiStripCommon/interface/Book.h"
#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/Framework/interface/ESHandle.h"
#include "Geometry/Records/interface/TrackerTopologyRcd.h"
#include <TChain.h>
#include <TFile.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <fstream>

namespace sistrip {

EnsembleCalibrationLA::EnsembleCalibrationLA(const edm::ParameterSet& conf) :
  inputFiles( conf.getParameter<std::vector<std::string> >("InputFiles") ),
  inFileLocation( conf.getParameter<std::string>("InFileLocation")),
  Prefix( conf.getUntrackedParameter<std::string>("Prefix","")),
  maxEvents( conf.getUntrackedParameter<unsigned>("MaxEvents",0)),
  samples( conf.getParameter<unsigned>("Samples")),
  nbins( conf.getParameter<unsigned>("NBins")),
  lowBin( conf.getParameter<double>("LowBin")),
  highBin( conf.getParameter<double>("HighBin")),
  vMethods( conf.getParameter<std::vector<int> >("Methods"))
{}

void EnsembleCalibrationLA::endJob()
{
  Book book("la_ensemble");
  TChain*const chain = new TChain("la_ensemble");
  BOOST_FOREACH(std::string file, inputFiles) chain->Add((file+inFileLocation).c_str());

  int methods = 0;  BOOST_FOREACH(unsigned method, vMethods) methods|=method;

  LA_Filler_Fitter laff(methods,samples,nbins,lowBin,highBin,maxEvents,tTopo_);
  laff.fill(chain,book);
  laff.fit(book);
  laff.summarize_ensembles(book);

  write_ensembles_text(book);
  write_ensembles_plots(book);
  write_samples_plots(book);
  write_calibrations();
}

void EnsembleCalibrationLA::endRun(const edm::Run&, const edm::EventSetup& eSetup)
{
  edm::ESHandle<TrackerTopology> tTopoHandle;
  eSetup.get<TrackerTopologyRcd>().get(tTopoHandle);
  tTopo_ = tTopoHandle.product();
}

void EnsembleCalibrationLA::write_ensembles_text(const Book& book)
{
  std::pair<std::string, std::vector<LA_Filler_Fitter::EnsembleSummary> > ensemble;
  BOOST_FOREACH(ensemble, LA_Filler_Fitter::ensemble_summary(book)) {
    std::fstream file((Prefix+ensemble.first+".dat").c_str(),std::ios::out);
    BOOST_FOREACH(LA_Filler_Fitter::EnsembleSummary summary, ensemble.second)
      file << summary << std::endl;

    const std::pair<std::pair<float,float>,std::pair<float,float> > line = LA_Filler_Fitter::offset_slope(ensemble.second);
    const float pull =  LA_Filler_Fitter::pull(ensemble.second);

    unsigned index = 15;
    std::string label;
    {
      std::cout << ensemble.first << std::endl;
      boost::regex format(".*(T[IO]B)_layer(\\d)([as])_(.*)");
      if(boost::regex_match(ensemble.first,format)) {
	const bool TIB = "TIB" == boost::regex_replace(ensemble.first, format, "\\1");
	const bool stereo = "s" == boost::regex_replace(ensemble.first, format, "\\3");
	const unsigned layer = boost::lexical_cast<unsigned>(boost::regex_replace(ensemble.first, format, "\\2"));
	label = boost::regex_replace(ensemble.first, format, "\\4");
	index = LA_Filler_Fitter::layer_index(TIB,stereo,layer);

	calibrations[label].slopes[index]=line.second.first;
	calibrations[label].offsets[index]=line.first.first;
	calibrations[label].pulls[index]=pull;
      }
    }

    file << std::endl << std::endl
	 << "# Best Fit Line: "
	 << line.first.first <<"("<< line.first.second<<")   +   x* "
	 << line.second.first<<"("<< line.second.second<<")"
	 << std::endl
	 << "# Pull (average sigma of (x_measure-x_truth)/e_measure): " << pull
	 << std::endl
	 << "LA_Calibration( METHOD_XXXXX , xxx, " << line.second.first << ", " << line.first.first << ", " << pull << ")," << std::endl;
    file.close();
  }
}

void EnsembleCalibrationLA::
write_ensembles_plots(const Book& book) const {
  TFile file((Prefix+"sampleFits.root").c_str(),"RECREATE");
  for(Book::const_iterator hist = book.begin(".*(profile|ratio|reconstruction|symm|symmchi2|_w\\d)"); hist!=book.end(); ++hist)
    hist->second->Write();
  file.Close();
}

void EnsembleCalibrationLA::
write_samples_plots(const Book& book) const {
  TFile file((Prefix+"ensembleFits.root").c_str(),"RECREATE");
  for(Book::const_iterator hist = book.begin(".*(measure|merr|ensembleReco|pull)"); hist!=book.end(); ++hist)
    hist->second->Write();
  file.Close();
}

void EnsembleCalibrationLA::
write_calibrations() const {
  std::fstream file((Prefix+"calibrations.dat").c_str(),std::ios::out);
  std::pair<std::string,MethodCalibrations> cal;
  BOOST_FOREACH(cal,calibrations) {
    file << cal.first << std::endl
	 << "\t slopes(";    BOOST_FOREACH(float i, cal.second.slopes) file << i<< ","; file << ")" << std::endl
	 << "\t offsets(";   BOOST_FOREACH(float i, cal.second.offsets) file << i<< ","; file << ")" << std::endl
	 << "\t pulls(";     BOOST_FOREACH(float i, cal.second.pulls) file << i<< ","; file << ")" << std::endl;
  }
  file.close();
}

}


#include <utility>
#include <vector>
#include <string>
#include <eosiolib/eosio.hpp>
#include <eosiolib/time.hpp>
#include <eosiolib/asset.hpp>
#include <eosiolib/contract.hpp>
#include <eosiolib/types.hpp>
#include <eosiolib/transaction.hpp>
#include <eosiolib/crypto.h>
#include <boost/algorithm/string.hpp>

using eosio::asset;
using eosio::permission_level;
using eosio::action;
using eosio::print;
using eosio::name;
using eosio::unpack_action_data;
using eosio::symbol_type;
using eosio::transaction;
using eosio::time_point_sec;

class EOSBetDice : public eosio::contract {
	public:
		const uint32_t TWO_MINUTES = 2 * 60;
		const uint64_t MINBET = 1000;
		const uint64_t HOUSEEDGE_times10000 = 200;
		const uint64_t HOUSEEDGE_REF_times10000 = 150;
		const uint64_t REFERRER_REWARD_times10000 = 50;

		const uint64_t BETID_ID = 1;
		const uint64_t TOTALAMTBET_ID = 2;
		const uint64_t TOTALAMTWON_ID = 3;
		const uint64_t LIABILITIES_ID = 4;

		EOSBetDice(account_name self):eosio::contract(self),
			activebets(_self, _self),
			globalvars(_self, _self),
			randkeys(_self, _self)
		{}

		// @abi action
		void initcontract(public_key randomness_key){

			require_auth(N(eosbetcasino));

			auto globalvars_itr = globalvars.begin();
			eosio_assert(globalvars_itr == globalvars.end(), "Contract is init");

			globalvars.emplace(_self, [&](auto& g){
				g.id = BETID_ID;
				g.val = 0;
			});

			globalvars.emplace(_self, [&](auto& g){
				g.id = TOTALAMTBET_ID;
				g.val = 0;
			});

			globalvars.emplace(_self, [&](auto& g){
				g.id = TOTALAMTWON_ID;
				g.val = 0;
			});

			globalvars.emplace(_self, [&](auto& g){
				g.id = LIABILITIES_ID;
				g.val = 0;
			});

			randkeys.emplace(_self, [&](auto & k){
				k.id = 1;
				k.key = randomness_key;
			});
		}

		// @abi action
		void newrandkey(public_key randomness_key){

			require_auth(N(eosbetcasino));

			auto rand_itr = randkeys.begin();
			randkeys.modify(rand_itr, _self, [&](auto& k){
				k.key = randomness_key;
			});
		}

		// @abi action
		void suspendbet(const uint64_t bet_id){

			require_auth(N(eosbetcasino));

			auto activebets_itr = activebets.find(bet_id);
			eosio_assert(activebets_itr != activebets.end(), "No bet exists");

			std::string bettor_str = name_to_string(activebets_itr->bettor);

			decrement_liabilities(activebets_itr->bet_amt);

			action(
				permission_level{_self, N(active)},
				N(eosio.token),
				N(transfer),
				std::make_tuple(
					_self,
					N(eosbettransf),
					asset(activebets_itr->bet_amt, symbol_type(S(4, EOS))),
					bettor_str
				)
			).send();

			activebets.erase(activebets_itr);
		}

		void transfer(uint64_t sender, uint64_t receiver) {

			auto transfer_data = unpack_action_data<st_transfer>();

			if (transfer_data.from == _self || transfer_data.from == N(eosbetcasino)){
				return;
			}

			eosio_assert( transfer_data.quantity.is_valid(), "Invalid asset");

			const uint64_t your_bet_amount = (uint64_t)transfer_data.quantity.amount;
			eosio_assert(MINBET <= your_bet_amount, "Must bet greater than min");

			increment_liabilities_bet_id(your_bet_amount);

			std::string roll_str;
			std::string ref_str;
			std::string seed_str;

			const std::size_t first_break = transfer_data.memo.find("-");
			roll_str = transfer_data.memo.substr(0, first_break);

			if(first_break != std::string::npos){

				const std::string after_first_break = transfer_data.memo.substr(first_break + 1);
				const std::size_t second_break = after_first_break.find("-");

				if(second_break != std::string::npos){

					ref_str = after_first_break.substr(0, second_break);
					seed_str = after_first_break.substr(second_break + 1);
				}
				else {

					ref_str = after_first_break;
					seed_str = std::string("");
				}
			}
			else {
				ref_str = std::string("");
				seed_str = std::string("");
			}

			account_name referral = N(eosbetcasino);

			const account_name possible_ref = eosio::string_to_name(ref_str.c_str());
			uint64_t house_edge = HOUSEEDGE_times10000;

			if (possible_ref != _self && possible_ref != transfer_data.from && is_account(possible_ref)){
				referral = possible_ref;
				house_edge = HOUSEEDGE_REF_times10000;
			}

			const uint64_t roll_under = std::stoull(roll_str, 0, 10);
			eosio_assert( roll_under >= 2 && roll_under <= 96, "Roll must be >= 2, <= 96.");

			const uint64_t your_win_amount = (your_bet_amount * get_payout_mult_times10000(roll_under, house_edge) / 10000) - your_bet_amount;
			eosio_assert(your_win_amount <= get_max_win(), "Bet less than max");

			checksum256 user_seed_hash;
			sha256( (char *)&seed_str, seed_str.length(), &user_seed_hash );

			auto s = read_transaction(nullptr, 0);
		    char *tx = (char *)malloc(s);
		    read_transaction(tx, s);
		    checksum256 tx_hash;
		    sha256(tx, s, &tx_hash);

			st_seeds seeds;
			seeds.seed1 = user_seed_hash;
			seeds.seed2 = tx_hash;

			checksum256 seed_hash;
			sha256( (char *)&seeds.seed1, sizeof(seeds.seed1) * 2, &seed_hash);

			const uint64_t bet_id = ((uint64_t)tx_hash.hash[0] << 56) + ((uint64_t)tx_hash.hash[1] << 48) + ((uint64_t)tx_hash.hash[2] << 40) + ((uint64_t)tx_hash.hash[3] << 32) + ((uint64_t)tx_hash.hash[4] << 24) + ((uint64_t)tx_hash.hash[5] << 16) + ((uint64_t)tx_hash.hash[6] << 8) + (uint64_t)tx_hash.hash[7];

			activebets.emplace(_self, [&](auto& bet){
				bet.id = bet_id;
				bet.bettor = transfer_data.from;
				bet.referral = referral;
				bet.bet_amt = your_bet_amount;
				bet.roll_under = roll_under;
				bet.seed = seed_hash;
				bet.bet_time = time_point_sec(now());
			});
		}

		// @abi action
		void resolvebet(const uint64_t bet_id, signature sig) {

			require_auth2(N(eosbetcasino), N(random));

			auto activebets_itr = activebets.find( bet_id );
			eosio_assert(activebets_itr != activebets.end(), "Bet doesn't exist");

			auto key_entry = randkeys.get(1);
			public_key rand_signing_key = key_entry.key;
			assert_recover_key(&activebets_itr->seed, (const char *)&sig, sizeof(sig), (const char *)&rand_signing_key, sizeof(rand_signing_key));

			checksum256 random_num_hash;
			sha256( (char *)&sig, sizeof(sig), &random_num_hash );

			const uint64_t random_roll = ((random_num_hash.hash[0] + random_num_hash.hash[1] + random_num_hash.hash[2] + random_num_hash.hash[3] + random_num_hash.hash[4] + random_num_hash.hash[5] + random_num_hash.hash[6] + random_num_hash.hash[7]) % 100) + 1;

			uint64_t edge = HOUSEEDGE_times10000;
			uint64_t ref_reward = 0;
			uint64_t payout = 0;
			if (activebets_itr->referral != N(eosbetcasino)){
				edge = HOUSEEDGE_REF_times10000;
				ref_reward = activebets_itr->bet_amt * REFERRER_REWARD_times10000 / 10000;
			}

			if(random_roll < activebets_itr->roll_under){
				payout = (activebets_itr->bet_amt * get_payout_mult_times10000(activebets_itr->roll_under, edge)) / 10000;
			}

			increment_game_stats(activebets_itr->bet_amt, payout);
			decrement_liabilities(activebets_itr->bet_amt);

			if (payout > 0){
				action(
					permission_level{_self, N(active)},
					N(eosio.token),
					N(transfer),
					std::make_tuple(
						_self,
						activebets_itr->bettor,
						asset(payout, symbol_type(S(4, EOS))),
						std::string("Bet id: ") + std::to_string(bet_id) + std::string(" -- Winner! Play: dice.eosbet.io")
					)
				).send();
			}

			transaction ref_tx{};

			ref_tx.actions.emplace_back(
				permission_level{_self, N(active)},
				_self,
				N(betreceipt),
				std::make_tuple(
					bet_id,
					activebets_itr->bettor,
					N(eosio.token),
					asset(activebets_itr->bet_amt, symbol_type(S(4, EOS))),
					asset(payout, symbol_type(S(4, EOS))),
					activebets_itr->seed,
					sig,
					activebets_itr->roll_under,
					random_roll
				)
			);

			if (ref_reward > 0){

				ref_tx.actions.emplace_back(
					permission_level{_self, N(active)},
					N(eosio.token),
					N(transfer),
					std::make_tuple(
						_self,
						N(safetransfer),
						asset(ref_reward, symbol_type(S(4, EOS))),
						name_to_string(activebets_itr->referral) + std::string(" Bet id: ") + std::to_string(bet_id) + std::string(" -- Referral reward! Play: dice.eosbet.io")
					)
				);
			}

			ref_tx.delay_sec = 5;
			ref_tx.send(bet_id, _self);

			airdrop_tokens(bet_id, activebets_itr->bet_amt, activebets_itr->bettor);

			activebets.erase(activebets_itr);
		}

		// @abi action
		void betreceipt(
			uint64_t bet_id,
			account_name bettor,
			account_name amt_contract,
			asset bet_amt,
			asset payout,
			checksum256 seed,
			signature signature,
			uint64_t roll_under,
			uint64_t random_roll
 			) {

			require_auth(N(eosbetdice11));
 			require_recipient( bettor );
		}

		// @abi action
		void refundbet(const uint64_t bet_id) {

			require_auth2(N(eosbetcasino), N(random));

			auto activebets_itr = activebets.find( bet_id );

			eosio_assert(activebets_itr != activebets.end(), "Game doesn't exist");

			const time_point_sec bet_time = activebets_itr->bet_time;

			eosio_assert(time_point_sec(now() - TWO_MINUTES) > bet_time, "Wait 10 minutes");

			decrement_liabilities(activebets_itr->bet_amt);

			action(
				permission_level{_self, N(active)},
				N(eosio.token),
				N(transfer),
				std::make_tuple(
					_self,
					N(safetransfer),
					asset(activebets_itr->bet_amt, symbol_type(S(4, EOS))),
					name_to_string(activebets_itr->bettor) + std::string(" Bet id: ") + std::to_string(bet_id) + std::string(" -- REFUND. Sorry for the inconvenience.")
				)
			).send();

			activebets.erase(activebets_itr);
		}

	private:
		// @abi table activebets i64
		struct bet {
			uint64_t 		id;
			account_name	bettor;
			account_name	referral;
			uint64_t		bet_amt;
			uint64_t		roll_under;
			checksum256		seed;
			time_point_sec bet_time;

			uint64_t 		primary_key() const { return id; }

			EOSLIB_SERIALIZE( bet, (id)(bettor)(referral)(bet_amt)(roll_under)(seed)(bet_time))
		};

		typedef eosio::multi_index< N(activebets), bet> bets_index;

		// @abi table globalvars i64
		struct globalvar{
			uint64_t		id;
			uint64_t		val;

			uint64_t		primary_key() const { return id; }

			EOSLIB_SERIALIZE(globalvar, (id)(val));
		};

		typedef eosio::multi_index< N(globalvars), globalvar> globalvars_index;

		// @abi table randkeys i64
		struct randkey {
			uint64_t 		id;
			public_key		key;

			uint64_t		primary_key() const { return id; }
		};

		typedef eosio::multi_index< N(randkeys), randkey > randkeys_index;

		// taken from eosio.token.hpp
		struct account {
	    	asset    balance;

	    	uint64_t primary_key() const { return balance.symbol.name(); }
	    };

		typedef eosio::multi_index<N(accounts), account> accounts;

		// taken from eosio.token.hpp
		struct st_transfer {
            account_name  from;
            account_name  to;
            asset         quantity;
            std::string   memo;
        };

        struct st_seeds{
         	checksum256 	seed1;
         	checksum256		seed2;
        };

		bets_index			activebets;
		globalvars_index	globalvars;
		randkeys_index		randkeys;

		std::string name_to_string(uint64_t acct_int) const {
	     static const char* charmap = ".12345abcdefghijklmnopqrstuvwxyz";

	      std::string str(13,'.');

	      uint64_t tmp = acct_int;

	      for( uint32_t i = 0; i <= 12; ++i ) {
	         char c = charmap[tmp & (i == 0 ? 0x0f : 0x1f)];
	         str[12-i] = c;
	         tmp >>= (i == 0 ? 4 : 5);
	      }

	      boost::algorithm::trim_right_if( str, []( char c ){ return c == '.'; } );
	      return str;
	   }

		void increment_liabilities_bet_id(const uint64_t bet_amt){

			auto globalvars_itr = globalvars.find(BETID_ID);

			globalvars.modify(globalvars_itr, _self, [&](auto& g){
				g.val++;
			});

			globalvars_itr = globalvars.find(LIABILITIES_ID);

			globalvars.modify(globalvars_itr, _self, [&](auto& g){
				g.val += bet_amt;
			});
		}

		void increment_game_stats(const uint64_t bet_amt, const uint64_t won_amt){

			auto globalvars_itr = globalvars.find(TOTALAMTBET_ID);

			globalvars.modify(globalvars_itr, _self, [&](auto& g){
				g.val += bet_amt;
			});

			if (won_amt > 0){
				globalvars_itr = globalvars.find(TOTALAMTWON_ID);

				globalvars.modify(globalvars_itr, _self, [&](auto& g){
					g.val += won_amt;
				});
			}
		}

		void decrement_liabilities(const uint64_t bet_amt){
			auto globalvars_itr = globalvars.find(LIABILITIES_ID);

			globalvars.modify(globalvars_itr, _self, [&](auto& g){
				g.val -= bet_amt;
			});
		}

		void airdrop_tokens(const uint64_t bet_id, const uint64_t bet_amt, const account_name bettor){
			uint64_t drop_amt = (1 * bet_amt) / 30;

			const uint64_t bet_token_balance = get_token_balance( N(betdividends), symbol_type(S(4, BET)) );

			if (bet_token_balance == 0){
				return;
			}
			else if (bet_token_balance < drop_amt){
				drop_amt = bet_token_balance;
			}
			action(
               permission_level{_self, N(active)},
               N(betdividends),
               N(transfer),
               std::make_tuple(
                   _self,
                   bettor,
                   asset(drop_amt, symbol_type(S(4, BET))),
                   std::string("Bet id: ") + std::to_string(bet_id) + std::string(" -- Enjoy airdrop! Play: dice.eosbet.io")
	            )
	       ).send();
		}

		uint64_t get_token_balance(const account_name token_contract, const symbol_type& token_type) const {

			accounts from_accounts(token_contract, _self);

			const auto token_name = token_type.name();
			auto my_account_itr = from_accounts.find(token_name);
			if (my_account_itr == from_accounts.end()){
				return 0;
			}
			const asset my_balance = my_account_itr->balance;
			return (uint64_t)my_balance.amount;
		}

		uint64_t get_payout_mult_times10000(const uint64_t roll_under, const uint64_t house_edge_times_10000) const {

			return ((10000 - house_edge_times_10000) * 100) / (roll_under - 1);
		}

		uint64_t get_max_win() const {
			const uint64_t eos_balance = get_token_balance(N(eosio.token), symbol_type(S(4, EOS)));

			auto liabilities_struct = globalvars.get(LIABILITIES_ID);
			const uint64_t liabilities = liabilities_struct.val;

			return (eos_balance - liabilities) / 25;
		}
};

#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( code == self || code == N(eosio.token)) { \
      	 if( action == N(transfer)){ \
      	 	eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
      	 } \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
}

EOSIO_ABI_EX( EOSBetDice,
	(initcontract)
	(newrandkey)
	(resolvebet)
	(refundbet)
	(transfer)
	(betreceipt)
	(suspendbet)
)


/*

    HUANG Jing
    haungjing@mae.cuhk.edu.hk
    Partially based on correll lab's script http://104.236.202.16/?p=3064.

*/

#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
#include <sensor_msgs/image_encodings.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/photo.hpp>    // denoising module
#include <opencv2/calib3d.hpp>  // solvePnP
#include <iostream>
#include <algorithm> // for std::max_element()
#include <vector>
#include <math.h>
#include <iterator>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
#include "dvrk_retraction/visual_feedback.h"
#include "dvrk_retraction/visual_info_srv.h"

// namespace enc = sensor_msgs::image_encodings;

static const char WINDOW[] = "New OpenCV Image";

bool compareCvPoint_x(cv::Point point1, cv::Point point2)
{
  return (point1.x > point2.x);
}

bool compareCvPoint_y(cv::Point point1, cv::Point point2)
{
  return (point1.y < point2.y);
}

class ImageConverter
{
    ros::NodeHandle nh_;
    ros::Publisher image_info_pub = nh_.advertise<dvrk_retraction::visual_feedback>("visual_info", 1000);
    ros::ServiceServer visual_info_service = nh_.advertiseService("vusual_info_srv", &ImageConverter::get_visual_info_srv, this);
    image_transport::ImageTransport it_;
    image_transport::Subscriber image_sub_;
    image_transport::Publisher image_pub_;
    float focal_x = 683;
    float focal_y = 683;
    float depth = 58;
public:
    std::vector<float> class_centroid;
    std::vector<float> class_rotate_origin;
    std::vector<float> class_principal_axis;
    float class_rotate_angle;
    std::vector<float> class_target_region_center;
    float class_target_region_radius;

public:
    ImageConverter(char* ros_image_stream): it_(nh_)
    {
        image_pub_ = it_.advertise("image_processed", 1);
        image_sub_ = it_.subscribe(ros_image_stream, 1, &ImageConverter::imageCb, this);
        cv::namedWindow(WINDOW);
    }

    ~ImageConverter()
    {
        cv::destroyWindow(WINDOW);
    }

    void imageCb(const sensor_msgs::ImageConstPtr& msg)
    {
      cv_bridge::CvImagePtr cv_ptr;
      try
      {
        //std::cout << "I am trying" << std::endl;
        cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
      }
      catch (cv_bridge::Exception& e)
      {
        std::cout << "ROS image msg conversion failed." << std::endl;
        ROS_ERROR("cv_bridge exception: %s", e.what());
        return;
      }

    cv::Point imageOrigin(320, 240), xAxisEnd(220, 240), yAxisEnd(320, 340);
    cv::Scalar redLow(0, 173, 152), redHigh(10, 255, 255), blackLow(0, 0, 0), blackHigh(149, 218, 71);
    std::vector< std::vector<cv::Point>> contours;
    cv::Mat trackedImage, trackedImage_denoised, dst, trackedImage_undist;
    cv::Mat cameraMatrix = (cv::Mat_<double>(3, 3) << 683.9731, 0, 320., 0, 683.9731, 240, 0, 0, 1);
    cv::Mat distCoef = (cv::Mat_<double>(1, 5) << -0.7175, 2.8528, 0, 0, -5.1548);
    dvrk_retraction::visual_feedback image_info_msg;

    cv::cvtColor(cv_ptr->image, trackedImage, CV_BGR2HSV);
    // cv::fastNlMeansDenoisingColored(trackedImage,trackedImage_denoised, 3, 3, 7, 21); // This function costs much time.
    cv::blur(trackedImage, trackedImage, cv::Size(3, 3));   // blurring is very important to get rid of noise.
    // cv::inRange(trackedImage, redLow, redHigh, dst);
    // cv::imshow("Binary Image", dst);

    //*** Use bgr difference to generate binary image rather than inRange()-START ***//
    cv::Mat bgr_thr = cv::Mat::zeros(trackedImage.size(), CV_8UC1);
    int Rows = bgr_thr.rows, Cols = bgr_thr.cols;
    for (int r = 0; r < Rows; r ++)
        for (int c = 0; c < Cols; c++)
        {
            cv::Vec3b intensity = cv_ptr->image.at<cv::Vec3b>(cv::Point(c, r));
            if (intensity[2] - intensity[0] > 80 && intensity[2] - intensity[1] > 80)
            {
                bgr_thr.at<uchar>(cv::Point(c, r)) = 255;
            }
        }
    dst = bgr_thr;
    //*** Use bgr difference to generate binary image rather than inRange()-END ***//

    cv::Moments m_dst = moments(dst, true);
    cv::Point p(m_dst.m10 / m_dst.m00, m_dst.m01 / m_dst.m00);
    if (p.x < 0 || p.y < 0)
    {
      std::cout << "No feature shape detected." << std::endl;
    }
    else
    {
      cv::findContours(dst, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));
      int largestContourIndex = 0;
      for (int i = 0; i < contours.size(); i++)
        largestContourIndex = (contours[i].size() > contours[largestContourIndex].size()) ? i : largestContourIndex;

      cv::Moments shapeMoments = moments(contours[largestContourIndex]);
      cv::Point2f shapeCenter(static_cast<float>(shapeMoments.m10 / shapeMoments.m00),
                              static_cast<float>(shapeMoments.m01 / shapeMoments.m00));
      Eigen::Matrix2f I;        // inertia tensor
      I <<  shapeMoments.mu20, shapeMoments.mu11,
            shapeMoments.mu11, shapeMoments.mu02;
      Eigen::EigenSolver<Eigen::Matrix2f> es(I);
      Eigen::Vector2d eigenVector1(es.eigenvectors().col(0).real()[0], es.eigenvectors().col(0).real()[1]),
                      eigenVector2(es.eigenvectors().col(1).real()[0], es.eigenvectors().col(1).real()[1]);

      // ***The procedure is to get rid of the possible random switch of the principal axis display.
      // ***There is a need of look at the characters of the retrun eigenvectors first to know how to
      // ***better set the processing.
      if (eigenVector1[0] < 0 )
      {
        Eigen::Vector2d tempVector = eigenVector1;
        eigenVector1 = eigenVector2;
      }
      eigenVector2 = Eigen::Vector2d(eigenVector1[1], -eigenVector1[0]);

      cv::RotatedRect rectShape = cv::minAreaRect(contours[largestContourIndex]);
      cv::Point2f rectShapeVertices[4];
      rectShape.points(rectShapeVertices);    // order of vertices: bottomLeft, topLeft, topRight, bottomRight. And the bottom
                                              // is the most bottom.

      std::vector<float> rectLine0_1{rectShapeVertices[0].x - rectShapeVertices[1].x,
                                     rectShapeVertices[0].y - rectShapeVertices[1].y };
      std::vector<float> rectLine1_2{rectShapeVertices[2].x - rectShapeVertices[1].x,
                                     rectShapeVertices[2].y - rectShapeVertices[1].y };
      float rectLineLengthSquare0_1 = pow(rectLine0_1[0], 2) + pow(rectLine0_1[1], 2),
            rectLineLengthSquare1_2 = pow(rectLine1_2[0], 2) + pow(rectLine1_2[1], 2);
      cv::Point2f rotateOrigin;
      //if (rectShapeVertices[2].x > rectShapeVertices[0].x)
      if (rectLineLengthSquare0_1 < rectLineLengthSquare1_2)
      {
        rotateOrigin.x = (rectShapeVertices[0].x + rectShapeVertices[1].x) / 2;
        rotateOrigin.y = (rectShapeVertices[0].y + rectShapeVertices[1].y) / 2;
      }
      else
      {
        rotateOrigin.x = (rectShapeVertices[1].x + rectShapeVertices[2].x) / 2;
        rotateOrigin.y = (rectShapeVertices[1].y + rectShapeVertices[2].y) / 2;
      }

      //*** Drawing part1-START ***//
      for (int i = 0; i < 4; i++)  // draw the rectangle
      {
        cv::line(cv_ptr->image, rectShapeVertices[i], rectShapeVertices[(i+1) % 4], cv::Scalar(0, 255, 0), 2);
      }
      // cv::circle(cv_ptr->image, rotateOrigin, 8, cv::Scalar(155, 245, 66), -1);
      cv::Point2f principleAxie1 = shapeCenter + 80 * cv::Point2f( eigenVector1[0], eigenVector1[1]),
                  principleAxie2 = shapeCenter + 80 * cv::Point2f( eigenVector2[0], eigenVector2[1]);
      // cv::arrowedLine(cv_ptr->image, shapeCenter, principleAxie1, cv::Scalar(0, 0, 255), 2);
      // cv::arrowedLine(cv_ptr->image, shapeCenter, principleAxie2, cv::Scalar(0, 255, 0), 2);
      cv::circle(cv_ptr->image, shapeCenter, 5, cv::Scalar(0, 0, 0), -1);
      cv::drawContours(cv_ptr->image, contours, largestContourIndex, cv::Scalar(255, 0, 0), 2);
      //*** Drawing part1-END ***//

      //*** get the posture of the attached plane ***//
      std::cout << "This is test for finding extreme points: \n"
                << (*std::max_element(contours[largestContourIndex].begin(), contours[largestContourIndex].end(),
                    compareCvPoint_x)).x
                << std::endl;
      std::vector<cv::Point>::iterator leftestVertexItr = std::max_element(contours[largestContourIndex].begin(),
                                                                            contours[largestContourIndex].end(),
                                                                            compareCvPoint_x);
      cv::Point2f leftestVertex = *leftestVertexItr,
                  vertexRef1 = *(leftestVertexItr + 20),
                  vertexRef2 = *(leftestVertexItr - 20),  // Index of Point in contours increases anticlosewise.
                  axisXref = *(leftestVertexItr + 35) - *(leftestVertexItr + 15),
                  axisYref = *(leftestVertexItr - 35) - *(leftestVertexItr - 15),
                  axisZref;
      axisXref = axisXref / (sqrt(pow(axisXref.x, 2) + pow(axisXref.y, 2))), // normalize
      axisYref = axisYref / (sqrt(pow(axisYref.x, 2) + pow(axisYref.y, 2)));
      cv::Point2f objectPoint1 = shapeCenter + 50 * axisXref,
                  objectPoint2 = shapeCenter + 50 * axisYref,
                  objectPoint3;
      /* cv::Mat objectPointsPnP = (cv::Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, 0, 0, 1),
             // imagePointsPnP = (cv::Mat_<double>(2, 3) << objectPoint1.x, objectPoint2.x, (objectPoint1.x + objectPoint2.x)-20,
             //                                             objectPoint1.y, objectPoint2.y, (objectPoint1.y + objectPoint2.y)/2),
              rvecPnP, tvecPnP;
      std::vector<cv::Point2f> imagePointsPnP{objectPoint1, objectPoint2, (objectPoint1 + objectPoint2)/2};
      // cv::solvePnPRansac(objectPointsPnP, imagePointsPnP, cameraMatrix, distCoef, rvecPnP, tvecPnP);
      std::cout << rvecPnP << "\nPnP\n" << tvecPnP << std::endl; */
      Eigen::Matrix3d cameraMatrixEigen;
      for(int i = 0; i < 3; ++i)
        for(int j= 0; j < 3; ++j)
          cameraMatrixEigen(i, j) = cameraMatrix.at<double>(i, j);

      double  f_u = cameraMatrix.at<double>(0, 0), f_v = cameraMatrix.at<double>(1, 1),
              s_u = cameraMatrix.at<double>(0, 2), s_v = cameraMatrix.at<double>(1, 2),
              z_1 = 0.8, x_1 = (objectPoint1.x - s_u) * z_1 / f_u,
                        y_1 = (objectPoint1.y - s_v) * z_1 / f_v,
              z_2 = 0.82, x_2 = (objectPoint2.x - s_u) * z_2 / f_u,
                          y_2 = (objectPoint2.y - s_v) * z_2 / f_v,
              x_3, y_3, z_3;
      Eigen::Vector3d planeXCamera(x_1, y_1, z_1), planeYCamera(x_2, y_2, z_2),
                      planeZCamera = planeXCamera.cross(planeYCamera),
                      imageZPoint;
      planeZCamera = planeZCamera / planeZCamera(2);
      imageZPoint = cameraMatrixEigen * planeZCamera;
      objectPoint3.x = imageZPoint(0);
      objectPoint3.y = imageZPoint(1);
      axisZref = objectPoint3 / sqrt(pow(objectPoint3.x, 2) + pow(objectPoint3.y, 2));

      cv::circle(cv_ptr->image, leftestVertex, 5, cv::Scalar(255, 255, 255), -1);
      cv::circle(cv_ptr->image, vertexRef1, 5, cv::Scalar(0, 0, 255), -1);
      cv::circle(cv_ptr->image, vertexRef2, 5, cv::Scalar(255, 0, 0), -1);
      cv::arrowedLine(cv_ptr->image, shapeCenter, shapeCenter + 50 * axisXref, cv::Scalar(100, 120, 150), 3);
      cv::arrowedLine(cv_ptr->image, shapeCenter, shapeCenter + 50 * axisYref, cv::Scalar(50, 60, 70), 3);
      cv::arrowedLine(cv_ptr->image, shapeCenter, shapeCenter + 50 * axisZref, cv::Scalar(150, 100, 70), 3);

      //*** visual_feedback ros message ***//
      double rotateAngle = acos(eigenVector1[0]) / M_PI * 180;   // eigenvector is given with norm = 1.
      double targetAngle = 45;
      cv::Point2f rotateRadius = shapeCenter - rotateOrigin;
      Eigen::Vector2d rotateRadiusVec(rotateRadius.x, rotateRadius.y);
      cv::Point2f targetRegionCenterVec(rotateRadiusVec.norm() * cos(targetAngle / 180 * M_PI),
                                        -rotateRadiusVec.norm() * sin(targetAngle / 180 * M_PI));
      cv::Point2f targetRegionCenter = rotateOrigin + targetRegionCenterVec;
      const double targetRegionRadius = 15;
      // cv::circle(cv_ptr->image, targetRegionCenter, targetRegionRadius, cv::Scalar(155, 245, 66), 2);
      image_info_msg.centroid.push_back(shapeCenter.x);
      image_info_msg.centroid.push_back(shapeCenter.y);
      image_info_msg.rotate_origin.push_back(rotateOrigin.x);
      image_info_msg.rotate_origin.push_back(rotateOrigin.y);
      image_info_msg.principal_axis.push_back(eigenVector1[0]);
      image_info_msg.principal_axis.push_back(eigenVector1[1]);
      image_info_msg.rotate_angle = rotateAngle;
      image_info_msg.target_angle = targetAngle;
      image_info_msg.target_region_center.push_back(targetRegionCenter.x);
      image_info_msg.target_region_center.push_back(targetRegionCenter.y);
      image_info_msg.target_region_radius = targetRegionRadius;

      std::cout << "visual info: \nrotate angle: " << rotateAngle << std::endl;

      //***** visual_info_srv response assignment*****
      class_centroid.clear();
      class_rotate_origin.clear();
      class_principal_axis.clear();
      class_target_region_center.clear();

      class_centroid.push_back(shapeCenter.x);
      class_centroid.push_back(shapeCenter.y);
      class_rotate_origin.push_back(rotateOrigin.x);
      class_rotate_origin.push_back(rotateOrigin.y);
      class_principal_axis.push_back(eigenVector1[0]);
      class_principal_axis.push_back(eigenVector1[1]);
      class_rotate_angle = rotateAngle;
      class_target_region_center.push_back(targetRegionCenter.x);
      class_target_region_center.push_back(targetRegionCenter.y);
      class_target_region_radius = targetRegionRadius;

    }

    // cv::circle(cv_ptr->image, imageOrigin, 5, cv::Scalar(0, 0, 0), -1);
    // cv::arrowedLine(cv_ptr->image, imageOrigin, xAxisEnd, cv::Scalar(0, 0, 255), 2);
    // cv::putText(cv_ptr->image, "x", xAxisEnd - cv::Point(10, 10), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 0, 255), 2);
    // cv::arrowedLine(cv_ptr->image, imageOrigin, yAxisEnd, cv::Scalar(0, 255, 0), 2);
    // cv::putText(cv_ptr->image, "y", yAxisEnd + cv::Point(10, 10), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 255, 0), 2);

   // end of processing
    cv::imshow(WINDOW, cv_ptr->image);
    cv::waitKey(3);

    // image_pub_.publish(cv_ptr->toImageMsg());
    image_info_pub.publish(image_info_msg);
    }

  bool get_visual_info_srv(dvrk_retraction::visual_info_srv::Request &req,
                          dvrk_retraction::visual_info_srv::Response &res)
  {
    res.centroid.push_back(class_centroid[0]);
    res.centroid.push_back(class_centroid[1]);
    res.rotate_origin.push_back(class_rotate_origin[0]);
    res.rotate_origin.push_back(class_rotate_origin[1]);
    res.principal_axis.push_back(class_principal_axis[0]);
    res.principal_axis.push_back(class_principal_axis[1]);
    res.rotate_angle = class_rotate_angle;
    res.target_region_center.push_back(class_target_region_center[0]);
    res.target_region_center.push_back(class_target_region_center[1]);
    res.target_region_radius = class_target_region_radius;
    ROS_INFO("Successfully obtain visual information.\n");
    return true;
  }
};

int main(int argc, char** argv)
{
  if (argc == 2)
  {
    ros::init(argc, argv, "image_processing");
    ImageConverter ic(argv[1]);
    ros::spin();
    return 0;
  }
  else if (argc == 1)
  {
  // set default ros image source
    char *image_topic = (char*) "/cameras/source_camera/image";
    ros::init(argc, argv, "image_processing");
    ImageConverter ic(image_topic);
    ros::spin();
    return 1;
  }
  else
  {
    std::cout << "ERROR:\tusage - RosToOpencvImage <ros_image_topic>" << std::endl;
    return 1;
  }
}


#include <iostream>
#include <ros/ros.h>
#include <nav_msgs/Odometry.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/Pose.h>
#include <geometry_msgs/PoseArray.h>
#include <geometry_msgs/Vector3.h>
#include <nav_msgs/Path.h>
#include "sample_waypoints.h"
#include <vector>
#include <deque>
#include <boost/format.hpp>
#include <eigen3/Eigen/Dense>

using namespace std;
using bfmt = boost::format;

ros::Publisher pub1;
ros::Publisher pub2; //waypoints_vis
ros::Publisher pub3;
string waypoint_type = string("manual");
bool is_odom_ready;
nav_msgs::Odometry odom;
nav_msgs::Path waypoints;

// series waypoint needed
std::deque<nav_msgs::Path> waypointSegments;
ros::Time trigged_time;

void load_seg(ros::NodeHandle& nh, int segid, const ros::Time& time_base) {
    std::string seg_str = boost::str(bfmt("seg%d/") % segid);
    double yaw;
    double time_from_start;
    ROS_INFO("Getting segment %d", segid);
    ROS_ASSERT(nh.getParam(seg_str + "yaw", yaw));
    ROS_ASSERT_MSG((yaw > -3.1499999) && (yaw < 3.14999999), "yaw=%.3f", yaw);
    ROS_ASSERT(nh.getParam(seg_str + "time_from_start", time_from_start));
    ROS_ASSERT(time_from_start >= 0.0);

    std::vector<double> ptx;
    std::vector<double> pty;
    std::vector<double> ptz;

    ROS_ASSERT(nh.getParam(seg_str + "x", ptx));
    ROS_ASSERT(nh.getParam(seg_str + "y", pty));
    ROS_ASSERT(nh.getParam(seg_str + "z", ptz));

    ROS_ASSERT(ptx.size());
    ROS_ASSERT(ptx.size() == pty.size() && ptx.size() == ptz.size());

    nav_msgs::Path path_msg;

    path_msg.header.stamp = time_base + ros::Duration(time_from_start);

    double baseyaw = tf::getYaw(odom.pose.pose.orientation);

    for (size_t k = 0; k < ptx.size(); ++k) {
        geometry_msgs::PoseStamped pt;
        pt.pose.orientation = tf::createQuaternionMsgFromYaw(baseyaw + yaw);
        Eigen::Vector2d dp(ptx.at(k), pty.at(k));
        Eigen::Vector2d rdp;
        rdp.x() = std::cos(-baseyaw-yaw)*dp.x() + std::sin(-baseyaw-yaw)*dp.y();
        rdp.y() =-std::sin(-baseyaw-yaw)*dp.x() + std::cos(-baseyaw-yaw)*dp.y();
        pt.pose.position.x = rdp.x() + odom.pose.pose.position.x;
        pt.pose.position.y = rdp.y() + odom.pose.pose.position.y;
        pt.pose.position.z = ptz.at(k) + odom.pose.pose.position.z;
        path_msg.poses.push_back(pt);
    }

    waypointSegments.push_back(path_msg);
}

void load_waypoints(ros::NodeHandle& nh, const ros::Time& time_base) {
    int seg_cnt = 0;
    waypointSegments.clear();
    ROS_ASSERT(nh.getParam("segment_cnt", seg_cnt));
    for (int i = 0; i < seg_cnt; ++i) {
        load_seg(nh, i, time_base);
        if (i > 0) {
            ROS_ASSERT(waypointSegments[i - 1].header.stamp < waypointSegments[i].header.stamp);
        }
    }
    ROS_INFO("Overall load %zu segments", waypointSegments.size());
}

void publish_waypoints() {
    waypoints.header.frame_id = std::string("world");
    waypoints.header.stamp = ros::Time::now();
    pub1.publish(waypoints);
    geometry_msgs::PoseStamped init_pose;
    init_pose.header = odom.header;
    init_pose.pose = odom.pose.pose;
    waypoints.poses.insert(waypoints.poses.begin(), init_pose);
    // pub2.publish(waypoints);
    waypoints.poses.clear();
}

void publish_waypoints_vis() {
    nav_msgs::Path wp_vis = waypoints;
    geometry_msgs::PoseArray poseArray;
    poseArray.header.frame_id = std::string("world");
    poseArray.header.stamp = ros::Time::now();

    {
        geometry_msgs::Pose init_pose;
        init_pose = odom.pose.pose;
        poseArray.poses.push_back(init_pose);
    }

    for (auto it = waypoints.poses.begin(); it != waypoints.poses.end(); ++it) {
        geometry_msgs::Pose p;
        p = it->pose;
        poseArray.poses.push_back(p);
    }
    pub2.publish(poseArray);
}

//
void odom_callback(const nav_msgs::Odometry::ConstPtr& msg) {
    is_odom_ready = true;
    odom = *msg;

    if (waypointSegments.size()) {
        ros::Time expected_time = waypointSegments.front().header.stamp;
        if (odom.header.stamp >= expected_time) {
            waypoints = waypointSegments.front();

            std::stringstream ss;
            ss << bfmt("Series send %.3f from start:\n") % trigged_time.toSec();
            for (auto& pose_stamped : waypoints.poses) {
                ss << bfmt("P[%.2f, %.2f, %.2f] q(%.2f,%.2f,%.2f,%.2f)") %
                          pose_stamped.pose.position.x % pose_stamped.pose.position.y %
                          pose_stamped.pose.position.z % pose_stamped.pose.orientation.w %
                          pose_stamped.pose.orientation.x % pose_stamped.pose.orientation.y %
                          pose_stamped.pose.orientation.z << std::endl;
            }
            ROS_INFO_STREAM(ss.str());

            publish_waypoints_vis();
            publish_waypoints();

            waypointSegments.pop_front();
        }
    }
}

void goal_callback(const geometry_msgs::PoseStamped::ConstPtr& msg) {
/*    if (!is_odom_ready) {
        ROS_ERROR("[waypoint_generator] No odom!");
        return;
    }*/

    trigged_time = ros::Time::now(); //odom.header.stamp;
    //ROS_ASSERT(trigged_time > ros::Time(0));

    ros::NodeHandle n("~");
    n.param("waypoint_type", waypoint_type, string("manual"));

    if (waypoint_type == string("circle")) {
        waypoints = circle();
        publish_waypoints_vis();
        publish_waypoints();
    } else if (waypoint_type == string("eight")) {
        waypoints = eight();
        publish_waypoints_vis();
        publish_waypoints();
    } else if (waypoint_type == string("point")) {
        waypoints = point();
        publish_waypoints_vis();
        publish_waypoints();
    } else if (waypoint_type == string("series")) {
        load_waypoints(n, trigged_time);
    } else if (waypoint_type == string("manual-lonely-waypoint")) {
        if (msg->pose.position.z > 0) {
            // if height > 0, it's a valid goal;
            geometry_msgs::PoseStamped pt = *msg;
            waypoints.poses.clear();
            waypoints.poses.push_back(pt);
            publish_waypoints_vis();
            publish_waypoints();
        } else {
            ROS_WARN("[waypoint_generator] invalid goal in manual-lonely-waypoint mode.");
        }
    } else {
        if (msg->pose.position.z > 0) {
            // if height > 0, it's a normal goal;
            geometry_msgs::PoseStamped pt = *msg;
            if (waypoint_type == string("noyaw")) {
                double yaw = tf::getYaw(odom.pose.pose.orientation);
                pt.pose.orientation = tf::createQuaternionMsgFromYaw(yaw);
            }
            waypoints.poses.push_back(pt);
            publish_waypoints_vis();
        } else if (msg->pose.position.z > -1.0) {
            // if 0 > height > -1.0, remove last goal;
            if (waypoints.poses.size() >= 1) {
                waypoints.poses.erase(std::prev(waypoints.poses.end()));
            }
            publish_waypoints_vis();
        } else {
            // if -1.0 > height, end of input
            if (waypoints.poses.size() >= 1) {
                publish_waypoints_vis();
                publish_waypoints();
            }
        }
    }
}

void traj_start_trigger_callback(const geometry_msgs::PoseStamped& msg) {
    if (!is_odom_ready) {
        ROS_ERROR("[waypoint_generator] No odom!");
        return;
    }

    ROS_WARN("[waypoint_generator] Trigger!");
    trigged_time = odom.header.stamp;
    ROS_ASSERT(trigged_time > ros::Time(0));

    ros::NodeHandle n("~");
    n.param("waypoint_type", waypoint_type, string("manual"));

    ROS_ERROR_STREAM("Pattern " << waypoint_type << " generated!");
    if (waypoint_type == string("free")) {
        waypoints = point();
        publish_waypoints_vis();
        publish_waypoints();
    } else if (waypoint_type == string("circle")) {
        waypoints = circle();
        publish_waypoints_vis();
        publish_waypoints();
    } else if (waypoint_type == string("eight")) {
        waypoints = eight();
        publish_waypoints_vis();
        publish_waypoints();
   } else if (waypoint_type == string("point")) {
        waypoints = point();
        publish_waypoints_vis();
        publish_waypoints();
    } else if (waypoint_type == string("series")) {
        load_waypoints(n, trigged_time);
    }
}

int main(int argc, char** argv) {
    ros::init(argc, argv, "waypoint_generator");
    ros::NodeHandle n("~");
    n.param("waypoint_type", waypoint_type, string("manual"));
    ros::Subscriber sub1 = n.subscribe("odom", 10, odom_callback);
    ros::Subscriber sub2 = n.subscribe("goal", 10, goal_callback);
    ros::Subscriber sub3 = n.subscribe("traj_start_trigger", 10, traj_start_trigger_callback);
    pub1 = n.advertise<nav_msgs::Path>("waypoints", 50);
    pub2 = n.advertise<geometry_msgs::PoseArray>("waypoints_vis", 10);

    trigged_time = ros::Time(0);

    ros::spin();
    return 0;
}


#ifndef GRID_H_
#define GRID_H_

//#include <Python.h>
#include <random>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <boost/functional/hash.hpp>

#include "config.h"
#include "Game.h"
#include "pole.h"
#include "drive.h"
#include "net/network.hpp"
#include "computation_graph.hpp"

using namespace net;

typedef std::unordered_map<int, std::pair<double,double>> partial_states; // conjunction of ranges
typedef std::vector<partial_states> unsafes;  // disjunction of conjunctions of ranges
typedef std::vector<std::pair<double,double>> whole_states;

template < typename SEQUENCE > struct seq_hash
{
    std::size_t operator() ( const SEQUENCE& seq ) const
    {
        std::size_t hash = 0 ;
        boost::hash_range( hash, seq.begin(), seq.end() ) ;
        return hash ;
    }
};

template < typename SEQUENCE, typename T >
using sequence_to_data_map = std::unordered_map< SEQUENCE, T, seq_hash<SEQUENCE> > ;

int binary_search(std::vector<double> v, double data) {
    auto it = std::upper_bound(v.begin(), v.end(), data);
    if (it == v.begin()) {
    	return 0;
    } else if (it == v.end()) {
    	return v.size()-2;
    } else {
    	std::size_t index = std::distance(v.begin(), it);
        return index-1;
    }
}

class Grid {// abstract a world into grids
private:
	void enumerateStateConfiguration (int i, std::vector<std::vector<int>>& stateconfigs) {
		std::cout << "enumerateStateConfiguration i = " << i << "\n";
		if (i == dimensions.size() - 1) {
			for (int k = 0; k < dimensions[i].size() - 1; k++) {
				std::vector<int> nv;
				nv.push_back(k);
				stateconfigs.push_back(nv);
			}
			return;
		}

		enumerateStateConfiguration (i+1, stateconfigs);

		int n = stateconfigs.size();
		for (int j = 0; j < n; j++) {
			stateconfigs[j].insert (stateconfigs[j].begin(), 0);
		}
		for (int k = 1; k < dimensions[i].size() - 1; k++) {
			for (int j = 0; j < n; j++) {
				std::vector<int> nv;
				nv.insert(nv.begin(), stateconfigs[j].begin(), stateconfigs[j].end());
				nv[0] = k;
				stateconfigs.push_back(nv);
			}
		}
	}

	bool empty_range (std::pair<double,double> arg1, std::pair<double,double> arg2) {
		std::pair<double,double> intersection = { std::max(arg1.first, arg2.first), std::min(arg1.second, arg2.second) };
		return (intersection.second <= intersection.first);
	}

	bool check_range (whole_states st, whole_states pst) {
		for (int i = 0; i < st.size(); i++) {
			if (empty_range (st[i], pst[i]))
				return false;
		}
		return true;
	}

	bool check_range (whole_states st, partial_states pst) {
		for (int i = 0; i < st.size(); i++) {
			if (pst.find(i) != pst.end() && empty_range (st[i], pst[i]))
				return false;
		}
		return true;
	}

	bool check_range (whole_states st, unsafes usfs) {
		for (int i = 0; i < usfs.size(); i++) {
			if (check_range (st, usfs[i]))
				return true;
		}
		return false;
	}

	std::vector<double> stepGame (std::vector<double> gamestate) {
		game->setGameState (gamestate);

		/*Vector vec;
        vec.resize (game->inputs());
        for (int i = 0; i < game->inputs(); i++) {
        	vec[i] = gamestate[i];
        }
        const auto& result = graph->forward(vec);
        // greedy algorithm that generates the next action.
        int row, col;
        result.maxCoeff(&row,&col);
        game->step (row);*/

		double x = gamestate[0];
		double gamma = gamestate[1];
		int row;
  		if (x <= -1.21205854416)
    		row = 0;
  		else
    		if (gamma <= 0.137111499906)
      			row = 2;
    		else
      			if (x <= 0.387143492699)
        			row = 0;
      			else
        			row = 1;
        game->step(row);
        return game->getGameState();
	}

public:
	// e.g. x  : -0.1, 0, 0.1 yields (-0.1, 0), (0, 0.1)
	// e.g. dx : -0.1, 0, 0.1 yields (-0.1, 0), (0, 0.1)
	// e.g. t  : -0.1, 0, 0.1 yields (-0.1, 0), (0, 0.1)
	std::vector<std::vector<double>> dimensions;

	std::unordered_map<int, std::vector<int>> index_to_coords;
	sequence_to_data_map<std::vector<int>, int> coords_to_index;

	std::vector<int> error_states;
	std::unordered_set<int> error_set;
	std::vector<int> init_states;

	// Number of states
	int S;
	// Number of actions
	int A;
	// Pointer to a game instance
	Game* game;
	// Pointer to a neurual network
	ComputationGraph* graph;

	Grid (Game& game, ComputationGraph& graph,
			std::vector<std::vector<double>>& dimensions, whole_states& init_zones, unsafes& error_zones) {
		(this->dimensions).insert((this->dimensions).end(), dimensions.begin(), dimensions.end());
		this->game = &game;
		this->graph = &graph;
		this->A = game.actions();

		std::vector<std::vector<int>> stateconfigs;
		std::cout << "Discretizing state space ...\n";
		enumerateStateConfiguration (0, stateconfigs);
		std::cout << "State space discretized\n";
		S = 0;
		for (int i = 0; i < stateconfigs.size(); i++) {
			std::cout << "(";
			for (int ind : stateconfigs[i])
				std::cout << ind << " ";
			std::cout << ") is mapped to " << i << "\n";
			index_to_coords.insert (std::make_pair (S, stateconfigs[i]));
			coords_to_index.insert (std::make_pair (stateconfigs[i], S));
			S ++;

			whole_states state;
			for (int j = 0; j < stateconfigs[i].size(); j++) {
				double start = dimensions[j][stateconfigs[i][j]];
				double end = dimensions[j][stateconfigs[i][j]+1];
				state.push_back(std::make_pair(start,end));
			}
			if (check_range(state, error_zones)) {
				error_states.push_back(i);
				error_set.insert(i);
			}
			if (check_range(state, init_zones)) {
				init_states.push_back(i);
			}
		}
	}

	// An observation is converted to the state of our world.
	// e.g. (x:-002, dx:0.05, t : 0.1)
	int observation_to_index (std::vector<double> observation) {
		//std::cout << "Transitioning to (";
		//for (int j = 0; j < observation.size(); j++) {
		//	std::cout << observation[j];
		//}
		//std::cout << ")\n";

		std::vector<int> coord;
		for (int i = 0; i < observation.size(); i++) {
			double v = observation[i];
			int ind = binary_search (dimensions[i], v);
			coord.push_back(ind);
		}

		//std::cout << "Transitioning to in coords (";
		//for (int j = 0; j < coord.size(); j++) {
		//	std::cout << coord[j];
		//}
		//std::cout << ")\n";

		int index = coords_to_index[coord];
		return index;
	}

	// A state of our world is mapped back to a range of possible observations.
	std::vector<int> index_to_observation_range (int state) {
		return index_to_coords[state];
	}

	// Build the tranision relations of states in our world.
	void build_transitions (int n_samples, std::unordered_map<int, std::unordered_map<int, double>>& transitionCounts) {
		std::cout << "Building probablisitic transitions about all states\n";
		for (int i = 0; i < S; i++) {
			if (error_set.find(i) != error_set.end()) // No tranisition exploration for unsafe states.
				continue;
			std::vector<int> ranges = index_to_observation_range(i);
			//std::cout << "Transitioning from (";
			//for (int j = 0; j < ranges.size(); j++) {
			//	std::cout << ranges[j];
			//}
			//std::cout << ")\n";
			int iteri = 0;
			unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    		std::mt19937 generator (seed);
    		std::uniform_real_distribution<double> uniform01(0.0, 1.0);
    		std::vector<std::vector<double>> points;
			while (iteri < n_samples) {
				// Sample a point within the range of intersted.
				std::vector<double> point;
				for (int j = 0; j < ranges.size(); j++) {
					double start = dimensions[j][ranges[j]];
					double end = dimensions[j][ranges[j]+1];

					double sv =  (end - start) * uniform01(generator) + start;
					point.push_back(sv);
				}
				//std::cout << "Samping a value in range (";
				//	for (int j = 0; j < point.size(); j++) {
				//	std::cout << point[j];
				//}
				//std::cout << ")\n";
				points.push_back(point);
				iteri++;
			}
			std::unordered_map<int, double> currTransitionCount;
			for (int j = 0; j < n_samples; j++) {
				int s = observation_to_index(stepGame(points[j]));
				//std::cout << "sampled a tranision from " << i << " to " << s << "\n";
				currTransitionCount[s]++;
			}
			for( auto& n : currTransitionCount ) {
        		n.second /= n_samples;
    		}
    		transitionCounts[i] = currTransitionCount;
		}
	}

	void outputTraisitionCounts (std::unordered_map<int, std::unordered_map<int, double>>& transitionCounts) {
		std::string datafile = "./data/optimal_policy";
		std::fstream evl(datafile, std::fstream::out);
		for (int i = 0; i < S; i++) {
			for (int j = 0; j < S; j++) {
				if (transitionCounts[i].find(j) != transitionCounts[i].end())
					evl << i << " " << j << " " << transitionCounts[i][j] << "\n";
				else
					evl << i << " " << j << " 0\n";
			}
		}
		evl.close();
	}

	void outputInitial () {
		std::string datafile = "./data/start";
		std::fstream evl(datafile, std::fstream::out);
		for (int i = 0; i < init_states.size(); i++)
			evl << init_states[i] << "\n";
		evl.close();
	}

	void outputUnsafe () {
		std::string datafile = "./data/unsafe";
		std::fstream evl(datafile, std::fstream::out);
		for (int i = 0; i < error_states.size(); i++)
			evl << error_states[i] << "\n";
		evl.close();
	}

	void createSingleInitial (std::unordered_map<int, std::unordered_map<int, double>>& transitionCounts) {
		std::unordered_map<int, double> currTransitionCount;
		for (int i = 0; i < init_states.size(); i++) {
			currTransitionCount[init_states[i]] = ((double)1.0 / (init_states.size()));
		}
		transitionCounts[S] = currTransitionCount;
		S++;
	}

	void createSingleFinal (std::unordered_map<int, std::unordered_map<int, double>>& transitionCounts) {
		for (int i = 0; i < error_states.size(); i++) {
			std::unordered_map<int, double> currTransitionCount;
			currTransitionCount[S] = 1.0;
			transitionCounts[error_states[i]] = currTransitionCount;
		}
		std::unordered_map<int, double> currTransitionCount;
		currTransitionCount[S] = 1.0;
		transitionCounts[S] = currTransitionCount;
		S++;
	}

	void outputStateSpace () {
		std::string datafile = "./data/state_space";
		std::fstream evl(datafile, std::fstream::out);
		evl << "states\n";
		evl << S << "\n";
		evl << "actions\n";
		evl << A;
		evl.close();
	}

	// Verify the safety of our world.
	double verify (int n_samples, int steps) {
		std::cout << "Verify generated probablisitic system\n";
		outputInitial ();
		outputUnsafe ();

		std::unordered_map<int, std::unordered_map<int, double>> transitionCounts;
		build_transitions (n_samples, transitionCounts);
		// Create a single intial state and final state
		createSingleInitial(transitionCounts);
		createSingleFinal(transitionCounts);
		// export policy from transitionCounts
		outputTraisitionCounts (transitionCounts);

		// Output the state space.
		outputStateSpace();

		double verification_result = 0.0;

		// call prism to construct a probablisitic tranision system.
		/*setenv("PYTHONPATH", ".", 1);
		Py_Initialize();

		PyObject *pName = PyString_FromString("prism");
    	PyObject *pModule = PyImport_Import(pName);
    	Py_DECREF(pName);

    	if (pModule != NULL) {
        	PyObject *pFunc = PyObject_GetAttrString(pModule, "model_check");
        	if (pFunc && PyCallable_Check(pFunc)) {
        		PyObject *pArgs = PyTuple_New(2);
        		PyObject *pValue1 = PyInt_FromLong(steps);
        		PyObject *pValue2 = PyInt_FromLong(S);
        		PyTuple_SetItem(pArgs, 0, pValue1);
        		PyTuple_SetItem(pArgs, 1, pValue2);

            	PyObject *pValue = PyObject_CallObject(pFunc, pArgs);
            	Py_DECREF(pArgs);
            	if (pValue != NULL) {
            		verification_result = PyFloat_AsDouble(pValue);
                	printf("Result of call: %lf\n", verification_result);
                	Py_DECREF(pValue);
            	}
            	else {
                	Py_DECREF(pFunc);
                	Py_DECREF(pModule);
                	PyErr_Print();
                	fprintf(stderr,"Call Prism model_check failed\n");
                	return 0;
            	}
        	} else {
            	if (PyErr_Occurred())
                	PyErr_Print();
            	fprintf(stderr, "Cannot find model_check function\n");
            	return 0;
        	}
        	Py_XDECREF(pFunc);
        	Py_DECREF(pModule);
        } else {
        	PyErr_Print();
        	fprintf(stderr, "Failed to load prism\n");
        	return 0;
        }
        Py_Finalize();*/

        return verification_result;
	}

};

int main_verify(int argc, char** argv)
{
	std::string model(argv[1]);
	Network agent;
	agent.load (model);
	ComputationGraph graph(agent);
	//Pole pole;
	Drive pole;
	std::vector<std::vector<double>> dimensions;
	//double xd[] = {-1.2, -1.0, 0, 1.0, 1.2};
	//double xd[] = {-1.2,-1.0,-0.5,-0.4,-0.2,-0.000001,0.000001,0.2,0.4,0.5,1.0,1.2};
	// -- _that_is_within_safe_angle_ -- double xd[] = {-1.2,-1.0,-0.5,-0.4,-0.3,-0.2,-0.1,-0.000001,0.000001,1.2};
	// -- nice-abstraction - double xd[] = {-1.2,-1.0,-0.9,-0.7,-0.5,-0.3,-0.1,-0.000001,0.000001,1.2};
	//double xd[] = {-1.2,-1.0,-0.9,-0.7,-0.5,-0.3,-0.1,-0.000001,0.000001,1.2};
	double xd[] = {-2.2,-2.0,-1.9,-1.8,-1.7,-1.6,-1.5,-1.4,-1.3,-1.21205854416,-1.1,-1.0,-0.9,-0.8,-0.7,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,-0.008,-0.006,-0.004,-0.002,0,0.002,0.004,0.006,0.008,0.1,0.2,0.387143492699,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.2};
  	std::vector<double> dx (xd, xd + sizeof(xd) / sizeof(double));
  	//double dxd[] = {-1.0, -0.2, 0, 0.2, 1.0};
  	//double dxd[] = {-1.0,-0.09,-0.003,-0.002,-0.000001,0.000001,0.002,0.003,0.09,1.0};
  	// -- _that_is_within_safe_angle_ -- double dxd[] = {-1.0,-0.09,-0.005,-0.003,-0.002,-0.000001,0.000001,1.0};
  	// -- nice-abstraction - double dxd[] = {-0.1,-0.09,-0.009,-0.005,-0.003,-0.002,-0.000001,0.000001,0.1};
  	//double dxd[] = {-0.2,-0.1,-0.009,-0.005,-0.003,-0.002,-0.000001,0.000001,0.2};
  	double dxd[] = {-1.3,-1.2,-1.1,-1,-0.9,-0.8,-0.78539815,-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,-0.008,-0.006,-0.004,-0.002,0,0.002,0.004,0.006,0.008,0.137111499906,0.2,0.3,0.4,0.5,0.6,0.78539815,0.8,0.9,1,1.1,1.2,1.3};
  	std::vector<double> ddx (dxd, dxd + sizeof(dxd) / sizeof(double));
  	//double thetad[] = {-1.0, -0.026179938765, 0, 0.026179938765, 1.0};
  	//double thetad[] = {-1.0,-0.026179938765,-0.005,-0.003,-0.002,-0.000001,0.000001,0.002,0.003,0.005,0.026179938765,1.0};
  	// -- _that_is_within_safe_angle_ -- double thetad[] = {-1.0,-0.026179938765,-0.01,-0.003,-0.002,-0.000001,0.000001,1.0};
  	// -- nice-abstraction - double thetad[] = {-0.1,-0.026179938765,-0.01,-0.003,-0.002,-0.000001,0.000001,0.1};
  	//*** double thetad[] = {-0.1,-0.026179938765,-0.01,-0.003,-0.002,-0.000001,0.000001,0.1};
  	//*** std::vector<double> dtheta (thetad, thetad + sizeof(thetad) / sizeof(double));
  	//double dthetad[] = {-1.0, -0.02, 0, 0.02, 1.0};
  	//double dthetad[] = {-1.0,-0.1,-0.05,-0.02,-0.01745329251,0.01745329251,0.02,0.05,0.1,1.0};
  	// -- _that_is_within_safe_angle_ -- double dthetad[] = {-1.0,-0.1,-0.05,-0.02,-0.01745329251,0.01745329251,1.0};
  	// -- nice-abstraction - double dthetad[] = {-0.1,-0.05,-0.02,-0.01745329251,0.01745329251,0.1};
  	// *** double dthetad[] = {-0.1,-0.05,-0.02,-0.01745329251,0.01745329251,0.1};
  	// *** std::vector<double> ddtheta (dthetad, dthetad + sizeof(dthetad) / sizeof(double));
	dimensions.push_back(dx);
	dimensions.push_back(ddx);
	//dimensions.push_back(dtheta);
	//dimensions.push_back(ddtheta);

	unsafes error_zones;
	//partial_states e1;
	//e1[0] = std::make_pair(-2.2, -2.0);
	//error_zones.push_back(e1);
	partial_states e2;
	e2[0] = std::make_pair(2.0, 2.2);
	error_zones.push_back(e2);
	//partial_states e1;
	//e1[2] = std::make_pair(-1.0, -0.026179938765);
	//error_zones.push_back(e1);
	//partial_states e2;
	//e2[2] = std::make_pair(0.026179938765, 1.0);
	//error_zones.push_back(e2);
	//partial_states e3;
	//e3[0] = std::make_pair(-1.2, -1.0);
	//error_zones.push_back(e3);
	//partial_states e4;
	//e4[0] = std::make_pair(1.0, 1.2);
	//error_zones.push_back(e4);

	whole_states init_zones;
	init_zones.push_back (std::make_pair(-1,1));
	init_zones.push_back (std::make_pair(-0.78539815,0.78539815));
	//init_zones.push_back (std::make_pair(-0.000001,0.000001));
	//init_zones.push_back (std::make_pair(-0.000001,0.000001));
	//init_zones.push_back (std::make_pair(-0.000001,0.000001));
	//init_zones.push_back (std::make_pair(-0.01745329251,0.01745329251));

	Grid g(pole, graph, dimensions, init_zones, error_zones);
	g.verify (1000, 200);
	return 0;
}

#endif /* GRID_H_ */


///////////////////////////////////////////////////////////////////////////////
// feature.hpp                                                               //
//                                                                           //
//  Copyright 2010 Erwann Rogard. 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_STATISTICS_DETAIL_NON_PARAMETRIC_CONTINGENCY_TABLE_PEARSON_CHISQ_INDEPENDENCE_TAG_HPP_ER_2010
#define BOOST_STATISTICS_DETAIL_NON_PARAMETRIC_CONTINGENCY_TABLE_PEARSON_CHISQ_INDEPENDENCE_TAG_HPP_ER_2010
#include <boost/mpl/always.hpp>
#include <boost/accumulators/framework/accumulator_base.hpp>
#include <boost/statistics/detail/non_parametric/contingency_table/pearson_chisq/independence/depends_on.hpp>

namespace boost {
namespace statistics{
namespace detail{
namespace contingency_table{
namespace pearson_chi_square_statistic{
namespace impl{

    template<typename Keys,typename T1>
    class independence_between : public boost::accumulators::accumulator_base
    {
        typedef boost::accumulators::dont_care dont_care_;

        public:

        typedef void result_type;

        independence_between(dont_care_){}

        void operator()(dont_care_)const{}

        result_type result(dont_care_)const{}

    };

}// impl
namespace tag
{
    template<typename Keys,typename T1 = double>
    struct independence_between
      : independence_between_aux::template depends_on<Keys>::type
    {
        typedef T1 value_type;

        typedef boost::mpl::always<
            pearson_chi_square_statistic::impl::independence_between<Keys,T1>
        > impl;
    };

}// tag
}// pearson_chi_square_statistic
}// contingency_table
}// detail
}// statistics
}// boost

#endif