// system includes ------------------------------------------------------------
#include <algorithm>
#include <boost/program_options.hpp>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
// own includes ---------------------------------------------------------------
#include "spectral/basis/spectral_basis_dimension_accessor.hpp"
#include "spectral/basis/spectral_basis_factory_ks.hpp"
#include "spectral/basis/spectral_elem_accessor.hpp"
#include "spectral/basis/toolbox/spectral_basis.hpp"

using namespace std;
using namespace boltzmann;
namespace po = boost::program_options;

const int dim = 2;

int main(int argc, char* argv[])
{
  int K;
  double beta;
  bool sorted;
  po::options_description options("options");
  options.add_options()
      ("help", "produce help message")
      ("K", po::value<int>(&K)->default_value(10));
  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, options), vm);
  po::notify(vm);

  if (vm.count("help")) {
    cout << options << "\n";
    return 1;
  }

  // ------------------------------------------------------------
  typedef SpectralBasisFactoryKS::basis_type basis_type;
  basis_type spectral_basis;
  typedef typename boost::mpl::at_c<typename basis_type::elem_t::types_t, 0>::type angular_t;
  typedef typename boost::mpl::at_c<typename basis_type::elem_t::types_t, 1>::type radial_t;
  typedef typename basis_type::DimAcc::template get_vec<radial_t> get_radial_basis_t;
  SpectralBasisFactoryKS::create(spectral_basis, K);
  // sort
  typedef typename basis_type::elem_t elem_t;
  typedef typename elem_t::Acc::template get<angular_t> get_xi;
  typedef typename elem_t::Acc::template get<radial_t> get_laguerre;

  /* for (auto elem : spectral_basis) { */
  /*   cout << elem.id().to_string() << "\n"; */
  /* } */

  get_radial_basis_t get_rad_basis;
  auto wtf = get_rad_basis(spectral_basis);

  cout << "---------- l=1 elements ----------"
       << "\n";
  auto range = spectral::filter_freq(spectral_basis.begin(), spectral_basis.end(), 1);
  for (auto it = std::get<0>(range); it != std::get<1>(range); ++it) {
    cout << it->id().to_string() << "\n";
  }

  // using boost iterator range
  for (auto&& elem : boost::make_iterator_range(std::get<0>(range), std::get<1>(range))) {
    cout << elem.id().to_string() << "\n";
  }

  return 0;
}


#include "obj.h"

#include <fstream>
#include <sstream>
#include <string>
#include <cassert>

#include <boost/filesystem.hpp>

#include "maths.h"
#include "mesh.h"

namespace {
	struct Face {
		int v = -1;
		int vn = -1;
		int vt = -1;
	};

	std::istream& operator >> (std::istream& in, Face& f) {
		f.v = -1;
		f.vn = -1;
		f.vt = -1;

		in >> f.v;
		if(in.peek() == '/') {
			in.get();

			if(in.peek() != '/')
				in >> f.vt;

			assert(in.peek() == '/');
			in.get();

			if(in.peek() >= '0' && in.peek() <= '9')
				in >> f.vn;

			assert(in.peek() != '/');
		}

		return in;
	}

	Mesh makeMesh(const std::vector<Vec3>& v, const std::vector<std::vector<Face>>& f) {
		std::size_t triangleCount = 0;
		for(auto& t : f)
			triangleCount += t.size()-2;

		Mesh result(v.size(), triangleCount);

		std::copy(v.begin(), v.end(), result.vertices().begin());

		std::size_t triangleIndex = 0;
		for(auto& t : f) {
			for(std::size_t ti=1;ti < t.size()-1; ++ti) {
				Triangle tr;
				tr.v0 = t[0].v-1;
				tr.v1 = t[ti].v-1;
				tr.v2 = t[ti+1].v-1;

				result.triangles()[triangleIndex] = tr;
				++triangleIndex;
			}
		}
		assert(triangleIndex == triangleCount);

		return result;
	}
}

