// Boost.Units - A C++ library for zero-overhead dimensional analysis and
// unit/quantity manipulation and conversion
//
// Copyright (C) 2003-2008 Matthias Christian Schabel
// Copyright (C) 2008 Steven Watanabe
//
// 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_UNITS_DETAIL_PUSH_FRONT_OR_ADD_HPP
#define BOOST_UNITS_DETAIL_PUSH_FRONT_OR_ADD_HPP

#include <boost/mpl/plus.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/push_front.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/type_traits/is_same.hpp>

#include <boost/units/units_fwd.hpp>
#include <boost/units/detail/push_front_if.hpp>

namespace boost {

namespace units {

template<class Item, class Next>
struct list;

namespace detail {

template<class T>
struct is_empty_dim;

/// add an instantiation of dim to Sequence.
template<bool>
struct push_front_or_add_impl;

template<>
struct push_front_or_add_impl<true>
{
    template<typename Sequence, typename T>
    struct apply
    {
        typedef typename mpl::plus<T, typename Sequence::item>::type item;
        typedef typename push_front_if<!is_empty_dim<item>::value>::template apply<
            typename Sequence::next,
            item
        > type;
    };
};

template<>
struct push_front_or_add_impl<false>
{
    template<typename Sequence, typename T>
    struct apply
    {
        typedef list<T, Sequence> type;
    };
};

template<typename Sequence, typename T>
struct push_front_or_add
{
    typedef typename push_front_or_add_impl<boost::is_same<typename T::tag_type, typename Sequence::item::tag_type>::value>::template apply<
        Sequence,
        T
    >::type type;
};

template<typename T>
struct push_front_or_add<dimensionless_type, T>
{
    typedef list<T, dimensionless_type> type;
};

} // namespace detail

} // namespace units

} // namespace boost

#endif


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// 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 CRYPTO3_ACCUMULATORS_BLOCK_HPP
#define CRYPTO3_ACCUMULATORS_BLOCK_HPP

#include <boost/container/static_vector.hpp>

#include <boost/parameter/value_type.hpp>

#include <boost/accumulators/framework/accumulator_base.hpp>
#include <boost/accumulators/framework/extractor.hpp>
#include <boost/accumulators/framework/depends_on.hpp>
#include <boost/accumulators/framework/parameters/sample.hpp>

#include <boost/crypto3/detail/make_array.hpp>
#include <boost/crypto3/detail/digest.hpp>
#include <boost/crypto3/detail/inject.hpp>

#include <boost/crypto3/block/accumulators/bits_count.hpp>

#include <boost/crypto3/block/accumulators/parameters/cipher.hpp>
#include <boost/crypto3/block/accumulators/parameters/bits.hpp>
#include <boost/accumulators/framework/parameters/sample.hpp>
#include <boost/crypto3/block/detail/cipher_modes.hpp>

#include <boost/crypto3/block/cipher.hpp>

namespace boost {
    namespace crypto3 {
        namespace accumulators {
            namespace impl {
                template<typename Mode>
                struct block_impl : boost::accumulators::accumulator_base {
                protected:
                    typedef Mode mode_type;
                    typedef typename Mode::cipher_type cipher_type;
                    typedef typename Mode::padding_type padding_type;

                    typedef typename mode_type::endian_type endian_type;

                    constexpr static const std::size_t word_bits = mode_type::word_bits;
                    typedef typename mode_type::word_type word_type;

                    constexpr static const std::size_t block_bits = mode_type::block_bits;
                    constexpr static const std::size_t block_words = mode_type::block_words;
                    typedef typename mode_type::block_type block_type;

                    constexpr static const std::size_t value_bits = sizeof(typename block_type::value_type) * CHAR_BIT;
                    constexpr static const std::size_t block_values = block_bits / value_bits;

                    typedef ::boost::crypto3::detail::injector<endian_type, value_bits, block_values, block_bits>
                        injector_type;

                public:
                    typedef digest<block_bits> result_type;

                    template<typename Args>
                    block_impl(const Args &args) :
                        total_seen(0), filled(false), mode(args[boost::accumulators::sample]) {
                    }

                    template<typename ArgumentPack>
                    inline void operator()(const ArgumentPack &args) {
                        resolve_type(args[boost::accumulators::sample],
                                     args[::boost::crypto3::accumulators::bits | std::size_t()]);
                    }

                    inline result_type result(boost::accumulators::dont_care) const {
                        using namespace ::boost::crypto3::detail;

                        result_type res = dgst;

                        block_type processed_block = mode.end_message(cache, total_seen);

                        res = ::boost::crypto3::resize<block_bits>(res, res.size() + block_values);

                        pack<endian_type, endian_type, value_bits, octet_bits>(
                            processed_block.begin(), processed_block.end(), res.end() - block_values);

                        return res;
                    }

                protected:
                    inline void resolve_type(const block_type &value, std::size_t bits) {
                        process(value, bits == 0 ? block_bits : bits);
                    }

                    inline void resolve_type(const word_type &value, std::size_t bits) {
                        process(value, bits == 0 ? word_bits : bits);
                    }

                    inline void process_block() {
                        using namespace ::boost::crypto3::detail;

                        block_type processed_block;
                        if (dgst.empty()) {
                            processed_block = mode.begin_message(cache, total_seen);
                        } else {
                            processed_block = mode.process_block(cache, total_seen);
                        }

                        dgst = ::boost::crypto3::resize<block_bits>(dgst, dgst.size() + block_values);

                        pack<endian_type, endian_type, value_bits, octet_bits>(
                            processed_block.begin(), processed_block.end(), dgst.end() - block_values);

                        filled = false;
                    }

