#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>

#include <psemek/gfx/painter.hpp>

#include <psemek/math/camera.hpp>
#include <psemek/math/gauss.hpp>
#include <psemek/math/distance.hpp>
#include <psemek/math/interval.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/rotation.hpp>

#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/random/normal.hpp>

#include <psemek/async/threadpool.hpp>

#include <psemek/util/clock.hpp>
#include <psemek/util/function.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/pretty_print.hpp>

//#include <eigen3/Eigen/Core>
//#include <eigen3/Eigen/Cholesky>

#include <fstream>
#include <iomanip>

using namespace psemek;

namespace
{

struct bone
{
	math::point<float, 2> position;
	math::vector<float, 2> direction;
	math::vector<float, 2> velocity;
	float angular_velocity;
	float length;
	float width;
	float mass = 0.f;
	float inertia = 0.f;
};

struct joint
{
	std::size_t i0, i1;
	float s0, s1;
	// angle range max should be in [0, 2*pi]
	// angle range min should be less or equal to angle range max
	std::optional<math::interval<float>> angle_range = std::nullopt;
};

template <std::size_t Bones, std::size_t Constraints>
struct constraint
{
	std::size_t bone[Bones];
	math::matrix<float, Constraints, 4 * Bones> jacobian;
	math::vector<float, Constraints> bias;
	math::interval<float> range = math::interval<float>::full();
};

struct system
{
	float const dt = 0.01f;

	std::vector<bone> bones;
	std::vector<joint> joints;

	struct selection
	{
		std::size_t index;
		float pos;
		math::vector<float, 2> delta;
	};

	void advance(float time, std::optional<selection> sel, util::function<std::vector<float>(system const &)> torque_provider);

private:
	float time_lag_ = 0.f;
	std::vector<float> old_torque_;

	void step(std::optional<selection> sel, util::function<std::vector<float>(system const &)> const & torque_provider);