Scene loadObj(boost::filesystem::path path) {
	assert(boost::filesystem::exists(path));

	Scene scene;

	std::vector<Vec3> vertices, normals;
	std::vector<std::vector<Face>> faces;

	std::ifstream file(path.string());

	while(!file.eof()) {
		std::string line;
		std::getline(file, line);

		if(!line.empty() && line[0] != '#') {
			std::stringstream linestr(line);

			std::string id;
			linestr >> id;

			if(id == "mtllib")
				;
			else if(id == "o") {
				if(!vertices.empty()) {
					scene.addMesh(makeMesh(vertices, faces));

					vertices.clear();
					normals.clear();
					faces.clear();
				}
			}
			else if(id == "v") {
				Vec3 v;
				linestr >> v.x >> v.y >> v.z;

				vertices.push_back(v);
			}
			else if(id == "vn") {
				Vec3 n;
				linestr >> n.x >> n.y >> n.z;

				normals.push_back(n);
			}
			else if(id == "f") {
				faces.push_back(std::vector<Face>());
				while(!linestr.eof()) {
					Face f;
					linestr >> f;

					if(f.v > 0)
						faces.back().push_back(f);
				}

				assert(faces.back().size() >= 3);
			}
		}
	}

	if(!vertices.empty())
		scene.addMesh(makeMesh(vertices, faces));

	return scene;
}


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2018 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program 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
// of the License, or (at your option) any later version.
//
// This program 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 this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_filtering.hh"
#include "graph.hh"
#include "graph_properties.hh"

#include <boost/graph/topological_sort.hpp>

using namespace std;
using namespace boost;
using namespace graph_tool;

struct get_topological_sort
{
    template <class Graph>
    void operator()(const Graph& g, vector<int32_t>& sort) const
    {
        sort.clear();
        topological_sort(g, std::back_inserter(sort));
    }
};

bool topological_sort(GraphInterface& gi, vector<int32_t>& sort)
{
    try
    {
        run_action<>()
            (gi, std::bind(get_topological_sort(), std::placeholders::_1,
                           std::ref(sort)))();
        return true;
    }
    catch (not_a_dag& e)
    {
        return false;
    }
}


//
// Created by lz on 3/19/17.
//

#ifndef STATIC_MAP_CONSTEXPR_STRING_VIEW_HPP
#define STATIC_MAP_CONSTEXPR_STRING_VIEW_HPP

#include <boost/utility/string_view.hpp>
#include <cstddef>
#include <string>
#include <type_traits>

namespace static_map
{
    template <typename CharT>
    struct constexpr_char_traits : public std::char_traits<CharT>
    {
        using typename std::char_traits<CharT>::char_type;
        using std::char_traits<CharT>::eq;
        // These 2 methods are not constexpr in C++14, however
        // they *can* be implemented with constexpr
        static constexpr std::size_t length(const CharT *s)
        {
            std::size_t len = 0;
            for(; !eq(*s, char_type(0)); ++s)
                ++len;
            return len;
        }

        static constexpr int compare(const CharT *s1, const CharT *s2, int cnt)
        {
            while(cnt--)
            {
                if(*s1 < *s2)
                    return -1;
                else if(*s1 > *s2)
                    return 1;
                ++s1;
                ++s2;
            }
            return 0;
        }
    };

    template <typename CharT>
    class constexpr_basic_string_view : public boost::basic_string_view<CharT, constexpr_char_traits<CharT>>
    {
        using BaseT = boost::basic_string_view<CharT, constexpr_char_traits<CharT>>;

      public:
        template <typename... Us>
        constexpr constexpr_basic_string_view(Us &&... args)
            : BaseT(std::forward<Us>(args)...)
        {
        }

        friend constexpr bool operator==(const constexpr_basic_string_view &a, const constexpr_basic_string_view &b)
        {
            return a.compare(b) == 0;
        }
    };

    using constexpr_string_view = constexpr_basic_string_view<char>;

    template <typename T>
    struct constexpr_hash;

    template <typename CharT>
    struct constexpr_hash<constexpr_basic_string_view<CharT>>
    {
        constexpr std::size_t operator()(const constexpr_basic_string_view<CharT> &str_view) const
        {
            size_t hash_val = 0;
            constexpr size_t seed = 31;  // odd, prime, 2^n-1
            for(std::size_t i = 0; i < str_view.size(); ++i)
                hash_val = hash_val * seed + str_view[i];
            return hash_val;
        }
    };
}
#endif  // STATIC_MAP_CONSTEXPR_STRING_VIEW_HPP


/********************************************************************************
 * Copyright 2009 The Robotics Group, The Maersk Mc-Kinney Moller Institute,
 * Faculty of Engineering, University of Southern Denmark
 *
 * 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.
 ********************************************************************************/

#ifndef RW_SENSOR_TACTILEARRAYMODEL_HPP
#define RW_SENSOR_TACTILEARRAYMODEL_HPP

#if !defined(SWIG)
#include "SensorModel.hpp"