                    inline void process(const block_type &value, std::size_t value_seen) {
                        using namespace ::boost::crypto3::detail;

                        if (filled) {
                            process_block();
                        }

                        std::size_t cached_bits = total_seen % block_bits;

                        if (cached_bits != 0) {
                            // If there are already any bits in the cache

                            std::size_t needed_to_fill_bits = block_bits - cached_bits;
                            std::size_t new_bits_to_append =
                                (needed_to_fill_bits > value_seen) ? value_seen : needed_to_fill_bits;

                            injector_type::inject(value, new_bits_to_append, cache, cached_bits);
                            total_seen += new_bits_to_append;

                            if (cached_bits == block_bits) {
                                // If there are enough bits in the incoming value to fill the block
                                filled = true;

                                if (value_seen > new_bits_to_append) {

                                    process_block();

                                    // If there are some remaining bits in the incoming value - put them into the cache,
                                    // which is now empty

                                    cached_bits = 0;

                                    injector_type::inject(value, value_seen - new_bits_to_append, cache, cached_bits,
                                                          new_bits_to_append);

                                    total_seen += value_seen - new_bits_to_append;
                                }
                            }

                        } else {

                            total_seen += value_seen;

                            // If there are no bits in the cache
                            if (value_seen == block_bits) {
                                // The incoming value is a full block
                                filled = true;

                                std::move(value.begin(), value.end(), cache.begin());

                            } else {
                                // The incoming value is not a full block
                                std::move(value.begin(),
                                          value.begin() + value_seen / word_bits + (value_seen % word_bits ? 1 : 0),
                                          cache.begin());
                            }
                        }
                    }

                    inline void process(const word_type &value, std::size_t value_seen) {
                        using namespace ::boost::crypto3::detail;

                        if (filled) {
                            process_block();
                        }

                        std::size_t cached_bits = total_seen % block_bits;

                        if (cached_bits % word_bits != 0) {
                            std::size_t needed_to_fill_bits = block_bits - cached_bits;
                            std::size_t new_bits_to_append =
                                (needed_to_fill_bits > value_seen) ? value_seen : needed_to_fill_bits;

                            injector_type::inject(value, new_bits_to_append, cache, cached_bits);
                            total_seen += new_bits_to_append;

                            if (cached_bits == block_bits) {
                                // If there are enough bits in the incoming value to fill the block

                                filled = true;

                                if (value_seen > new_bits_to_append) {

                                    process_block();

                                    // If there are some remaining bits in the incoming value - put them into the cache,
                                    // which is now empty
                                    cached_bits = 0;

                                    injector_type::inject(value, value_seen - new_bits_to_append, cache, cached_bits,
                                                          new_bits_to_append);

                                    total_seen += value_seen - new_bits_to_append;
                                }
                            }

                        } else {
                            cache[cached_bits / word_bits] = value;

                            total_seen += value_seen;
                        }
                    }

                    mode_type mode;

                    bool filled;
                    std::size_t total_seen;
                    block_type cache;
                    result_type dgst;
                };
            }    // namespace impl

            namespace tag {
                template<typename Mode>
                struct block : boost::accumulators::depends_on<bits_count> {
                    typedef Mode mode_type;

                    /// INTERNAL ONLY
                    ///

                    typedef boost::mpl::always<accumulators::impl::block_impl<mode_type>> impl;
                };
            }    // namespace tag

            namespace extract {
                template<typename Mode, typename AccumulatorSet>
                typename boost::mpl::apply<AccumulatorSet, tag::block<Mode>>::type::result_type
                    block(const AccumulatorSet &acc) {
                    return boost::accumulators::extract_result<tag::block<Mode>>(acc);
                }
            }    // namespace extract
        }        // namespace accumulators
    }            // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_ACCUMULATORS_BLOCK_HPP


#pragma once

#include <map>
#include <memory>
#include <vector>
#include <array>
#include <Eigen/Geometry>

#include "../stateobject.hh"
#include "../../BodyPart/bodypart.hh"
#include "../../JointId/jointid.hh"

namespace bold
{
  typedef unsigned char uchar;

  class BodyPartPosition
  {
  public:
    /** The transformation matrix of this body part.
    *
    * This matrix contains the position and orientation of the body part
    * relative to the torso.
    *
    * Using the returned Affine3d to transform a vector will convert
    * from the frame of this body part into the frame of the torso.
    */
    Eigen::Affine3d const& getTransform() const { return d_transform; }

    /** Get the position of the body part, relative to the torso. */
    Eigen::Vector3d getPosition() const
    {
      return d_transform.translation().head<3>();
    }

  protected:
    BodyPartPosition(Eigen::Affine3d const& transform)
    : d_transform(transform)
    {}

    virtual ~BodyPartPosition() = default;

    // TODO rename partTorsoTransform (and check that's correct!)
    Eigen::Affine3d d_transform;
  };

  class LimbPosition : public BodyPartPosition
  {
  public:
    LimbPosition(std::shared_ptr<Limb const> limb, Eigen::Affine3d const& transform);

    std::shared_ptr<Limb const> const& getLimb() const { return d_limb; }

    Eigen::Vector3d getCentreOfMassPosition() const;

  private:
    std::shared_ptr<Limb const> d_limb;
  };

  class JointPosition : public BodyPartPosition
  {
  public:
    JointPosition(std::shared_ptr<Joint const> joint, Eigen::Affine3d const& transform, double angleRads);

    std::shared_ptr<Joint const> const& getJoint() const { return d_joint; }

    /// @returns the joint's axis direction vector in the agent coordinate system
    Eigen::Vector3d getAxisVec() const { return d_transform * d_joint->axis; }

    double getAngleRads() const { return d_angleRads; }
    double getAngleDegs() const { return Math::radToDeg(d_angleRads); }

  private:
    std::shared_ptr<Joint const> d_joint;
    double d_angleRads;
  };

  class BodyControl;
  class BodyModel;
  class HardwareState;
  template<typename> class Setting;

  /** Models the kinematic chain of the robot's body.
   *
   * The coordinate frame of body parts has x pointing to the right,
   * y pointing forwards, and z pointing up, relative to the agent's forward
   * facing direction.
   *
   * The body is modelled using subclasses of BodyPart:
   * - Limb represents a rigid body part
   * - Joint represents a single revolute joint that connects two body parts
   *
   * The torso is the root of the kinematic chain, from which five chains
   * extend: the two legs, two arms and the head.
   *
   * All body parts know their transform, which contains the translation and
   * rotation of the part's coordinate frame, in the frame of the torso.
   *
   * For more detail on the naming and use of transforms, see EigenTests.hh.
   */
  class BodyState : public StateObject
  {
  public:
    static std::shared_ptr<BodyState const> zero(std::shared_ptr<BodyModel const> const& bodyModel, ulong thinkCycleNumber = 0);

    static Eigen::Vector3d distanceBetween(std::shared_ptr<BodyPartPosition const> const& p1, std::shared_ptr<BodyPartPosition const> const& p2)
    {
      return p2->getPosition() - p1->getPosition();
    }