	template <std::size_t B, std::size_t C>
	void solve_constraint(constraint<B, C> const & c);
};

void system::advance(float time, std::optional<selection> sel, util::function<std::vector<float>(system const &)> torque_provider)
{
	time_lag_ += time;

	while (time_lag_ >= dt)
	{
		time_lag_ -= dt;
		step(sel, torque_provider);
	}
}

void system::step(std::optional<selection> sel, util::function<std::vector<float>(system const &)> const & torque_provider)
{
	if (old_torque_.size() != joints.size())
		old_torque_.assign(joints.size(), 0.f);

	math::vector<float, 2> const gravity { 0.f, -10.f };

	for (std::size_t i = 0; i < bones.size(); ++i)
	{
		auto & b = bones[i];

		b.velocity += gravity * dt;
	}

	auto torque = torque_provider(*this);

	for (std::size_t i = 0; i < joints.size(); ++i)
	{
		auto const & j = joints[i];

		auto & b0 = bones[j.i0];
		auto & b1 = bones[j.i1];

		float const max_torque_change = 250.f * dt;

		float d = std::max(std::abs(torque[i] - old_torque_[i]) / max_torque_change, 1.f);

		old_torque_[i] = old_torque_[i] + (torque[i] - old_torque_[i]) / d;

		b0.angular_velocity -= old_torque_[i] * dt / b0.inertia;
		b1.angular_velocity += old_torque_[i] * dt / b1.inertia;
	}

	for (std::size_t i = 0; i < bones.size(); ++i)
	{
		auto & b = bones[i];

		b.position += b.velocity * dt;
		b.direction = math::normalized(b.direction + math::ort(b.direction) * b.angular_velocity * dt);
	}

	std::vector<constraint<1, 1>> constraints_1_1;
	std::vector<constraint<1, 2>> constraints_1_2;
	std::vector<constraint<2, 1>> constraints_2_1;
	std::vector<constraint<2, 2>> constraints_2_2;

	if (sel)
	{
		auto & b = bones[sel->index];

		constraint<1, 2> & c = constraints_1_2.emplace_back();
		c.bone[0] = sel->index;

		c.jacobian[0][0] = 1.f;
		c.jacobian[0][1] = 0.f;
		c.jacobian[0][2] = sel->pos * b.length / 2.f;
		c.jacobian[0][3] = 0.f;

		c.jacobian[1][0] = 0.f;
		c.jacobian[1][1] = 1.f;
		c.jacobian[1][2] = 0.f;
		c.jacobian[1][3] = sel->pos * b.length / 2.f;

		c.bias = sel->delta * 0.5f / dt;
	}

	for (std::size_t i = 0; i < bones.size(); ++i)
	{
		auto & b = bones[i];

		float const depth_factor = 0.5f / dt;

		float const bounce = 0.05f;
		float const friction = 0.05f;

		auto push_1 = [&](float depth, float s, math::vector<float, 2> const & v){
			constraint<1, 1> & c = constraints_1_1.emplace_back();

			c.bone[0] = i;

			c.jacobian[0][0] = 0.f;
			c.jacobian[0][1] = 1.f;
			c.jacobian[0][2] = 0.f;
			c.jacobian[0][3] = b.length / 2.f * s;

			c.bias[0] = v[1] - depth * depth_factor;

			c.range.min = 0.f;

			constraint<1, 1> & fc = constraints_1_1.emplace_back();

			fc.bone[0] = i;

			fc.jacobian[0][0] = 1.f;
			fc.jacobian[0][1] = 0.f;
			fc.jacobian[0][2] = s * b.length / 2.f;
			fc.jacobian[0][3] = 0.f;

			fc.bias = fc.bias.zero();

			fc.range.min = -friction;
			fc.range.max = friction;
		};

		std::optional<float> d0, d1;

		auto p0 = b.position - b.direction * b.length / 2.f;
		auto p1 = b.position + b.direction * b.length / 2.f;

		auto v0 = b.velocity - math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
		auto v1 = b.velocity + math::ort(b.direction) * b.angular_velocity * b.length / 2.f;

		if (p0[1] < b.width / 2.f)
		{
			float d = b.width / 2.f - p0[1];
			if (d > 0.f)
			{
				d0 = d;
			}
		}

		if (p1[1] < b.width / 2.f)
		{
			float d = b.width / 2.f - p1[1];
			if (d > 0.f)
			{
				d1 = d;
			}
		}

		if (d0 && d1)
		{
			constraint<1, 2> & c = constraints_1_2.emplace_back();

			c.bone[0] = i;

			c.jacobian[0][0] = 0.f;
			c.jacobian[0][1] = 1.f;
			c.jacobian[0][2] = 0.f;
			c.jacobian[0][3] = -b.length / 2.f;
			c.jacobian[1][0] = 0.f;
			c.jacobian[1][1] = 1.f;
			c.jacobian[1][2] = 0.f;
			c.jacobian[1][3] = b.length / 2.f;

			c.bias[0] = - (*d0) * depth_factor + v0[1] * bounce;
			c.bias[1] = - (*d1) * depth_factor + v1[1] * bounce;

			c.range.min = 0.f;

			constraint<1, 2> & fc = constraints_1_2.emplace_back();

			fc.bone[0] = i;

			fc.jacobian[0][0] = 1.f;
			fc.jacobian[0][1] = 0.f;
			fc.jacobian[0][2] = -b.length / 2.f;
			fc.jacobian[0][3] = 0.f;
			fc.jacobian[1][0] = 1.f;
			fc.jacobian[1][1] = 0.f;
			fc.jacobian[1][2] = b.length / 2.f;
			fc.jacobian[1][3] = 0.f;

			fc.range.min = -friction;
			fc.range.max = friction;

			fc.bias = fc.bias.zero();

		}
		else if (d0)
		{
			push_1(*d0, -1.f, v0 * bounce);
		}
		else if (d1)
		{
			push_1(*d1, 1.f, v1 * bounce);
		}
	}

	for (auto const & j : joints)
	{
		constraint<2, 2> & c = constraints_2_2.emplace_back();

		auto & b0 = bones[j.i0];
		auto & b1 = bones[j.i1];

		c.bone[0] = j.i0;
		c.bone[1] = j.i1;

		c.jacobian = c.jacobian.zero();
		c.jacobian[0][0] = -1.f;
		c.jacobian[0][2] = -j.s0 * b0.length / 2.f;
		c.jacobian[0][4] = 1.f;
		c.jacobian[0][6] = j.s1 * b1.length / 2.f;
		c.jacobian[1][1] = -1.f;
		c.jacobian[1][3] = -j.s0 * b0.length / 2.f;
		c.jacobian[1][5] = 1.f;
		c.jacobian[1][7] = j.s1 * b1.length / 2.f;

		c.bias = ((b1.position + b1.direction * j.s1 * b1.length / 2.f) - (b0.position + b0.direction * j.s0 * b0.length / 2.f)) / dt;

		if (j.angle_range)
		{
			float x = math::dot(b0.direction, b1.direction);
			float y = math::det(b0.direction, b1.direction);

			// a \in [-pi, pi]
			float a = std::atan2(y, x);

			// j.angle_range->max \in [0, 2*pi]
			// j.angle_range->min \in [-2*pi, 2*pi]
			// j.angle_range->center() \in [-pi, 2*pi]
			// delta \in [-3*pi, 2*pi]
			float delta = a - j.angle_range->center();
			if (delta > math::pi)
				delta -= 2.f * math::pi;
			if (delta < -math::pi)
				delta += 2.f * math::pi;
			// delta in [-pi, pi]

			float const half_length = j.angle_range->length() / 2.f;

			if (std::abs(delta) > half_length)
			{
				float const factor = 2.f;

				constraint<2, 1> & c = constraints_2_1.emplace_back();

				c.bone[0] = j.i0;
				c.bone[1] = j.i1;

				c.jacobian = c.jacobian.zero();
				c.jacobian[0][2] = - y * b1.direction[0] + x * b1.direction[1];
				c.jacobian[0][3] = - y * b1.direction[1] - x * b1.direction[0];
				c.jacobian[0][6] = - y * b0.direction[0] - x * b0.direction[1];
				c.jacobian[0][7] = - y * b0.direction[1] + x * b0.direction[0];

				if (delta > 0.f)
				{
					c.bias[0] = (delta - half_length) * factor / dt;
					c.range.max = 0.f;
				}
				else
				{
					c.bias[0] = (delta + half_length) * factor / dt;
					c.range.min = 0.f;
				}
			}
		}
	}

	std::size_t const steps = 1;

	for (std::size_t step = 0; step < steps; ++step)
	{
		for (auto const & c : constraints_1_1)
		{
			solve_constraint(c);
		}

		for (auto const & c : constraints_1_2)
		{
			solve_constraint(c);
		}

		for (auto const & c : constraints_2_1)
		{
			solve_constraint(c);
		}

		for (auto const & c : constraints_2_2)
		{
			solve_constraint(c);
		}
	}
}

template <std::size_t B, std::size_t C>
void system::solve_constraint(constraint<B, C> const & c)
{
	math::vector<float, 4 * B> v;
	auto j_inv_mass = c.jacobian;

	for (std::size_t i = 0; i < B; ++i)
	{
		bone const & b = bones[c.bone[i]];

		v[4 * i + 0] = b.velocity[0];
		v[4 * i + 1] = b.velocity[1];
		v[4 * i + 2] = -b.angular_velocity * b.direction[1];
		v[4 * i + 3] = b.angular_velocity * b.direction[0];

		for (std::size_t j = 0; j < C; ++j)
		{
			j_inv_mass[j][4 * i + 0] /= b.mass;
			j_inv_mass[j][4 * i + 1] /= b.mass;
			j_inv_mass[j][4 * i + 2] /= b.inertia;
			j_inv_mass[j][4 * i + 3] /= b.inertia;
		}
	}

	math::vector<float, C> l = *math::solve(j_inv_mass * math::transpose(c.jacobian), - c.jacobian * v - c.bias);
	for (std::size_t j = 0; j < C; ++j)
		l[j] = math::clamp(l[j], c.range);

	auto p = math::transpose(j_inv_mass) * l;

	for (std::size_t i = 0; i < B; ++i)
	{
		bone  & b = bones[c.bone[i]];

		b.velocity[0] += p[4 * i + 0];
		b.velocity[1] += p[4 * i + 1];
		b.angular_velocity += math::det(b.direction, math::vector{p[4 * i + 2], p[4 * i + 3]});
	}
}

struct controller
{
	// inputs should be equal to number of bones * 7
	// outputs should be equal to number of joints