#include <rw/math/Transform3D.hpp>
#include <rw/math/Vector2D.hpp>
#include <rw/math/Vector3D.hpp>

#include <boost/multi_array.hpp>
#endif
namespace rw { namespace sensor {

    /**
     * @brief the TactileArrayModel describes tactile sensor consisting of
     * arrays of tactile cells that can be placed on a defined shape. The shape is described
     * with a matrix of 3d vertices. Such that tactil (0,0) maps to the quad
     * defined by the four vertices {(0,0),(0,1),(1,1),(1,0)}. Notice that the
     * normal is defined by sequence of the vertices and that the normal defines
     * the direction of tactile sensing.
     */
    class TactileArrayModel : public SensorModel
    {
      public:
        //! smart pointer type
        typedef rw::core::Ptr< TactileArrayModel > Ptr;

        //! type of tactile array readings
        typedef Eigen::MatrixXf ValueMatrix;
        //! type of vertices describing geometry of sensor
        typedef boost::multi_array< rw::math::Vector3D<>, 2 > VertexMatrix;

        /**
         * @brief constructor
         * @param name [in] name of sensor
         * @param sensorframe [in] the frame to which the sensor is attached
         * @param fThmap [in] transformation from sensor frame to the heightmap definition
         * @param heightMap [in] a height map defining the height of each corner in the tactile
         * array
         * @param cell_width [in] width of cell
         * @param cell_height [in] height of cell
         */
        TactileArrayModel (const std::string& name, rw::kinematics::Frame* sensorframe,
                           const rw::math::Transform3D<>& fThmap, const ValueMatrix& heightMap,
                           double cell_width, double cell_height);

        /**
         * @brief destructor
         */
        virtual ~TactileArrayModel ();

        /**
         * @brief gets the size of an individual tactile cell with coordinates (x,y)
         * @param x
         * @param y
         * @return the dimensions of the tactile cell in meters
         */
        rw::math::Vector2D<> getTexelSize (int x, int y) const;

        /**
         * @brief get the minimum and maximum pressure capability of any tactile
         * cell in the TactileArray
         * @return min and max pressure in Pa
         */
        std::pair< double, double > getPressureLimit () const;

        /**
         * @brief set pressure limits. should define min max of any tactile cell in array
         * @param min [in] min pressure in Pa
         * @param max [in] max pressure in Pa
         */
        void setPressureLimit (double min, double max);
        void setPressureLimit (std::pair< double, double > range)
        {
            setPressureLimit (range.first, range.second);
        };

        /**
         * @brief gets the 3d geometry of this tactilearray. The vertexes are expressed
         * realtive to the transform.
         * @return
         */
        const VertexMatrix& getVertexGrid () const;

        /**
         * @brief a transformation from the sensor frame to the geometric data of
         * the tactile array.
         * @return
         */
        const rw::math::Transform3D<>& getTransform () const;

        /**
         * @brief a matrix with position of each tactile cell center. The coordinates
         * are described relative to the TactileArray transform (see getTransform())
         * @return a matrix describing the center of each tactile cell.
         */
        const VertexMatrix& getCenters () const;

        /**
         * @brief a matrix of normals that are described relative to each tactile
         * cell center.
         * @return
         */
        const VertexMatrix& getNormals () const;

        //! get width of tactile array
        int getWidth () const;

        //! get height of tactile array
        int getHeight () const;

        //************** the statefull interface (dynamic states) ***************
        /**
         * @brief returns the pressure on each texel of the TactileArray in
         * the unit Pa (N/m^2).
         * @param state [in] state to get the values from
         * @return matrix of texel pressure values
         */
        ValueMatrix& getTexelData (rw::kinematics::State& state) const;
        const ValueMatrix& getTexelData (const rw::kinematics::State& state) const;

        /**
         * @brief set the pressure on each texel of the TactileArray in
         * the unit Pa (N/m^2).
         * @param data [in] pressure values
         * @param state [in] state to set the values in
         */
        void setTexelData (const ValueMatrix& data, rw::kinematics::State& state) const;

      protected:
        //! cache to store state information
        class TactileModelCache : public rw::kinematics::StateCache
        {
          public:
            typedef rw::core::Ptr< TactileModelCache > Ptr;
            rw::core::Ptr< ValueMatrix > _data;

            //! constructor
            TactileModelCache (){};