    /// Initialise with the specified angles, in radians. Indexed by JointId (i.e. 0 is ignored.)
    BodyState(
      std::shared_ptr<BodyModel const> const& bodyModel,
      std::array<double,23> const& angles,
      std::array<short,21> const& positionValueDiffs,
      ulong cycleNumber);

    BodyState(
      std::shared_ptr<BodyModel const> const& bodyModel,
      std::shared_ptr<HardwareState const> const& hardwareState,
      std::shared_ptr<BodyControl> const& bodyControl,
      ulong motionCycleNumber);

    std::shared_ptr<LimbPosition const> getLimb(std::string const& name) const;

    std::shared_ptr<JointPosition const> getJoint(JointId jointId) const;

    void visitJoints(std::function<void(std::shared_ptr<JointPosition const> const&)> visitor) const;
    void visitLimbs(std::function<void(std::shared_ptr<LimbPosition const> const&)> visitor) const;

    void writeJson(rapidjson::Writer<rapidjson::StringBuffer>& writer) const override { writeJsonInternal(writer); }
    void writeJson(rapidjson::Writer<WebSocketBuffer>& writer) const override { writeJsonInternal(writer); }
    void writeJson(rapidjson::PrettyWriter<rapidjson::StringBuffer>& writer) const override { writeJsonInternal(writer); }

    /** Transformation describing camera frame in agent frame, used to
     * transform camera coordinates to agent coordinates */
    Eigen::Affine3d const& getAgentCameraTransform() const { return d_agentCameraTr; }

    /** Transformation describing agent frame in camera frame, used to
     * transform agent coordinates to camera coordinates */
    Eigen::Affine3d const& getCameraAgentTransform() const { return d_cameraAgentTr; }

    Eigen::Vector3d const& getCentreOfMass() const;

    /** Height of torso center above the ground
     *
     * Assumes the lowest foot is flat on the floor */
    double getTorsoHeight() const { return d_torsoHeight; }

    std::array<short,21> const& getPositionValueDiffById() const { return d_positionValueDiffById; }

    Eigen::Affine3d determineFootAgentTr(bool leftFoot) const;

  private:
    template<typename TBuffer>
    void writeJsonInternal(rapidjson::Writer<TBuffer>& writer) const;

    /// Initialise with the specified angles (radians), and position errors (values)
    /// Indexed by JointId (i.e. 0 is ignored.), including camera tilt angle
    void initialise(std::shared_ptr<BodyModel const> const& bodyModel, std::array<double,23> const& angles);

    std::array<short,21> d_positionValueDiffById;

    double d_torsoHeight;

    std::shared_ptr<LimbPosition const> d_torso;

    std::array<std::shared_ptr<JointPosition const>,23> d_jointById;
    std::map<std::string,std::shared_ptr<LimbPosition const>> d_limbByName;

    Eigen::Affine3d d_cameraAgentTr;
    Eigen::Affine3d d_agentCameraTr;

    mutable Eigen::Vector3d d_centreOfMass;
    mutable bool d_isCentreOfMassComputed;

    ulong d_motionCycleNumber;

  public:
    // Needed when having fixed sized Eigen member
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  };

  template<typename TBuffer>
  inline void BodyState::writeJsonInternal(rapidjson::Writer<TBuffer> &writer) const
  {
    writer.StartObject();
    {
      writer.String("motion-cycle");
      writer.Uint64(d_motionCycleNumber);

      writer.String("angles");
      writer.StartArray();
      {
        for (uchar j = (uchar)JointId::MIN; j <= (uchar)JointId::MAX; j++)
        {
          auto const& it = d_jointById[j];
          writer.Double(it->getAngleRads(), "%.3f");
        }
      }
      writer.EndArray();

      writer.String("errors");
      writer.StartArray();
      {
        for (uchar j = (uchar)JointId::MIN; j <= (uchar)JointId::MAX; j++)
          writer.Int(d_positionValueDiffById[j]);
      }
      writer.EndArray();

      auto com = getCentreOfMass();
      writer.String("com");
      writer.StartArray();
      writer.Double(com.x(), "%.4f");
      writer.Double(com.y(), "%.4f");
      writer.Double(com.z(), "%.4f");
      writer.EndArray();
    }
    writer.EndObject();
  }
}


// Boost.Geometry

// Copyright (c) 2021-2022, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGY_SPHERICAL_ENVELOPE_RANGE_HPP
#define BOOST_GEOMETRY_STRATEGY_SPHERICAL_ENVELOPE_RANGE_HPP

#include <boost/geometry/algorithms/assign.hpp>
#include <boost/geometry/algorithms/detail/envelope/initialize.hpp>
#include <boost/geometry/geometries/segment.hpp>
#include <boost/geometry/strategy/spherical/envelope_point.hpp>
#include <boost/geometry/strategy/spherical/envelope_segment.hpp>
#include <boost/geometry/strategy/spherical/expand_segment.hpp>
#include <boost/geometry/views/closeable_view.hpp>

// Get rid of this dependency?
#include <boost/geometry/strategies/spherical/point_in_poly_winding.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace envelope
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

template <typename Range, typename Box, typename EnvelopeStrategy, typename ExpandStrategy>
inline void spheroidal_linestring(Range const& range, Box& mbr,
                                  EnvelopeStrategy const& envelope_strategy,
                                  ExpandStrategy const& expand_strategy)
{
    auto it = boost::begin(range);
    auto const end = boost::end(range);
    if (it == end)
    {
        // initialize box (assign inverse)
        geometry::detail::envelope::initialize<Box>::apply(mbr);
        return;
    }

    auto prev = it;
    ++it;
    if (it == end)
    {
        // initialize box with the first point
        envelope::spherical_point::apply(*prev, mbr);
        return;
    }

    // initialize box with the first segment
    envelope_strategy.apply(*prev, *it, mbr);

    // consider now the remaining segments in the range (if any)
    prev = it;
    ++it;
    while (it != end)
    {
        using point_t = typename boost::range_value<Range>::type;
        geometry::model::referring_segment<point_t const> const seg(*prev, *it);
        expand_strategy.apply(mbr, seg);
        prev = it;
        ++it;
    }
}

// This strategy is intended to be used together with winding strategy to check
// if ring/polygon has a pole in its interior or exterior. It is not intended
// for checking if the pole is on the boundary.
template <typename CalculationType = void>
struct side_of_pole
{
    typedef spherical_tag cs_tag;