	static constexpr std::size_t inputs = 24 * 7;
	static constexpr std::size_t outputs = 23;

//	static constexpr std::size_t layer1 = outputs;
//	static constexpr std::size_t layer2 = 4;
//	static constexpr std::size_t layer3 = outputs;

	//static constexpr std::size_t param_count = (inputs + 1) * outputs;

	static constexpr float max_output = 20.f;

	math::matrix<float, outputs, inputs> m1;
	math::vector<float, outputs> t1;

//	math::matrix<float, layer1, inputs> m1;
//	math::vector<float, layer1> t1;
//	math::matrix<float, layer2, layer1> m2;
//	math::vector<float, layer2> t2;
//	math::matrix<float, layer3, layer2> m3;
//	math::vector<float, layer3> t3;

	//Eigen::VectorXf to_eigen() const;
	//void from_eigen(Eigen::VectorXf const & v);

	int generation = 0;

	template <typename RNG>
	void randomize(RNG && rng, float amplitude);

	template <typename RNG>
	void mutate(RNG && rng, float amplitude);

	void reset();

	math::vector<float, outputs> apply(system const & s) const;

	void read(std::istream & is);
	void write(std::ostream & os) const;

	static float activation(float x);

	template <std::size_t N>
	static math::vector<float, N> activation(math::vector<float, N> v);

	template <typename T>
	static void read(std::istream & is, T & x);