            size_t size () const
            {
                size_t stmp = 0;
                if (_data != NULL)
                    stmp += _data->cols () * _data->rows () * sizeof (float);
                return stmp;
            };

            virtual rw::core::Ptr< StateCache > clone () const
            {
                TactileModelCache::Ptr cache = rw::core::ownedPtr (new TactileModelCache (*this));
                if (_data != NULL)
                    cache->_data = rw::core::ownedPtr (new ValueMatrix (*_data));
                return cache;
            };
        };

      private:
        rw::math::Transform3D<> _fThmap;
        ValueMatrix _heightMap;
        double _cellWidth, _cellHeight;
        double _minPressure, _maxPressure;

        rw::kinematics::StatelessData< int > _sdata;

        VertexMatrix _vertexGrid, _cellCenters, _cellNormals;
    };

}}    // namespace rw::sensor

#endif /*RW_SENSOR_TACTILEARRAY_HPP*/


// Copyright 2015-2018 Hans Dembinski
//
// 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)

#include <boost/core/ignore_unused.hpp>
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#include <boost/histogram/axis.hpp>
#include <boost/histogram/axis/ostream.hpp>
#include <boost/histogram/histogram.hpp>
#include <boost/histogram/literals.hpp>
#include <boost/histogram/make_histogram.hpp>
#include <boost/histogram/ostream.hpp>
#include <sstream>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <vector>
#include "is_close.hpp"
#include "std_ostream.hpp"
#include "throw_exception.hpp"
#include "utility_allocator.hpp"
#include "utility_axis.hpp"
#include "utility_histogram.hpp"

using namespace boost::histogram;
using namespace boost::histogram::literals; // to get _c suffix

template <class A, class S>
void pass_histogram(boost::histogram::histogram<A, S>& h) {
  BOOST_TEST_EQ(h.at(0), 0);
  BOOST_TEST_EQ(h.at(1), 1);
  BOOST_TEST_EQ(h.at(2), 0);
  BOOST_TEST_EQ(h.axis(0_c), axis::integer<>(0, 3));
}