    template <typename P>
    static inline int apply(P const& p1, P const& p2, P const& pole)
    {
        using calc_t = typename promote_floating_point
            <
                typename select_calculation_type_alt
                    <
                        CalculationType, P
                    >::type
            >::type;

        using units_t = typename geometry::detail::cs_angular_units<P>::type;
        using constants = math::detail::constants_on_spheroid<calc_t, units_t>;

        calc_t const c0 = 0;
        calc_t const pi = constants::half_period();

        calc_t const lon1 = get<0>(p1);
        calc_t const lat1 = get<1>(p1);
        calc_t const lon2 = get<0>(p2);
        calc_t const lat2 = get<1>(p2);
        calc_t const lat_pole = get<1>(pole);

        calc_t const s_lon_diff = math::longitude_distance_signed<units_t>(lon1, lon2);
        bool const s_vertical = math::equals(s_lon_diff, c0)
                             || math::equals(s_lon_diff, pi);
        // Side of vertical segment is 0 for both poles.
        if (s_vertical)
        {
            return 0;
        }

        // This strategy shouldn't be called in this case but just in case
        // check if segment starts at a pole
        if (math::equals(lat_pole, lat1) || math::equals(lat_pole, lat2))
        {
            return 0;
        }

        // -1 is rhs
        //  1 is lhs
        if (lat_pole >= c0) // north pole
        {
            return s_lon_diff < c0 ? -1 : 1;
        }
        else // south pole
        {
            return s_lon_diff > c0 ? -1 : 1;
        }
    }
};

template <typename Point, typename Range, typename Strategy>
inline int point_in_range(Point const& point, Range const& range, Strategy const& strategy)
{
    typename Strategy::state_type state;

    auto it = boost::begin(range);
    auto const end = boost::end(range);
    for (auto previous = it++ ; it != end ; ++previous, ++it )
    {
        if (! strategy.apply(point, *previous, *it, state))
        {
            break;
        }
    }

    return strategy.result(state);
}

template <typename T, typename Ring, typename PoleWithinStrategy>
inline bool pole_within(T const& lat_pole, Ring const& ring,
                        PoleWithinStrategy const& pole_within_strategy)
{
    if (boost::size(ring) < core_detail::closure::minimum_ring_size
                                <
                                    geometry::closure<Ring>::value
                                >::value)
    {
        return false;
    }

    using point_t = typename geometry::point_type<Ring>::type;
    point_t point;
    geometry::assign_zero(point);
    geometry::set<1>(point, lat_pole);
    geometry::detail::closed_clockwise_view<Ring const> view(ring);
    return point_in_range(point, view, pole_within_strategy) > 0;
}

template
<
    typename Range,
    typename Box,
    typename EnvelopeStrategy,
    typename ExpandStrategy,
    typename PoleWithinStrategy
>
inline void spheroidal_ring(Range const& range, Box& mbr,
                            EnvelopeStrategy const& envelope_strategy,
                            ExpandStrategy const& expand_strategy,
                            PoleWithinStrategy const& pole_within_strategy)
{
    geometry::detail::closed_view<Range const> closed_range(range);

    spheroidal_linestring(closed_range, mbr, envelope_strategy, expand_strategy);

    using coord_t = typename geometry::coordinate_type<Box>::type;
    using point_t = typename geometry::point_type<Box>::type;
    using units_t = typename geometry::detail::cs_angular_units<point_t>::type;
    using constants_t = math::detail::constants_on_spheroid<coord_t, units_t>;
    coord_t const two_pi = constants_t::period();
    coord_t const lon_min = geometry::get<0, 0>(mbr);
    coord_t const lon_max = geometry::get<1, 0>(mbr);
    // If box covers the whole longitude range it is possible that the ring contains
    // one of the poles.
    // Technically it is possible that a reversed ring may cover more than
    // half of the globe and mbr of it's linear ring may be small and not cover the
    // longitude range. We currently don't support such rings.
    if (lon_max - lon_min >= two_pi)
    {
        coord_t const lat_n_pole = constants_t::max_latitude();
        coord_t const lat_s_pole = constants_t::min_latitude();
        coord_t lat_min = geometry::get<0, 1>(mbr);
        coord_t lat_max = geometry::get<1, 1>(mbr);
        // Normalize box latitudes, just in case
        if (math::equals(lat_min, lat_s_pole))
        {
            lat_min = lat_s_pole;
        }
        if (math::equals(lat_max, lat_n_pole))
        {
            lat_max = lat_n_pole;
        }

        if (lat_max < lat_n_pole)
        {
            if (pole_within(lat_n_pole, range, pole_within_strategy))
            {
                lat_max = lat_n_pole;
            }
        }
        if (lat_min > lat_s_pole)
        {
            if (pole_within(lat_s_pole, range, pole_within_strategy))
            {
                lat_min = lat_s_pole;
            }
        }

        geometry::set<0, 1>(mbr, lat_min);
        geometry::set<1, 1>(mbr, lat_max);
    }
}

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

template <typename CalculationType = void>
class spherical_linestring
{
public:
    template <typename Range, typename Box>
    static inline void apply(Range const& range, Box& mbr)
    {
        detail::spheroidal_linestring(range, mbr,
                                      envelope::spherical_segment<CalculationType>(),
                                      expand::spherical_segment<CalculationType>());
    }
};

template <typename CalculationType = void>
class spherical_ring
{
public:
    template <typename Range, typename Box>
    static inline void apply(Range const& range, Box& mbr)
    {
        detail::spheroidal_ring(range, mbr,
                                envelope::spherical_segment<CalculationType>(),
                                expand::spherical_segment<CalculationType>(),
                                within::detail::spherical_winding_base
                                    <
                                        envelope::detail::side_of_pole<CalculationType>,
                                        CalculationType
                                    >());
    }
};

}} // namespace strategy::envelope

}} //namepsace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGY_SPHERICAL_ENVELOPE_RANGE_HPP


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// 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 NETKET_PYOPERATOR_HPP
#define NETKET_PYOPERATOR_HPP