	template <typename T>
	static void write(std::ostream & os, T const & x);
};

template <typename RNG>
void controller::randomize(RNG && rng, float amplitude)
{
	generation = 0;

	random::uniform_distribution<float> d{-1.f, 1.f};

	auto visit_m = [&rng, &d, amplitude](auto & m)
	{
		for (std::size_t i = 0; i < m.rows(); ++i)
		{
			for (std::size_t j = 0; j < m.columns(); ++j)
			{
				m[i][j] = d(rng) * amplitude;
			}
		}
	};

	auto visit_v = [&rng, &d, amplitude](auto & v)
	{
		for (std::size_t i = 0; i < v.dimension(); ++i)
		{
			v[i] = d(rng) * amplitude;
		}
	};

	visit_m(m1);
//	visit_m(m2);
//	visit_m(m3);
	visit_v(t1);
//	visit_v(t2);
//	visit_v(t3);
}

/*
Eigen::VectorXf controller::to_eigen() const
{
	Eigen::VectorXf vec;
	vec.setZero(param_count);
	auto p = std::copy(m1.coords, m1.coords + inputs * outputs, vec.data());
	std::copy(t1.coords, t1.coords + outputs, p);
	return vec;
}

void controller::from_eigen(Eigen::VectorXf const & v)
{
	if (v.size() != param_count)
		throw std::runtime_error("Wrong data size");

	std::copy(v.data(), v.data() + inputs * outputs, m1.coords);
	std::copy(v.data() + inputs * outputs, v.data() + (inputs + 1) * outputs, t1.coords);
}
*/

template <typename RNG>
void controller::mutate(RNG && rng, float amplitude)
{
	random::normal_distribution<float> d{};

	auto visit_m = [&rng, &d, amplitude](auto & m)
	{
		for (std::size_t i = 0; i < m.rows(); ++i)
		{
			for (std::size_t j = 0; j < m.columns(); ++j)
			{
				m[i][j] += d(rng) * amplitude;
			}
		}
	};

	auto visit_v = [&rng, &d, amplitude](auto & v)
	{
		for (std::size_t i = 0; i < v.dimension(); ++i)
		{
			v[i] += d(rng) * amplitude;
		}
	};

	visit_m(m1);
//	visit_m(m2);
//	visit_m(m3);
	visit_v(t1);
//	visit_v(t2);
//	visit_v(t3);
}

void controller::reset()
{}

math::vector<float, controller::outputs> controller::apply(system const & sys) const
{
	if (sys.bones.size() * 7 != inputs)
		throw std::runtime_error(util::to_string("Wrong number of inputs: should be ", sys.bones.size() * 7));
	if (sys.joints.size() != outputs)
		throw std::runtime_error(util::to_string("Wrong number of outputs: should be ", sys.joints.size()));

	math::vector<float, inputs> input;
	for (std::size_t i = 0; i < inputs / 7; ++i)
	{
		input[7 * i + 0] = sys.bones[i].position[0] - sys.bones[0].position[0];
		input[7 * i + 1] = sys.bones[i].position[1];
		input[7 * i + 2] = sys.bones[i].direction[0];
		input[7 * i + 3] = sys.bones[i].direction[1];
		input[7 * i + 4] = sys.bones[i].velocity[0];
		input[7 * i + 5] = sys.bones[i].velocity[1];
		input[7 * i + 6] = sys.bones[i].angular_velocity;
	}

	auto r1 = activation(m1 * input + t1);
//	auto r2 = activation(m2 * r1 + t2);
//	auto r3 = activation(m3 * r2 + t3);

	for (std::size_t i = 0; i < sys.joints.size(); ++i)
//		r3[i] *= 0.5f * (sys.bones[sys.joints[i].i0].mass + sys.bones[sys.joints[i].i1].mass);
		r1[i] *= 0.5f * (sys.bones[sys.joints[i].i0].mass + sys.bones[sys.joints[i].i1].mass);

	return r1 * max_output;
}

template <typename T>
void controller::read(std::istream & is, T & x)
{
	is.read(reinterpret_cast<char *>(&x), sizeof(x));
}

template <typename T>
void controller::write(std::ostream & os, T const & x)
{
	os.write(reinterpret_cast<char const *>(&x), sizeof(x));
}

void controller::read(std::istream & is)
{
	read(is, m1);
	read(is, t1);
//	read(is, m2);
//	read(is, t2);
//	read(is, m3);
//	read(is, t3);
}

void controller::write(std::ostream & os) const
{
	write(os, m1);
	write(os, t1);
//	write(os, m2);
//	write(os, t2);
//	write(os, m3);
//	write(os, t3);
}

float controller::activation(float x)
{
	return 2.f / (1.f + std::exp(- 2.f * x)) - 1.f;
}

template <std::size_t N>
math::vector<float, N> controller::activation(math::vector<float, N> v)
{
	for (std::size_t i = 0; i < N; ++i)
		v[i] = activation(v[i]);
	return v;
}

controller lerp(controller const & c1, controller const & c2, float t)
{
	controller c;
	c.m1 = math::lerp(c1.m1, c2.m1, t);
//	c.m2 = math::lerp(c1.m2, c2.m2, t);
//	c.m3 = math::lerp(c1.m3, c2.m3, t);
	c.t1 = math::lerp(c1.t1, c2.t1, t);
//	c.t2 = math::lerp(c1.t2, c2.t2, t);
//	c.t3 = math::lerp(c1.t3, c2.t3, t);
	c.generation = std::max(c1.generation, c2.generation) + 1;
	return c;
}

struct animation_2d_app
	: app::application_base
{
	animation_2d_app(options const &, context const &);

	void on_event(app::resize_event const & event) override;
	void on_event(app::mouse_wheel_event const & event) override;

	void on_event(app::key_event const & event) override;

	void update() override;
	void present() override;

	void update_camera();

	math::box<float, 2> view_bbox;
	bool centered = false;

	gfx::painter painter;

	util::clock<> frame_clock;
	util::clock<> test_clock;

	system physics;

	std::optional<std::size_t> selected;
	float selected_s = 0.f;

	random::generator rng{random::device{}};

	async::threadpool bg{"bg"};

	enum class mode
	{
		train,
		test,
	} mode = mode::train;

	application::context context;

	std::vector<controller> population;
	std::size_t const population_size = 1024;
	std::size_t const max_train_frames = 10.f / physics.dt;
	std::size_t const max_train_variations = 1;
	float const position_variation_amplitude = 0.f;
	float const angle_variation_amplitude = 0.f;

	std::size_t train_iterations = 0;
	std::size_t const max_train_iterations = 1024*8;
	float const initial_variance = 10.f;
	static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + math::lerp(0.f, -2.f, t)); };

	//Eigen::VectorXf mean;
	//Eigen::MatrixXf covariance;

	float best_score = 0.f;
	bool const warm_start = false;
	bool const enable_testing = true;
	bool testing_control = true;
	std::string const cache_location = "./runner";

	std::size_t test_id = 0;
	std::vector<float> test_speeds;

	void reset_state(system & sys) const;

	float eval_score(controller const & c, random::generator rng) const;
	void do_train();
	void do_optimize();
	void do_test();
};

