#pragma once

#include <psemek/math/vector.hpp>
#include <psemek/math/point.hpp>

#include <iostream>

namespace psemek::math
{

	template <typename T, std::size_t N>
	struct ray
	{
		point<T, N> origin;
		vector<T, N> direction;

		auto operator()(T const & t) const
		{
			return origin + t * direction;
		}

		ray & operator += (vector<T, N> const & v)
		{
			origin += v;
			return *this;
		}

		ray & operator -= (vector<T, N> const & v)
		{
			origin -= v;
			return *this;
		}
	};

	template <typename T, std::size_t N>
	ray(point<T, N>, vector<T, N>) -> ray<T, N>;

	template <typename T, std::size_t N>
	ray<T, N> advance(ray<T, N> r, T const & t)
	{
		r.origin = r(t);
		return r;
	}

	template <typename T, std::size_t N>
	ray<T, N> operator + (ray<T, N> const & r, vector<T, N> const & v)
	{
		auto res = r;
		res += v;
		return res;
	}

	template <typename T, std::size_t N>
	ray<T, N> operator - (ray<T, N> const & r, vector<T, N> const & v)
	{
		auto res = r;
		res -= v;
		return res;
	}

	template <typename T1, typename T, std::size_t N>
	ray<T1, N> cast(ray<T, N> const & r)
	{
		return {cast<T1>(r.origin), cast<T1>(r.direction)};
	}

	template <typename T, std::size_t N>
	std::ostream & operator << (std::ostream & os, ray<T, N> const & r)
	{
		os << '[' << r.origin << ", " << r.direction << ']';
		return os;
	}

	template <typename T, std::size_t N>
	ray<T, N> normalized(ray<T, N> r)
	{
		r.direction = normalized(r.direction);
		return r;
	}
}