#include <pybind11/complex.h>
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <Eigen/Sparse>
#include <Eigen/SparseCore>
#include <complex>
#include <tuple>
#include <vector>
#include "abstract_operator.hpp"
#include "py_bosonhubbard.hpp"
#include "py_graph_operator.hpp"
#include "py_local_liouvillian.hpp"
#include "py_local_operator.hpp"
#include "py_pauli_strings.hpp"
namespace py = pybind11;

namespace netket {

void AddOperatorModule(py::module m) {
  auto subm = m.def_submodule("operator");

  auto op =
      py::class_<AbstractOperator, std::shared_ptr<AbstractOperator>>(
          m, "Operator", R"EOF(
      Abstract class for quantum Operators. This class prototypes the methods
      needed by a class satisfying the Operator concept. Users interested in
      implementing new quantum Operators should derive they own class from this
      class
       )EOF")
          .def("get_conn",
               [](AbstractOperator& op,
                  py::array_t<double, py::array::c_style> samples) {
                 switch (samples.ndim()) {
                   case 2:
                     return py::cast(
                         op.GetConn(Eigen::Map<const RowMatrix<double>>{
                             samples.data(), samples.shape(0),
                             samples.shape(1)}));
                   case 1: {
                     auto conns =
                         op.GetConn(Eigen::Map<const RowMatrix<double>>{
                             samples.data(), 1, samples.shape(0)});

                     return py::cast(
                         std::tuple<RowMatrix<double>, Eigen::VectorXcd>(
                             std::get<0>(conns)[0], std::get<1>(conns)[0]));
                   }
                   default:
                     NETKET_CHECK(false, InvalidInputError,
                                  "samples has wrong dimension: "
                                      << samples.ndim()
                                      << "; expected either 1 or 2");
                 }
               },
               py::arg("v"), R"EOF(
       Member function finding the connected elements of the Operator. Starting
       from a given visible state v, it finds all other visible states v' such
       that the matrix element :math:`O(v,v')` is different from zero. In general there
       will be several different connected visible units satisfying this
       condition, and they are denoted here :math:`v'(k)`, for :math:`k=0,1...N_{\mathrm{connected}}`.

       Args:
           v: A constant reference to the visible configuration.

       )EOF")
          .def("get_conn_flattened", &AbstractOperator::GetConnFlattened,
               py::arg("v"), py::arg("sections"))
          .def("get_n_conn", &AbstractOperator::GetNConn, py::arg("v"),
               py::arg("n_conn"))
          .def_property_readonly(
              "hilbert", &AbstractOperator::GetHilbert,
              R"EOF(netket.hilbert.Hilbert: ``Hilbert`` space of operator.)EOF")
          .def("to_sparse", &AbstractOperator::ToSparse,
               R"EOF(
         Returns the sparse matrix representation of the operator. Note that, in general,
         the size of the matrix is exponential in the number of quantum
         numbers, and this operation should thus only be performed for
         low-dimensional Hilbert spaces or sufficiently sparse operators.

         This method requires an indexable Hilbert space.
         )EOF")
          .def("to_dense", &AbstractOperator::ToDense,
               R"EOF(
         Returns the dense matrix representation of the operator. Note that, in general,
         the size of the matrix is exponential in the number of quantum
         numbers, and this operation should thus only be performed for
         low-dimensional Hilbert spaces.

         This method requires an indexable Hilbert space.
         )EOF")
          .def("to_linear_operator",
               [](py::object py_self) {
                 const auto* cxx_self = py_self.cast<AbstractOperator const*>();
                 const auto dtype =
                     py::module::import("numpy").attr("complex128");
                 const auto linear_operator =
                     py::module::import("scipy.sparse.linalg")
                         .attr("LinearOperator");
                 const auto dim = cxx_self->Dimension();
                 return linear_operator(
                     py::arg{"shape"} = std::make_tuple(dim, dim),
                     py::arg{"matvec"} = py::cpp_function(
                         // TODO: Does this copy data?
                         [py_self, cxx_self](const Eigen::VectorXcd& x) {
                           return cxx_self->Apply(x);
                         }),
                     py::arg{"dtype"} = dtype);
               },
               R"EOF(
        Converts `Operator` to `scipy.sparse.linalg.LinearOperator`.

        This method requires an indexable Hilbert space.
          )EOF")
          .def("__call__", &AbstractOperator::Apply,
               R"EOF(
        Applies the operator to a state.
            )EOF");

  AddBoseHubbard(subm);
  AddLocalOperator(subm);
  AddGraphOperator(subm);
  AddPauliStrings(subm);
  AddLocalSuperOperatorModule(subm);

  subm.def("_rotated_grad_kernel",
           [](Eigen::Ref<const Eigen::ArrayXcd> log_vals_prime,
              Eigen::Ref<const Eigen::ArrayXcd> mels,
              Eigen::Ref<Eigen::ArrayXcd> vec) {
             const auto max_log_val = log_vals_prime.real().maxCoeff();

             vec = (mels * (log_vals_prime - max_log_val).exp()).conjugate();
             vec /= vec.sum();
           });

  subm.def(
      "_der_local_values_notcentered_kernel",
      [](Eigen::Ref<const Eigen::VectorXcd> log_vals_zero,
         const std::vector<Eigen::Ref<const Eigen::VectorXcd>>& log_vals_prime,
         const std::vector<Eigen::Ref<const Eigen::VectorXcd>>& mels,
         const std::vector<Eigen::Ref<const RowMatrix<Complex>>>& der_log_vals,
         //   Eigen::Ref<Eigen::VectorXcd> local_vals,
         Eigen::Ref<RowMatrix<Complex>> der_local_vals) {
        Eigen::VectorXcd tmp = Eigen::VectorXcd(1);
        for (std::size_t k = 0; k < mels.size(); k++) {
          tmp.resize(log_vals_prime[k].size());

          tmp = (mels[k].array() *
                 (log_vals_prime[k].array() - log_vals_zero(k)).exp());

          // Computing the local_val is not needed, but it's almost for free so
          // we might as well return this too.
          // local_vals(k) = tmp.sum();

          der_local_vals.row(k) =
              (der_log_vals[k].array().colwise() * tmp.array()).colwise().sum();
        }
      });

  subm.def(
      "_der_local_values_kernel",
      [](Eigen::Ref<const Eigen::VectorXcd> log_vals_zero,
         const std::vector<Eigen::Ref<const Eigen::VectorXcd>>& log_vals_prime,
         const std::vector<Eigen::Ref<const Eigen::VectorXcd>>& mels,
         const Eigen::Ref<const RowMatrix<Complex>>& der_log_zero,
         const std::vector<Eigen::Ref<const RowMatrix<Complex>>>& der_log_vals,
         //   Eigen::Ref<Eigen::VectorXcd> local_vals,
         Eigen::Ref<RowMatrix<Complex>> der_local_vals) {
        for (std::size_t k = 0; k < mels.size(); k++) {
          auto tmp = (mels[k].array() *
                      (log_vals_prime[k].array() - log_vals_zero(k)).exp());

          // Computing the local_val is not needed, but it's almost for free so
          // we might as well return this too.
          // local_vals(k) = tmp.sum();

          der_local_vals.row(k) =
              ((der_log_vals[k].colwise() - der_log_zero.col(k))
                   .array()
                   .colwise() *
               tmp.array())
                  .colwise()
                  .sum();
        }
      });
}
}  // namespace netket