animation_2d_app::animation_2d_app(options const &, application::context const & context)
	: context(context)
{
	view_bbox[1] = {-1.f, 15.f};

	context.vsync(false);
}

void animation_2d_app::on_event(app::resize_event const & event)
{
	app::application_base::on_event(event);

	update_camera();
}

void animation_2d_app::on_event(app::mouse_wheel_event const & event)
{
	float p = std::pow(0.8f, event.delta);
	view_bbox[1].max *= p;

	update_camera();
}

void animation_2d_app::update_camera()
{
	float ratio = static_cast<float>(state().size[0]) / state().size[1];

	float cx = 0.f;
	if (centered)
	{
		float m = 0.f;
		for (auto const & b : physics.bones)
		{
			cx += b.position[0] * b.mass;
			m += b.mass;
		}
		cx /= m;
	}
	else
	{
		cx = ratio * view_bbox[1].length() / 2.f;
	}

	view_bbox[0] = math::expand(math::interval<float>::singleton(cx), ratio * view_bbox[1].length() / 2.f);
}

void animation_2d_app::on_event(app::key_event const & event)
{
	if (event.down && event.key == app::keycode::C)
	{
		centered = !centered;
		update_camera();
	}

	if (event.down && event.key == app::keycode::P)
	{
		testing_control = !testing_control;
	}

	if (mode == mode::train)
	{
		if (event.down && event.key == app::keycode::S)
		{
			train_iterations = max_train_iterations;
		}
	}
	if (mode == mode::test)
	{
		bool reset = false;
		if (event.down && event.key == app::keycode::LEFT)
		{
			reset = true;
			test_id = (test_id + population.size() - 1) % population.size();
		}
		if (event.down && event.key == app::keycode::RIGHT)
		{
			reset = true;
			test_id = (test_id + 1) % population.size();
		}

		if (reset)
		{
			reset_state(physics);
			test_clock.restart();

			float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
			float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
			float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);

			math::plane_rotation<float, 2> rot{0, 1, angle};

			float miny = 0.f;
			for (auto & b : physics.bones)
			{
				b.position = rot(b.position) + math::vector{shiftx, shifty};
				b.direction = rot(b.direction);
				miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
			}
			for (auto & b : physics.bones)
				b.position[1] -= miny;

			population[test_id].reset();
			test_speeds.clear();
		}
	}
}

void animation_2d_app::update()
{
	if (mode == mode::train)
	{
		do_train();
		if (train_iterations >= max_train_iterations)
		{
			mode = mode::test;
			frame_clock.restart();
			test_clock.restart();
			reset_state(physics);

			std::ofstream os(cache_location, std::ios::binary);
			population[0].write(os);

			population[0].reset();

			context.vsync(true);
		}
	}
	else if (mode == mode::test)
	{
		if (!enable_testing)
		{
			stop();
			return;
		}

		do_test();
	}
}

