#pragma once
#include <cmath>
#include <ostream>

#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/iterator_range.hpp>

namespace {
	template <typename T>
	T clamp(const T& v, const T& lo, const T& hi) { // use std::clamp in c++17
		return std::max(lo, std::min(v, hi));
	}
}

struct Vec3 {
	Vec3() = default;
	explicit Vec3(double d) : X(d), Y(d), Z(d) {}
	Vec3(double x, double y, double z) : X(x), Y(y), Z(z) {}

	double X{0};
	double Y{0};
	double Z{0};

	const double& operator[](std::size_t idx) const {
		switch (idx)
		{
		case 0:
			return X;
		case 1:
			return Y;
		case 2:
			return Z;
		default:
			std::abort();
		}
	}

	double& operator[](std::size_t idx) {
		switch (idx)
		{
		case 0:
			return X;
		case 1:
			return Y;
		case 2:
			return Z;
		default:
			std::abort();
		}
	}

	Vec3& operator+=(Vec3 v) {
		X += v.X;
		Y += v.Y;
		Z += v.Z;
		return *this;
	}

	Vec3& operator-=(Vec3 v) {
		X -= v.X;
		Y -= v.Y;
		Z -= v.Z;
		return *this;
	}

	Vec3& operator*=(double d) {
		X *= d;
		Y *= d;
		Z *= d;
		return *this;
	}

	Vec3& operator*=(Vec3 v) {
		X *= v.X;
		Y *= v.Y;
		Z *= v.Z;
		return *this;
	}

	Vec3& operator/=(double d) {
		X /= d;
		Y /= d;
		Z /= d;
		return *this;
	}

	bool IsValid() const {
		return !std::isnan(X) && std::isfinite(X) &&
		!std::isnan(Y) && std::isfinite(Y) &&
		!std::isnan(Z) && std::isfinite(Z);
	}

	// Iterates over the Components of a Vec3
	class Vec3CompIterator : public boost::iterator_facade<
		Vec3CompIterator,
		const double,
		boost::bidirectional_traversal_tag> {
	public:
		Vec3CompIterator() : idx_(3) {}
		explicit Vec3CompIterator(const Vec3* vec) :vec_(vec) {}
	private:
		friend class boost::iterator_core_access;

		void increment() { ++idx_; }
		void decrement() { --idx_; }

		bool equal(Vec3CompIterator const& other) const { return this->idx_ == other.idx_; }
		const double& dereference() const { return (*vec_)[idx_]; }
		const Vec3 * vec_{ nullptr };
		std::size_t idx_{ 0 };
	};

	auto getComponents() const { return boost::make_iterator_range(Vec3CompIterator{ this }, Vec3CompIterator{}); }
};

inline Vec3 operator-(Vec3 v1, Vec3 v2) {
	v1 -= v2;
	return v1;
}

inline Vec3 operator+(Vec3 v1, Vec3 v2) {
	v1 += v2;
	return v1;
}

inline Vec3 operator*(Vec3 v1, Vec3 v2) {
	v1 *= v2;
	return v1;
}

inline Vec3 operator*(Vec3 v, double d) {
	v *= d;
	return v;
}

inline Vec3 operator/(Vec3 v, double d) {
	v /= d;
	return v;
}

inline bool operator<(Vec3 lhs, Vec3 rhs) {
	return lhs.X < rhs.X &&
		lhs.Y < rhs.Y &&
		lhs.Z < rhs.Z;
}
inline bool operator>(Vec3 lhs, Vec3 rhs) {
	return lhs.X > rhs.X &&
		lhs.Y > rhs.Y &&
		lhs.Z > rhs.Z;
}
inline bool operator<=(Vec3 lhs, Vec3 rhs) {
	return lhs.X <= rhs.X &&
		lhs.Y <= rhs.Y &&
		lhs.Z <= rhs.Z;
}
inline bool operator>=(Vec3 lhs, Vec3 rhs) {
	return lhs.X >= rhs.X &&
		lhs.Y >= rhs.Y &&
		lhs.Z >= rhs.Z;
}

inline double SquaredNorm(Vec3 v) { return v.X * v.X + v.Y * v.Y + v.Z * v.Z; }
inline double Norm(Vec3 v) { return std::sqrt(SquaredNorm(v)); }
inline double Distance(Vec3 v1, Vec3 v2) { return Norm(v1 - v2); }
inline double SquaredDistance(Vec3 v1, Vec3 v2) { return SquaredNorm(v1 - v2); }

inline std::ostream& operator<<(std::ostream& os, const Vec3& v) {
	os << v.X << ":" << v.Y << ":" << v.Z;
	return os;
}

inline Vec3 MinComponents(Vec3 first, Vec3 second) {
	return {
		std::min(first.X, second.X),
		std::min(first.Y, second.Y),
		std::min(first.Z, second.Z)
	};
}

inline Vec3 MaxComponents(Vec3 first, Vec3 second) {
	return {
		std::max(first.X, second.X),
		std::max(first.Y, second.Y),
		std::max(first.Z, second.Z)
	};
}

inline Vec3 ClampComponents(const Vec3 &v, const Vec3 &lo, const Vec3 &hi) {
  return {
	  clamp(v.X, lo.X, hi.X),
	  clamp(v.Y, lo.Y, hi.Y),
	  clamp(v.Z, lo.Z, hi.Z)
  };
}