#endif


#include <boost/geometry/strategies/cartesian/intersection.hpp>


/*
 * Copyright (C) 2011-2014 Daniel Scharrer
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the author(s) be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

/*!
 * \file
 *
 * CRC32 checksum routines.
 */
#ifndef INNOEXTRACT_CRYPTO_CRC32_HPP
#define INNOEXTRACT_CRYPTO_CRC32_HPP

#include <boost/cstdint.hpp>

#include "crypto/checksum.hpp"

namespace crypto {

//! CRC32 checksum calculation
struct crc32 : public checksum_base<crc32> {

	void init() { crc = CRC32_NEGL; }

	void update(const char * data, size_t length);

	boost::uint32_t finalize() const { return crc ^ CRC32_NEGL; }

private:

	static const boost::uint32_t CRC32_NEGL = 0xffffffffl;

	boost::uint32_t crc;
};

} // namespace crypto

#endif // INNOEXTRACT_CRYPTO_CRC32_HPP


// Copyright David Stone 2020.
// 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 <tm/evaluate/evaluate.hpp>
#include <tm/evaluate/expectiminimax.hpp>

#include <tm/move/call_move.hpp>
#include <tm/move/moves.hpp>
#include <tm/move/side_effects.hpp>

#include <tm/pokemon/species.hpp>

#include <tm/stat/ev.hpp>
#include <tm/stat/iv.hpp>

#include <tm/end_of_turn.hpp>
#include <tm/team.hpp>
#include <tm/weather.hpp>

#include <bounded/assert.hpp>
#include <bounded/integer.hpp>

#include <containers/array/array.hpp>
#include <containers/begin_end.hpp>
#include <containers/front_back.hpp>
#include <containers/legacy_iterator.hpp>
#include <containers/size.hpp>

#include <boost/iostreams/stream.hpp>

#include <random>

#include <catch2/catch_test_macros.hpp>

namespace technicalmachine {
namespace {
using namespace bounded::literal;

struct log_out {
	using char_type = char;
	using category = boost::iostreams::sink_tag;

	auto write(char const * const ptr, std::streamsize const size) -> std::streamsize {
		INFO(std::string_view(ptr, static_cast<std::size_t>(size)));
		return size;
	}
};

template<Generation generation>
auto test_expectiminimax(Team<generation> const & ai, Team<generation> const & foe, Weather const weather, Evaluate<generation> const evaluate, Depth const depth) -> BestMove {
	auto log_buffer = log_out();
	auto log = boost::iostreams::stream<log_out>(log_buffer);
	return expectiminimax(ai, foe, weather, evaluate, depth, log);
}

constexpr auto make_depth(unsigned const depth) {
	return Depth(DepthValues{depth, 0U}, 0U);
}

auto shuffled_regular_moves(Generation const generation, auto & random_engine, auto... ts) {
	// Random order to prevent ordering effects from accidentally arriving at
	// the correct move each time
	auto moves = RegularMoves{Move(generation, ts)...};
	std::shuffle(containers::legacy_iterator(containers::begin(moves)), containers::legacy_iterator(containers::end(moves)), random_engine);
	return moves;
}

TEST_CASE("expectiminimax OHKO", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto const weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(1U);

	auto team1 = Team<generation>(1_bi, true);
	team1.add_pokemon(Pokemon<generation>(
		Species::Jolteon,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Volt_Absorb,
		default_combined_stats<generation>,
		regular_moves(Moves::Thunderbolt, Moves::Charm, Moves::Thunder, Moves::Shadow_Ball)
	));
	team1.pokemon().switch_in(weather);
	team1.reset_start_of_turn();

	auto team2 = Team<generation>(1_bi);
	team2.add_pokemon(Pokemon<generation>(
		Species::Gyarados,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Intimidate,
		default_combined_stats<generation>,
		regular_moves(Moves::Dragon_Dance, Moves::Waterfall, Moves::Stone_Edge, Moves::Taunt)
	));
	team2.pokemon().switch_in(weather);
	team2.reset_start_of_turn();

	{
		auto const best_move = test_expectiminimax(team1, team2, weather, evaluate, depth);
		CHECK(best_move.name == Moves::Thunderbolt);
		CHECK(best_move.score == victory<generation>);
	}

	auto team3 = Team<generation>(1_bi);
	team3.add_pokemon(Pokemon<generation>(
		Species::Shedinja,
		Level(100_bi),
		Gender::male,
		Item::Lum_Berry,
		Ability::Wonder_Guard,
		default_combined_stats<generation>,
		regular_moves(Moves::Swords_Dance, Moves::X_Scissor, Moves::Shadow_Sneak, Moves::Will_O_Wisp)
	));
	team3.pokemon().switch_in(weather);
	team3.reset_start_of_turn();

	{
		auto const best_move = test_expectiminimax(team1, team3, weather, evaluate, depth);
		CHECK(best_move.name == Moves::Shadow_Ball);
		CHECK(best_move.score == victory<generation>);
	}
}

TEST_CASE("expectiminimax one-turn damage", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto const weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(1U);

	auto attacker = Team<generation>(1_bi, true);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Jolteon,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Volt_Absorb,
		CombinedStats<generation>{
			Nature::Hardy,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Thunderbolt, Moves::Charm, Moves::Thunder, Moves::Shadow_Ball)
	));
	attacker.pokemon().switch_in(weather);
	attacker.reset_start_of_turn();