void animation_2d_app::reset_state(system & sys) const
{
	sys.bones.clear();
	sys.joints.clear();

/*
	// Jumper
	sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{0.25f, 0.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
	sys.bones.push_back(bone{{0.75f, 1.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
	sys.joints.push_back(joint{0, 1, -1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
//*/

/*
	// Dachshund
	float s = 0.5f;
	float h = 0.3f;
	sys.bones.push_back(bone{{  -s, h*0.5f}, {0.f,  1.f}, {0.f, 0.f}, 0.f,   h, 0.25f});
	sys.bones.push_back(bone{{  -s, h*1.5f}, {0.f,  1.f}, {0.f, 0.f}, 0.f,   h, 0.25f});
	sys.bones.push_back(bone{{ 0.f, h*2.f }, {1.f,  0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
	sys.bones.push_back(bone{{   s, h*1.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f,   h, 0.25f});
	sys.bones.push_back(bone{{   s, h*0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f,   h, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(  0.f), math::rad(45.f)}});
	sys.joints.push_back(joint{1, 2, 1.f,   -s, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{2, 3,   s, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(  0.f), math::rad(45.f)}});
//*/

/*
	// Cube
	sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
//*/

/*
	// Cube with tiny legs
	sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
	sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
//*/

/*
	// Roller
	int n = 2;
	for (int i = 0; i < n; ++i)
		sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{n + 0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{n + 0.25f, std::sqrt(3.f) * 0.75f}, {-0.5f, std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	for (int i = 0; i < n; ++i)
		sys.bones.push_back(bone{{n - i - 0.5f, std::sqrt(3.f)}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.75f}, {-0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});

	for (int i = 0; i < 2 * n + 4; ++i)
		sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, math::interval<float>{math::rad(0.f), math::rad(60.f)}});
//*/

/*
	// Worm
	int n = 5;
	for (int i = 0; i < n; ++i)
		sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	for (int i = 0; i + 1 < n; ++i)
		sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
//*/


	// Caterpillar
	int n = 8;
	for (int i = 0; i < n; ++i)
		sys.bones.push_back(bone{{i + 0.5f, 0.5f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	for (int i = 0; i < n; ++i)
	{
		sys.bones.push_back(bone{{i + 0.25f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
		sys.bones.push_back(bone{{i + 0.25f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	}

	for (int i = 0; i + 1 < n; ++i)
		sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
	for (int i = 0; i < n; ++i)
	{
		sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
		sys.joints.push_back(joint{i, n + 2 * i + 1,  0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
	}
//*/

/*
	// Runner-1
	sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{0.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{1.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.bones.push_back(bone{{2.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(-15.f), math::rad( 15.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
//*/

/*
	// Runner: dog
	sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 0.f, 1.f  }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(  0.f), math::rad(45.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(  0.f), math::rad(45.f)}});
//*/

/*
	// Runner: goat
	sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 0.f, 1.f  }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad(  0.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad(  0.f)}});
//*/

/*
	// Runner: left
	sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 0.f, 1.f  }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad(  0.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(  0.f), math::rad( 45.f)}});
//*/

/*
	// Runner: right
	sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 0.f, 1.f  }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
	sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(  0.f), math::rad( 45.f)}});
	sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
	sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad(  0.f)}});
//*/

	float const bone_density = 1.f;
	for (auto & b : sys.bones)
	{
		b.position[1] += 0.125f;
		b.direction = math::normalized(b.direction);
		b.mass = b.length * b.width * bone_density;
		b.inertia = b.mass * (b.length * b.length + b.width * b.width) / 12.f;
	}
}

float animation_2d_app::eval_score(controller const & c, random::generator rng) const
{
	system physics;
	std::optional<system::selection> sel;

	float score = 0.f;

	for (std::size_t variation = 0; variation < max_train_variations; ++variation)
	{
		reset_state(physics);

		float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
		float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
		float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);

		math::plane_rotation<float, 2> rot{0, 1, angle};

		float miny = 0.f;
		for (auto & b : physics.bones)
		{
			b.position = rot(b.position) + math::vector{shiftx, shifty};
			b.direction = rot(b.direction);
			miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
		}
		for (auto & b : physics.bones)
			b.position[1] -= miny;

		float const dt = physics.dt;

		bool failure = false;
		float cur_score = 0.f;
		float energy = 0.f;
		float reward = 0.f;

		static std::vector<std::pair<std::size_t, float>> hit_floor_points =
		{
//			{2,  1.f},
//			{2, -1.f},
//			{2,  1.f},
//			{3, -1.f},
//			{4, -1.f},
//			{5, -1.f},
		};

		std::size_t train_frames = max_train_frames * 1.0f;

		float time = 0.f;

		std::vector<float> switch_times;
		for (int i = 0; i < 10; ++i)
			switch_times.push_back(random::uniform_distribution<float>{0.f, train_frames * dt}(rng));
		std::sort(switch_times.begin(), switch_times.end());

		for (std::size_t frame = 0; frame < train_frames; ++frame)
		{
			bool moving = false;

			for (auto t : switch_times)
			{
				if (time < t) break;
				moving = !moving;
			}

			physics.advance(dt, sel, [&c, &energy, dt](system const & physics){
				std::vector<float> torque(physics.joints.size(), 0.f);
				auto ctrl = c.apply(physics);
				for (std::size_t i = 0; i < ctrl.dimension(); ++i)
					torque[i] = ctrl[i];
				energy += math::length(ctrl) * dt;
				return torque;
			});

			for (auto f : hit_floor_points)
			{
				if (f.first >= physics.bones.size()) break;

				auto const & b = physics.bones[f.first];

				auto const p = b.position + f.second * b.direction * b.length / 2.f;

				if (p[1] <= b.width / 2.f)
				{
					failure = true;
					break;
				}
			}

//			penalty += math::sqr((1.f - physics.bones[2].direction[0]) * dt);
//			float const target_speed = 2.f;
//			penalty += math::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt;
//			penalty -= physics.bones[2].velocity[0] * dt / physics.bones;

			auto cm_vel = math::vector<float, 2>::zero();
			float mass = 0.f;
			for (auto const & b : physics.bones)
			{
				cm_vel += b.velocity * b.mass;
				mass += b.mass;
			}
			cm_vel /= mass;

			reward += (cm_vel[0]) * dt;

//			reward -= energy * 0.1f;

			if (failure)
			{
				cur_score = 0.f;
				break;
			}

			if (frame + 1 == max_train_frames)
			{
				cur_score = reward / max_train_variations;
			}

			time += dt;
		}

		(void)failure;

		score += cur_score;
	}

	return score;
}

// Old evolutionary training implementation
void animation_2d_app::do_train()
{
	if (population.empty())
	{
		population.resize(population_size);
		std::ifstream is(cache_location, std::ios::binary);
		if (warm_start && is)
		{
			controller w;
			w.read(is);
			for (auto & c : population)
			{
				c = w;
			}
		}
		else
		{
			for (auto & c : population)
				c.randomize(rng, initial_variance);
		}
	}
	else
	{
		std::size_t const preserved = population.size() / 16;
		std::size_t const cross_parents = population.size() / 8;
		std::size_t const cross_new = population.size() / 2;

		std::vector<controller> new_population(population.size());

		for (std::size_t i = 0; i < preserved; ++i)
		{
			new_population[i] = population[i];
		}

		float const mu = mutation_amplitude((train_iterations * 1.f) / max_train_iterations);

		for (std::size_t i = preserved; i < preserved + cross_new; ++i)
		{
			auto p1 = random::uniform_distribution<std::size_t>{0, cross_parents - 1}(rng);
			auto p2 = random::uniform_distribution<std::size_t>{0, cross_parents - 1}(rng);

			float t = random::uniform_distribution<float>{}(rng);
			new_population[i] = lerp(population[p1], population[p2], t);
			new_population[i].mutate(rng, mu);
		}

		for (std::size_t i = preserved + cross_new; i < population.size(); ++i)
			new_population[i].randomize(rng, initial_variance);

		population = std::move(new_population);
	}

	//(void)&controller::to_eigen;
	//(void)&controller::from_eigen;

	std::vector<std::pair<float, std::size_t>> scores(population.size());
	std::vector<async::future<void>> futures;
	for (std::size_t i = 0; i < population.size(); ++i)
	{
		futures.push_back(bg.dispatch([&, i, rng = rng]() mutable {
			scores[i] = {eval_score(population[i], rng), i};
		}));
	}
	bg.wait_all(futures.begin(), futures.end()).get();

	std::sort(scores.begin(), scores.end(), [](auto const & p1, auto const & p2){ return p1.first > p2.first; });

	std::vector<controller> new_population(population.size());
	for (std::size_t i = 0; i < population.size(); ++i)
	{
		new_population[i] = population[scores[i].second];
	}
	population = std::move(new_population);
	++train_iterations;

	best_score = scores.front().first;
}


/*
// Covariance matrix adaptation
void animation_2d_app::do_train()
{
	if (population.empty())
	{
		population.resize(population_size);
		std::ifstream is(cache_location, std::ios::binary);
		if (warm_start && is)
		{
			controller w;
			w.read(is);
			for (auto & c : population)
			{
				c = w;
				c.mutate(rng, initial_variance);
			}
		}
		else
		{
			for (auto & c : population)
				c.randomize(rng, initial_variance);
		}

		mean.setZero(controller::param_count);
		for (auto const & c : population)
		{
			Eigen::VectorXf v = c.to_eigen();
//			mean += v;
		}
		mean /= static_cast<float>(population.size());

		covariance.setZero(controller::param_count, controller::param_count);
		for (auto const & c : population)
		{
			Eigen::VectorXf const v = c.to_eigen() - mean;
			covariance += v * v.transpose();
		}
		covariance /= static_cast<float>(population.size() - 1);
	}
	else
	{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
		auto ldlt = covariance.ldlt();
		Eigen::MatrixXf const l = ldlt.matrixL();
		Eigen::VectorXf const d = ldlt.vectorD();

		random::normal_distribution<float> n;

		Eigen::VectorXf v(controller::param_count);

		for (std::size_t i = 0; i < population.size(); ++i)
		{
			for (std::size_t j = 0; j < controller::param_count; ++j)
				v[j] = n(rng);

			for (std::size_t j = 0; j < controller::param_count; ++j)
			{
//				if (d[j] < 0.f) throw std::runtime_error(util::to_string("Covariance matrix is not positive-semidefinite: ", d[j]));
				v[j] *= std::sqrt(std::max(d[j], 0.f));
			}

			population[i].from_eigen(l * v + mean);
		}
#pragma GCC diagnostic pop
	}

	std::vector<std::pair<float, std::size_t>> scores(population.size());
	std::vector<async::future<void>> futures(population.size());
	for (std::size_t i = 0; i < population.size(); ++i)
	{
		futures[i] = bg.dispatch([&, i, rng = rng]() mutable {
			scores[i] = {eval_score(population[i], rng), i};
		});
	}
	bg.wait_all(futures.begin(), futures.end()).get();

	std::sort(scores.begin(), scores.end(), [](auto const & p1, auto const & p2){ return p1.first > p2.first; });

	std::size_t const selected = population.size() / 4;

	auto weight = [selected](std::size_t rank, float){
		return static_cast<float>(selected - rank);
	};

	float sum_weights = 0.f;
	for (std::size_t i = 0; i < selected; ++i)
		sum_weights += weight(scores[i].second, scores[i].first);

	mean.setZero(controller::param_count);
	for (std::size_t i = 0; i < selected; ++i)
	{
		float w = weight(scores[i].second, scores[i].first) / sum_weights;
		mean += w * population[scores[i].second].to_eigen();
	}

	Eigen::MatrixXf new_covariance;
	new_covariance.setZero(controller::param_count, controller::param_count);
	for (std::size_t i = 0; i < selected; ++i)
	{
		float w = weight(scores[i].second, scores[i].first) / sum_weights;
		Eigen::VectorXf const v = population[scores[i].second].to_eigen() - mean;
		new_covariance += w * v * v.transpose();
	}
//	new_covariance *= static_cast<float>(population.size() - 1) / static_cast<float>(population.size());

	float const mu = 0.0001f;

	covariance = covariance * (1.f - mu) + new_covariance * mu;

	best_score = scores.front().first;

	++train_iterations;

	(void)lerp;
}
*/

void animation_2d_app::do_test()
{
	math::point<float, 2> mouse = view_bbox.corner(state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]);

	if (state().mouse_button_down.contains(app::mouse_button::left))
	{
		selected = std::nullopt;

		{
			float selected_distance = std::numeric_limits<float>::infinity();

			for (std::size_t i = 0; i < physics.bones.size(); ++i)
			{
				auto & b = physics.bones[i];

				auto p0 = b.position - b.direction * b.length / 2.f;
				auto p1 = b.position + b.direction * b.length / 2.f;
				auto r = p1 - p0;

				auto d = mouse - p0;

				float t = math::dot(d, r) / math::dot(r, r);

				float distance;

				if (0.f <= t && t <= 1.f)
				{
					distance = math::length(d - r * t);
				}
				else
				{
					float d0 = math::distance(p0, mouse);
					float d1 = math::distance(p1, mouse);

					if (d0 < d1)
					{
						t = 0.f;
					}
					else
					{
						t = 1.f;
					}

					distance = std::min(d0, d1);
				}

				if (distance < b.width / 2.f && distance < selected_distance)
				{
					selected_distance = distance;
					selected = i;
					selected_s = 2.f * t - 1.f;
				}
			}
		}
	}

	std::optional<system::selection> sel;
	if (selected && state().mouse_button_down.contains(app::mouse_button::left))
	{
		auto const & b = physics.bones[*selected];
		auto delta = b.position + b.direction * selected_s * b.length / 2.f - mouse;
		sel = system::selection{*selected, selected_s, delta};
	}

	if (testing_control)
	{
		physics.advance(frame_clock.restart().count(), sel, [this](system const & physics){
			std::vector<float> torque(physics.joints.size());
			auto ctrl = population[test_id].apply(physics);
			for (std::size_t i = 0; i < ctrl.dimension(); ++i)
				torque[i] = ctrl[i];
			return torque;
		});
	}
	else
	{
		physics.advance(frame_clock.restart().count(), sel, [](system const & physics){
			return std::vector<float>(physics.joints.size(), 0.f);
		});
	}

	{
		auto cm_vel = math::vector<float, 2>::zero();
		float mass = 0.f;
		for (auto const & b : physics.bones)
		{
			cm_vel += b.velocity * b.mass;
			mass += b.mass;
		}
		cm_vel /= mass;

		test_speeds.push_back(physics.bones[2].velocity[0]);
	}
}

void animation_2d_app::present()
{
	update_camera();

	gl::ClearColor(0.8f, 0.8f, 0.8f, 1.f);
	gl::Clear(gl::COLOR_BUFFER_BIT);

	{
		float ground_width = 0.1f;
		painter.line({view_bbox[0].min, -ground_width/2.f}, {view_bbox[0].max, -ground_width/2.f}, ground_width, gfx::black);
	}

	for (auto const & b : physics.bones)
	{
		auto c = gfx::dark(gfx::blue).as_color_rgba();
		painter.line(b.position - b.direction * b.length / 2.f, b.position + b.direction * b.length / 2.f, b.width, c);
	}

	if (selected)
	{
		auto c = gfx::blue.as_color_rgba();

		auto const & b = physics.bones[*selected];
		painter.line(b.position - b.direction * b.length / 2.f, b.position + b.direction * b.length / 2.f, b.width, c);
	}

	for (auto const & b : physics.bones)
	{
		for (float s : {-1.f, 1.f})
		{
			auto c = gfx::dark(gfx::red).as_color_rgba();
			auto p = b.position + s * b.direction * b.length / 2.f;
			if (p[1] < b.width / 2.f)
				c = gfx::yellow.as_color_rgba();
			painter.circle(p, b.width * 0.35f, c);
		}
	}

	painter.render(math::orthographic_camera{view_bbox}.transform());

	float avg_speed = 0.f;

	if (mode == mode::test)
	{
		for (float s : test_speeds)
			avg_speed += s / test_speeds.size();

		int margin = 40;
		float const step = 1.f;

		int max_frames_shown = (state().size[0] - 2 * margin) / step;
		int start = std::max(0, static_cast<int>(test_speeds.size() - max_frames_shown));

		for (std::size_t i = start; i + 1 < test_speeds.size(); ++i)
		{
			float const scale = 2.f;
			math::point p0{40.f + (i - start    ) * step, 180.f - test_speeds[i    ] * scale};
			math::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale};
			painter.line(p0, p1, 2.f, gfx::red, false);
		}
	}

	{
		gfx::painter::text_options opts;
		opts.c = gfx::black;
		opts.x = gfx::painter::x_align::left;
		opts.y = gfx::painter::y_align::top;
		opts.scale = {2.f, 2.f};
		painter.text({40.f, 40.f}, util::to_string(train_iterations, "/", max_train_iterations), opts);
		painter.text({40.f, 64.f}, util::to_string("Best score: ", std::setprecision(10), best_score), opts);
		painter.text({40.f, 88.f}, util::to_string("Model: ", test_id, "/", population.size(), ", gen ", population[test_id].generation), opts);
		painter.text({40.f, 112.f}, util::to_string(util::pretty(test_clock.duration(), std::chrono::milliseconds{1})), opts);
		painter.text({40.f, 136.f}, util::to_string("View height: ", view_bbox[1].length()), opts);

//		if (mode == mode::test && !test_speeds.empty()) painter.text({40.f, 136.f}, util::to_string("Speed: ", test_speeds.back()), opts);
	}

	painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
}

}

namespace psemek::app
{

	std::unique_ptr<application::factory> make_application_factory()
	{
		return default_application_factory<animation_2d_app>({.name = "Animation 2D example"});
	}

}