template <class Tag>
void run_tests() {
  // init_1
  {
    auto h = make(Tag(), axis::regular<>{3, -1, 1});
    BOOST_TEST_EQ(h.rank(), 1);
    BOOST_TEST_EQ(h.size(), 5);
    BOOST_TEST_EQ(h.axis(0_c).size(), 3);
    BOOST_TEST_EQ(h.axis().size(), 3);
    auto h2 = make_s(Tag(), std::vector<unsigned>(), axis::regular<>{3, -1, 1});
    BOOST_TEST_EQ(h2, h);
  }

  // init_2
  {
    auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2},
                  axis::circular<>{2, 0, 360}, axis::variable<>{-1, 0, 1},
                  axis::category<>{{3, 1, 2}});
    BOOST_TEST_EQ(h.rank(), 5);
    BOOST_TEST_EQ(h.size(), 5 * 5 * 3 * 4 * 4);
    auto h2 = make_s(Tag(), std::vector<unsigned>(), axis::regular<>{3, -1, 1},
                     axis::integer<>{-1, 2}, axis::circular<>{2, 0, 360},
                     axis::variable<>{-1, 0, 1}, axis::category<>{{3, 1, 2}});
    BOOST_TEST_EQ(h2, h);
  }

  // copy_ctor
  {
    auto h = make(Tag(), axis::integer<>{0, 2}, axis::integer<>{0, 3});
    h(0, 0);
    auto h2 = decltype(h)(h);
    BOOST_TEST_EQ(h2, h);
    auto h3 =
        histogram<std::tuple<axis::integer<>, axis::integer<>>, dense_storage<double>>(h);
    BOOST_TEST_EQ(h3, h);
  }

  // copy_assign
  {
    auto h = make(Tag(), axis::integer<>(0, 1), axis::integer<>(0, 2));
    h(0, 0);
    auto h2 = decltype(h)();
    BOOST_TEST_NE(h, h2);
    h2 = h;
    BOOST_TEST_EQ(h, h2);
    auto h3 =
        histogram<std::tuple<axis::integer<>, axis::integer<>>, dense_storage<double>>();
    h3 = h;
    BOOST_TEST_EQ(h, h3);
  }

  // move
  {
    auto h = make(Tag(), axis::integer<>(0, 1), axis::integer<>(0, 2));
    h(0, 0);
    const auto href = h;
    decltype(h) h2(std::move(h));
    BOOST_TEST_EQ(algorithm::sum(h), 0);
    BOOST_TEST_EQ(h.size(), 0);
    BOOST_TEST_EQ(h2, href);
    decltype(h) h3;
    h3 = std::move(h2);
    BOOST_TEST_EQ(algorithm::sum(h2), 0);
    BOOST_TEST_EQ(h2.size(), 0);
    BOOST_TEST_EQ(h3, href);
  }

  // axis methods
  {
    auto a = make(Tag(), axis::integer<double>(1, 2, "foo"));
    BOOST_TEST_EQ(a.axis().size(), 1);
    BOOST_TEST_EQ(a.axis().bin(0).lower(), 1);
    BOOST_TEST_EQ(a.axis().bin(0).upper(), 2);
    BOOST_TEST_EQ(a.axis().metadata(), "foo");
    unsafe_access::axis(a, 0).metadata() = "bar";
    BOOST_TEST_EQ(a.axis().metadata(), "bar");

    auto b = make(Tag(), axis::integer<double>(1, 2, "foo"), axis::integer<>(1, 3));

    // check static access
    BOOST_TEST_EQ(b.axis(0_c).size(), 1);
    BOOST_TEST_EQ(b.axis(0_c).bin(0).lower(), 1);
    BOOST_TEST_EQ(b.axis(0_c).bin(0).upper(), 2);
    BOOST_TEST_EQ(b.axis(1_c).size(), 2);
    BOOST_TEST_EQ(b.axis(1_c).bin(0), 1);
    BOOST_TEST_EQ(b.axis(1_c).bin(1), 2);
    unsafe_access::axis(b, 1_c).metadata() = "bar";
    BOOST_TEST_EQ(b.axis(0_c).metadata(), "foo");
    BOOST_TEST_EQ(b.axis(1_c).metadata(), "bar");

    // check dynamic access
    BOOST_TEST_EQ(b.axis(0).size(), 1);
    BOOST_TEST_EQ(b.axis(0).bin(0).lower(), 1);
    BOOST_TEST_EQ(b.axis(0).bin(0).upper(), 2);
    BOOST_TEST_EQ(b.axis(1).size(), 2);
    BOOST_TEST_EQ(b.axis(1).bin(0), 1);
    BOOST_TEST_EQ(b.axis(1).bin(1), 2);
    BOOST_TEST_EQ(b.axis(0).metadata(), "foo");
    BOOST_TEST_EQ(b.axis(1).metadata(), "bar");
    unsafe_access::axis(b, 0).metadata() = "baz";
    BOOST_TEST_EQ(b.axis(0).metadata(), "baz");

    auto c = make(Tag(), axis::category<>({1, 2}));
    BOOST_TEST_EQ(c.axis().size(), 2);
    unsafe_access::axis(c, 0).metadata() = "foo";
    BOOST_TEST_EQ(c.axis().metadata(), "foo");
    // need to cast here for this to work with Tag == dynamic_tag, too
    const auto& ca = axis::get<axis::category<>>(c.axis());
    BOOST_TEST_EQ(ca.bin(0), 1);
    const auto& ca2 = axis::get<axis::category<>>(c.axis(0));
    BOOST_TEST_EQ(&ca2, &ca);
  }

  // equal_compare
  {
    auto a = make(Tag(), axis::integer<>(0, 2));
    auto b = make(Tag(), axis::integer<>(0, 2), axis::integer<>(0, 3));
    BOOST_TEST(a != b);
    BOOST_TEST(b != a);
    auto c = make(Tag(), axis::integer<>(0, 2));
    BOOST_TEST(b != c);
    BOOST_TEST(c != b);
    BOOST_TEST(a == c);
    BOOST_TEST(c == a);
    auto d = make(Tag(), axis::regular<>(2, 0, 1));
    BOOST_TEST(c != d);
    BOOST_TEST(d != c);
    c(0);
    BOOST_TEST(a != c);
    BOOST_TEST(c != a);
    a(0);
    BOOST_TEST(a == c);
    BOOST_TEST(c == a);
    a(0);
    BOOST_TEST(a != c);
    BOOST_TEST(c != a);
  }

  // 1D
  {
    auto h = make(Tag(), axis::integer<int, axis::null_type>{0, 2});
    h(0);
    auto i = h(0);
    BOOST_TEST(i == h.begin() + 1); // +1 because of underflow
    i = h(-1);
    BOOST_TEST(i == h.begin()); // underflow
    i = h(10);
    BOOST_TEST(i == h.end() - 1); // overflow

    BOOST_TEST_EQ(h.rank(), 1);
    BOOST_TEST_EQ(h.axis().size(), 2);
    BOOST_TEST_EQ(algorithm::sum(h), 4);

    BOOST_TEST_EQ(h.at(-1), 1);
    BOOST_TEST_EQ(h.at(0), 2);
    BOOST_TEST_EQ(h.at(1), 0);
    BOOST_TEST_EQ(h.at(2), 1);
  }

  // 1D no *flow
  {
    auto h = make(Tag(), axis::integer<int, axis::null_type, axis::option::none_t>(0, 2));
    h(0);
    auto i = h(-0);
    BOOST_TEST(i == h.begin());
    i = h(-1);
    BOOST_TEST(i == h.end());
    i = h(10);
    BOOST_TEST(i == h.end());

    BOOST_TEST_EQ(h.rank(), 1);
    BOOST_TEST_EQ(h.axis().size(), 2);
    BOOST_TEST_EQ(algorithm::sum(h), 2);

    BOOST_TEST_EQ(h.at(0), 2);
    BOOST_TEST_EQ(h.at(1), 0);
  }

  // 1D category axis
  {
    auto h = make(Tag(), axis::category<>({1, 2}));
    h(1);
    h(2);
    h(4);
    h(5);

    BOOST_TEST_EQ(h.rank(), 1);
    BOOST_TEST_EQ(h.axis().size(), 2);
    BOOST_TEST_EQ(algorithm::sum(h), 4);

    BOOST_TEST_EQ(h.at(0), 1);
    BOOST_TEST_EQ(h.at(1), 1);
    BOOST_TEST_EQ(h.at(2), 2); // overflow bin
  }

  // 1D weight
  {
    auto h = make_s(Tag(), weight_storage(), axis::integer<>(0, 2));
    h(-1);
    h(0);
    h(weight(0.5), 0);
    h(1);
    h(weight(2), 2);

    BOOST_TEST_EQ(algorithm::sum(h).value(), 5.5);
    BOOST_TEST_EQ(algorithm::sum(h).variance(), 7.25);

    BOOST_TEST_EQ(h[-1].value(), 1);
    BOOST_TEST_EQ(h[-1].variance(), 1);
    BOOST_TEST_EQ(h[0].value(), 1.5);
    BOOST_TEST_EQ(h[0].variance(), 1.25);
    BOOST_TEST_EQ(h[1].value(), 1);
    BOOST_TEST_EQ(h[1].variance(), 1);
    BOOST_TEST_EQ(h[2].value(), 2);
    BOOST_TEST_EQ(h[2].variance(), 4);
  }

  // 1D profile
  {
    auto h = make_s(Tag(), profile_storage(), axis::integer<>(0, 2));

    h(0, sample(1));
    h(0, sample(2));
    h(0, sample(3));
    h(sample(4), 1);
    h(sample(5), 1);
    h(sample(6), 1);

    BOOST_TEST_EQ(h[0].count(), 3);
    BOOST_TEST_EQ(h[0].value(), 2);
    BOOST_TEST_EQ(h[0].variance(), 1);
    BOOST_TEST_EQ(h[1].count(), 3);
    BOOST_TEST_EQ(h[1].value(), 5);
    BOOST_TEST_EQ(h[1].variance(), 1);
  }

  // 1D weighted profile
  {
    auto h = make_s(Tag(), weighted_profile_storage(), axis::integer<>(0, 2));

    h(0, sample(1));
    h(sample(1), 0);

    h(0, weight(2), sample(3));
    h(0, sample(5), weight(2));

    h(weight(2), 1, sample(1));
    h(sample(2), 1, weight(2));

    h(weight(2), sample(3), 1);
    h(sample(4), weight(2), 1);

    BOOST_TEST_EQ(h[0].sum_of_weights(), 6);
    BOOST_TEST_EQ(h[0].value(), 3);
    BOOST_TEST_EQ(h[1].sum_of_weights(), 8);
    BOOST_TEST_EQ(h[1].value(), 2.5);
  }

  // 2D
  {
    auto h = make(Tag(), axis::integer<>(-1, 1),
                  axis::integer<int, axis::null_type, axis::option::none_t>(-1, 2));
    h(-1, -1);
    h(-1, 0);
    h(-1, -10);
    h(-10, 0);

    BOOST_TEST_EQ(h.rank(), 2);
    BOOST_TEST_EQ(h.axis(0_c).size(), 2);
    BOOST_TEST_EQ(h.axis(1_c).size(), 3);
    BOOST_TEST_EQ(algorithm::sum(h), 3);

    BOOST_TEST_EQ(h.at(-1, 0), 0);
    BOOST_TEST_EQ(h.at(-1, 1), 1);
    BOOST_TEST_EQ(h.at(-1, 2), 0);

    BOOST_TEST_EQ(h.at(0, 0), 1);
    BOOST_TEST_EQ(h.at(0, 1), 1);
    BOOST_TEST_EQ(h.at(0, 2), 0);

    BOOST_TEST_EQ(h.at(1, 0), 0);
    BOOST_TEST_EQ(h.at(1, 1), 0);
    BOOST_TEST_EQ(h.at(1, 2), 0);

    BOOST_TEST_EQ(h.at(2, 0), 0);
    BOOST_TEST_EQ(h.at(2, 1), 0);
    BOOST_TEST_EQ(h.at(2, 2), 0);
  }

  // 2D weight
  {
    auto h = make_s(Tag(), weight_storage(), axis::integer<>(-1, 1),
                    axis::integer<int, axis::null_type, axis::option::none_t>(-1, 2));
    h(-1, 0);              // -> 0, 1
    h(weight(10), -1, -1); // -> 0, 0
    h(weight(5), -1, -10); // is ignored
    h(weight(7), -10, 0);  // -> -1, 1

    BOOST_TEST_EQ(algorithm::sum(h).value(), 18);
    BOOST_TEST_EQ(algorithm::sum(h).variance(), 150);

    BOOST_TEST_EQ(h.at(-1, 0).value(), 0);
    BOOST_TEST_EQ(h.at(-1, 1).value(), 7);
    BOOST_TEST_EQ(h.at(-1, 2).value(), 0);

    BOOST_TEST_EQ(h.at(0, 0).value(), 10);
    BOOST_TEST_EQ(h.at(0, 1).value(), 1);
    BOOST_TEST_EQ(h.at(0, 2).value(), 0);

    BOOST_TEST_EQ(h.at(1, 0).value(), 0);
    BOOST_TEST_EQ(h.at(1, 1).value(), 0);
    BOOST_TEST_EQ(h.at(1, 2).value(), 0);

    BOOST_TEST_EQ(h.at(2, 0).value(), 0);
    BOOST_TEST_EQ(h.at(2, 1).value(), 0);
    BOOST_TEST_EQ(h.at(2, 2).value(), 0);

    BOOST_TEST_EQ(h.at(-1, 0).variance(), 0);
    BOOST_TEST_EQ(h.at(-1, 1).variance(), 49);
    BOOST_TEST_EQ(h.at(-1, 2).variance(), 0);

    BOOST_TEST_EQ(h.at(0, 0).variance(), 100);
    BOOST_TEST_EQ(h.at(0, 1).variance(), 1);
    BOOST_TEST_EQ(h.at(0, 2).variance(), 0);

    BOOST_TEST_EQ(h.at(1, 0).variance(), 0);
    BOOST_TEST_EQ(h.at(1, 1).variance(), 0);
    BOOST_TEST_EQ(h.at(1, 2).variance(), 0);

    BOOST_TEST_EQ(h.at(2, 0).variance(), 0);
    BOOST_TEST_EQ(h.at(2, 1).variance(), 0);
    BOOST_TEST_EQ(h.at(2, 2).variance(), 0);
  }

  // 3D weight
  {
    auto h = make_s(Tag(), weight_storage(), axis::integer<>(0, 3), axis::integer<>(0, 4),
                    axis::integer<>(0, 5));
    for (auto i = 0; i < h.axis(0_c).size(); ++i)
      for (auto j = 0; j < h.axis(1_c).size(); ++j)
        for (auto k = 0; k < h.axis(2_c).size(); ++k) h(i, j, k, weight(i + j + k));

    for (auto i = 0; i < h.axis(0_c).size(); ++i) {
      for (auto j = 0; j < h.axis(1_c).size(); ++j) {
        for (auto k = 0; k < h.axis(2_c).size(); ++k) {
          BOOST_TEST_EQ(h.at(i, j, k).value(), i + j + k);
          BOOST_TEST_EQ(h.at(i, j, k).variance(), (i + j + k) * (i + j + k));
        }
      }
    }
  }

  // STL support
  {
    auto v = std::vector<int>{0, 1, 2};
    auto h = std::for_each(v.begin(), v.end(), make(Tag(), axis::integer<>(0, 3)));
    BOOST_TEST_EQ(h.at(0), 1);
    BOOST_TEST_EQ(h.at(1), 1);
    BOOST_TEST_EQ(h.at(2), 1);
    BOOST_TEST_EQ(algorithm::sum(h), 3);

    auto a = std::vector<double>();
    // walks over all bins, including underflow and overflow
    std::partial_sum(h.begin(), h.end(), std::back_inserter(a));
    BOOST_TEST_EQ(a.size(), 5);
    BOOST_TEST_EQ(a[0], 0);
    BOOST_TEST_EQ(a[1], 1);
    BOOST_TEST_EQ(a[2], 2);
    BOOST_TEST_EQ(a[3], 3);
    BOOST_TEST_EQ(a[4], 3);
  }

  // histogram reset
  {
    auto h = make(Tag(), axis::integer<int, axis::null_type, axis::option::none_t>(0, 2));
    h(0);
    h(1);
    BOOST_TEST_EQ(h.at(0), 1);
    BOOST_TEST_EQ(h.at(1), 1);
    BOOST_TEST_EQ(algorithm::sum(h), 2);
    h.reset();
    BOOST_TEST_EQ(h.at(0), 0);
    BOOST_TEST_EQ(h.at(1), 0);
    BOOST_TEST_EQ(algorithm::sum(h), 0);
  }

  // using containers for input and output
  {
    auto h = make(Tag(), axis::integer<>(0, 2), axis::integer<double>(2, 4));
    // tuple in
    h(std::make_tuple(0, 2.0));
    h(std::make_tuple(1, 3.0));

    auto i00 = std::make_tuple(0, 0);
    auto i11 = std::make_tuple(1, 1);

    // tuple out
    BOOST_TEST_EQ(h.at(i00), 1);
    BOOST_TEST_EQ(h[i00], 1);
    BOOST_TEST_EQ(h[i11], 1);

    // iterable out
    int j11[] = {1, 1};
    BOOST_TEST_EQ(h.at(j11), 1);
    BOOST_TEST_EQ(h[j11], 1);
    int j111[] = {1, 1, 1};
    boost::ignore_unused(j111);
    BOOST_TEST_THROWS((void)h.at(j111), std::invalid_argument);
    int j13[] = {1, 3};
    boost::ignore_unused(j13);
    BOOST_TEST_THROWS((void)h.at(j13), std::out_of_range);

    // tuple with weight
    h(std::make_tuple(weight(2), 0, 2.0));
    h(std::make_tuple(1, 3.0, weight(2)));

    BOOST_TEST_EQ(h.at(i00), 3);
    BOOST_TEST_EQ(h[i00], 3);

    // test special case of 1-dimensional histogram, which should unpack
    // 1-dimensional tuple normally, but forward larger tuples to the axis
    auto h1 = make(Tag(), axis::integer<>(0, 2));
    h1(std::make_tuple(0));                      // as if one had passed 0 directly
    BOOST_TEST_EQ(h1.at(std::make_tuple(0)), 1); // as if one had passed 0 directly
  }

  // bad bin access
  {
    auto h = make(Tag(), axis::integer<>(0, 1), axis::integer<>(0, 1));
    BOOST_TEST_THROWS(h.at(0, 2), std::out_of_range);
    BOOST_TEST_THROWS(h.at(std::make_tuple(2, 0)), std::out_of_range);
  }

  // pass histogram to function
  {
    auto h = make(Tag(), axis::integer<>(0, 3));
    h(1);
    pass_histogram(h);
  }

  // allocator support
  {
    tracing_allocator_db db;
    {
      tracing_allocator<char> a(db);
      auto h = make_s(Tag(), std::vector<int, tracing_allocator<int>>(a),
                      axis::integer<>(0, 1000));
      h(0);
    }

    // int allocation for std::vector
    BOOST_TEST_EQ(db.at<int>().first, 0);
    BOOST_TEST_EQ(db.at<int>().second, 1002);

    if (Tag()) { // axis::variant allocation, only for dynamic histogram
      using T = axis::variant<axis::integer<>>;
      BOOST_TEST_EQ(db.at<T>().first, 0);
      // may be zero if vector uses small-vector-optimisation
      BOOST_TEST_LE(db.at<T>().second, 1);
    }
  }
}

int main() {
  run_tests<static_tag>();
  run_tests<dynamic_tag>();

  return boost::report_errors();
}