	auto defender = Team<generation>(1_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Swampert,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Torrent,
		default_combined_stats<generation>,
		regular_moves(Moves::Surf, Moves::Ice_Beam)
	));
	defender.pokemon().switch_in(weather);
	defender.reset_start_of_turn();

	auto const best_move = test_expectiminimax(attacker, defender, weather, evaluate, depth);
	CHECK(best_move.name == Moves::Shadow_Ball);
}

TEST_CASE("expectiminimax BellyZard", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto const weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(2U);

	auto attacker = Team<generation>(1_bi, true);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Charizard,
		Level(100_bi),
		Gender::male,
		Item::Salac_Berry,
		Ability::Blaze,
		CombinedStats<generation>{
			Nature::Hardy,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Fire_Punch, Moves::Belly_Drum, Moves::Earthquake, Moves::Double_Edge)
	));
	attacker.pokemon().switch_in(weather);
	attacker.reset_start_of_turn();

	auto defender = Team<generation>(1_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Mew,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Synchronize,
		CombinedStats<generation>{
			Nature::Hardy,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(252_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Soft_Boiled)
	));
	defender.pokemon().switch_in(weather);
	defender.reset_start_of_turn();

	auto const best_move = test_expectiminimax(attacker, defender, weather, evaluate, depth);
	CHECK(best_move.name == Moves::Belly_Drum);
	CHECK(best_move.score == victory<generation>);
}

TEST_CASE("expectiminimax Hippopotas vs Wobbuffet", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto const weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(11U);

	auto attacker = Team<generation>(1_bi, true);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Hippopotas,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Sand_Stream,
		CombinedStats<generation>{
			Nature::Adamant,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Curse, Moves::Crunch)
	));
	attacker.pokemon().switch_in(weather);
	attacker.reset_start_of_turn();

	// TODO: Implement Encore's effect ending when PP runs out, then Wobbuffet
	// can have Encore

	auto defender = Team<generation>(1_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Wobbuffet,
		Level(100_bi),
		Gender::genderless,
		Item::Leftovers,
		Ability::Shadow_Tag,
		CombinedStats<generation>{
			Nature::Hardy,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Counter)
	));
	defender.pokemon().switch_in(weather);
	defender.reset_start_of_turn();

	auto const best_move = test_expectiminimax(attacker, defender, weather, evaluate, depth);
	CHECK(best_move.name == Moves::Curse);
	CHECK(best_move.score == victory<generation>);
}

TEST_CASE("expectiminimax Baton Pass", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto const weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = Depth(DepthValues{4U, 0U}, 0U);

	auto attacker = Team<generation>(2_bi, true);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Smeargle,
		Level(100_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Own_Tempo,
		default_combined_stats<generation>,
		regular_moves(Moves::Baton_Pass, Moves::Belly_Drum)
	));
	attacker.pokemon().switch_in(weather);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Alakazam,
		Level(100_bi),
		Gender::male,
		Item::Lum_Berry,
		Ability::Synchronize,
		CombinedStats<generation>{
			Nature::Adamant,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Psycho_Cut, Moves::Recover)
	));
	attacker.reset_start_of_turn();

	auto defender = Team<generation>(2_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Gengar,
		Level(100_bi),
		Gender::male,
		Item::Choice_Specs,
		Ability::Levitate,
		CombinedStats<generation>{
			Nature::Modest,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Shadow_Ball)
	));
	defender.pokemon().switch_in(weather);

	defender.add_pokemon(Pokemon<generation>(
		Species::Misdreavus,
		Level(100_bi),
		Gender::female,
		Item::Choice_Specs,
		Ability::Levitate,
		CombinedStats<generation>{
			Nature::Modest,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(252_bi),
				EV(0_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Shadow_Ball)
	));

	defender.reset_start_of_turn();

	auto const best_move = test_expectiminimax(attacker, defender, weather, evaluate, depth);
	CHECK(best_move.name == Moves::Belly_Drum);
	CHECK(best_move.score == victory<generation>);
}

TEST_CASE("expectiminimax replace fainted", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(2U);

	auto attacker = Team<generation>(3_bi, true);

	attacker.add_pokemon(Pokemon<generation>(
		Species::Magikarp,
		Level(5_bi),
		Gender::male,
		Item::Leftovers,
		Ability::Swift_Swim,
		default_combined_stats<generation>,
		regular_moves(Moves::Tackle)
	));
	attacker.pokemon().switch_in(weather);

	attacker.add_pokemon(Pokemon<generation>(
		Species::Slugma,
		Level(100_bi),
		Gender::male,
		Item::Choice_Specs,
		Ability::Magma_Armor,
		default_combined_stats<generation>,
		regular_moves(Moves::Flamethrower, Moves::Earth_Power)
	));
	attacker.add_pokemon(Pokemon<generation>(
		Species::Zapdos,
		Level(100_bi),
		Gender::genderless,
		Item::Choice_Specs,
		Ability::Pressure,
		CombinedStats<generation>{
			Nature::Modest,
			max_dvs_or_ivs<generation>,
			default_evs<generation>
		},
		regular_moves(Moves::Thunderbolt)
	));

	attacker.reset_start_of_turn();

	auto defender = Team<generation>(1_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Suicune,
		Level(100_bi),
		Gender::genderless,
		Item::Leftovers,
		Ability::Pressure,
		default_combined_stats<generation>,
		regular_moves(Moves::Calm_Mind, Moves::Surf, Moves::Ice_Beam)
	));
	defender.pokemon().switch_in(weather);

	defender.reset_start_of_turn();

	{
		constexpr auto move_name = Moves::Surf;
		auto const side_effects = possible_side_effects(move_name, defender.pokemon().as_const(), attacker, weather);
		REQUIRE(containers::size(side_effects) == 1_bi);
		auto const & side_effect = containers::front(side_effects);
		call_move(
			defender,
			UsedMove<generation>(
				move_name,
				side_effect.function
			),
			attacker,
			FutureMove{false},
			weather,
			false,
			ActualDamage::Unknown{}
		);
	}

	auto const best_move = test_expectiminimax(attacker, defender, weather, evaluate, depth);
	CHECK(best_move.name == Moves::Switch2);
}

TEST_CASE("expectiminimax Latias vs Suicune", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto const weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(3U);

	auto attacker = Team<generation>(1_bi, true);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Latias,
		Level(100_bi),
		Gender::female,
		Item::Leftovers,
		Ability::Levitate,
		CombinedStats<generation>{
			Nature::Calm,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(120_bi),
				EV(136_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Calm_Mind, Moves::Dragon_Pulse, Moves::Recover)
	));
	attacker.pokemon().switch_in(weather);

	attacker.reset_start_of_turn();

	auto defender = Team<generation>(1_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Suicune,
		Level(100_bi),
		Gender::genderless,
		Item::Leftovers,
		Ability::Pressure,
		CombinedStats<generation>{
			Nature::Calm,
			max_dvs_or_ivs<generation>,
			EVs(
				EV(0_bi),
				EV(0_bi),
				EV(0_bi),
				EV(120_bi),
				EV(136_bi),
				EV(0_bi)
			)
		},
		regular_moves(Moves::Ice_Beam, Moves::Rest)
	));
	defender.pokemon().switch_in(weather);

	defender.reset_start_of_turn();

	auto const best_move = test_expectiminimax(attacker, defender, weather, evaluate, depth);
	CHECK(best_move.name == Moves::Calm_Mind);
}

TEST_CASE("expectiminimax Sleep Talk", "[expectiminimax]") {
	constexpr auto generation = Generation::four;
	auto const evaluate = Evaluate<generation>();
	auto weather = Weather();
	auto random_engine = std::mt19937(std::random_device()());
	auto const regular_moves = [&](auto... args) {
		return shuffled_regular_moves(generation, random_engine, args...);
	};
	constexpr auto depth = make_depth(1U);

	auto attacker = Team<generation>(1_bi, true);

	attacker.add_pokemon(Pokemon<generation>(
		Species::Jolteon,
		Level(100_bi),
		Gender::female,
		Item::Leftovers,
		Ability::Volt_Absorb,
		default_combined_stats<generation>,
		regular_moves(Moves::Sleep_Talk, Moves::Thunderbolt)
	));
	attacker.pokemon().switch_in(weather);
	attacker.reset_start_of_turn();

	auto defender = Team<generation>(1_bi);
	defender.add_pokemon(Pokemon<generation>(
		Species::Gyarados,
		Level(100_bi),
		Gender::male,
		Item::Life_Orb,
		Ability::Intimidate,
		default_combined_stats<generation>,
		regular_moves(Moves::Earthquake)
	));
	defender.pokemon().switch_in(weather);

	defender.reset_start_of_turn();

	constexpr auto keep_status = false;
	constexpr auto unknown_damage = ActualDamage::Unknown{};
	constexpr auto sleep_talk = UsedMove<generation>(
		Moves::Sleep_Talk,
		no_effect_function
	);
	constexpr auto thunderbolt = UsedMove<generation>(
		Moves::Thunderbolt,
		no_effect_function
	);
	constexpr auto other_move = FutureMove{false};

	auto next_turn = [&]{
		constexpr auto end_of_turn_flags = EndOfTurnFlags(false, false);
		end_of_turn(attacker, end_of_turn_flags, defender, end_of_turn_flags, weather);
		attacker.reset_start_of_turn();
		defender.reset_start_of_turn();
	};

	auto jolteon = attacker.pokemon();

	// TODO: Validate score, too

	CHECK(jolteon.status().name() == Statuses::clear);
	CHECK(test_expectiminimax(attacker, defender, weather, evaluate, depth).name == Moves::Thunderbolt);

	call_move(attacker, sleep_talk, defender, other_move, weather, keep_status, unknown_damage);
	jolteon.set_status(Statuses::sleep, weather);
	next_turn();
	CHECK(jolteon.status().name() == Statuses::sleep);
	CHECK(test_expectiminimax(attacker, defender, weather, evaluate, depth).name == Moves::Sleep_Talk);

	call_move(attacker, thunderbolt, defender, other_move, weather, keep_status, unknown_damage);
	next_turn();
	CHECK(jolteon.status().name() == Statuses::sleep);
	CHECK(test_expectiminimax(attacker, defender, weather, evaluate, depth).name == Moves::Sleep_Talk);

	call_move(attacker, thunderbolt, defender, other_move, weather, keep_status, unknown_damage);
	next_turn();
	CHECK(jolteon.status().name() == Statuses::sleep);
	CHECK(test_expectiminimax(attacker, defender, weather, evaluate, depth).name == Moves::Sleep_Talk);

	#if 0
		// Same probability of either move
		call_move(attacker, thunderbolt, defender, other_move, weather, keep_status, unknown_damage);
		next_turn();
		CHECK(jolteon.status().name() == Statuses::sleep);
		CHECK(test_expectiminimax(attacker, defender, weather, evaluate, depth).name == ?);
	#endif
}

TEST_CASE("Generation 1 frozen last Pokemon", "[expectiminimax]") {
	constexpr auto generation = Generation::one;
	auto const evaluate = Evaluate<generation>();
	auto weather = Weather();
	auto regular_moves = [](auto const ... name) {
		return RegularMoves{Move(generation, name)...};
	};

	auto attacker = Team<generation>(1_bi, true);
	attacker.add_pokemon(Pokemon<generation>(
		Species::Alakazam,
		Level(100_bi),
		Gender::genderless,
		Item::None,
		Ability::Honey_Gather,
		default_combined_stats<generation>,
		regular_moves(Moves::Psychic, Moves::Recover, Moves::Thunder_Wave, Moves::Seismic_Toss)
	));
	attacker.pokemon().switch_in(weather);

	auto defender = Team<generation>(1_bi);
	{
		auto gengar = Pokemon<generation>(
			Species::Gengar,
			Level(100_bi),
			Gender::genderless,
			Item::None,
			Ability::Honey_Gather,
			default_combined_stats<generation>,
			regular_moves(Moves::Explosion, Moves::Hypnosis, Moves::Thunderbolt, Moves::Night_Shade)
		);
		gengar.set_status(Statuses::freeze);
		gengar.set_hp(12_bi);

		defender.add_pokemon(gengar);
		defender.pokemon().switch_in(weather);
	}

	attacker.reset_start_of_turn();
	defender.reset_start_of_turn();

	CHECK(test_expectiminimax(attacker, defender, weather, evaluate, make_depth(1U)).name == Moves::Psychic);
	CHECK(test_expectiminimax(attacker, defender, weather, evaluate, make_depth(2U)).name == Moves::Psychic);
}

} // namespace
} // namespace technicalmachine