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

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};

	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+=(const Vec3& v) {
		X += v.X;
		Y += v.Y;
		Z += v.Z;
		return *this;
	}

	Vec3& operator-=(const 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*=(const 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);
	}

	void MaxComponents(const Vec3& other) {
		X = std::max(X, other.X);
		Y = std::max(Y, other.Y);
		Z = std::max(Z, other.Z);
	}
	void MinComponents(const Vec3& other) {
		X = std::min(X, other.X);
		Y = std::min(Y, other.Y);
		Z = std::min(Z, other.Z);
	}
	void ClampComponents(const Vec3& lo, const Vec3& hi) {
		X = clamp(X, lo.X, hi.X);
		Y = clamp(Y, lo.Y, hi.Y);
		Z = clamp(Z, lo.Z, hi.Z);
	}

};

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

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

inline Vec3 operator*(Vec3 v1, const 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<(const Vec3& lhs, const Vec3& rhs) {
	return lhs.X < rhs.X &&
		lhs.Y < rhs.Y &&
		lhs.Z < rhs.Z;
}
inline bool operator>(const Vec3& lhs, const Vec3& rhs) {
	return lhs.X > rhs.X &&
		lhs.Y > rhs.Y &&
		lhs.Z > rhs.Z;
}
inline bool operator<=(const Vec3& lhs, const Vec3& rhs) {
	return lhs.X <= rhs.X &&
		lhs.Y <= rhs.Y &&
		lhs.Z <= rhs.Z;
}
inline bool operator>=(const Vec3& lhs, const Vec3& rhs) {
	return lhs.X >= rhs.X &&
		lhs.Y >= rhs.Y &&
		lhs.Z >= rhs.Z;
}
inline bool operator==(const Vec3& lhs, const Vec3& rhs) {
	return lhs.X == rhs.X &&
		lhs.Y == rhs.Y &&
		lhs.Z == rhs.Z;
}

inline double SquaredNorm(const Vec3& v) { return v.X * v.X + v.Y * v.Y + v.Z * v.Z; }
inline double Norm(const Vec3& v) { return std::sqrt(SquaredNorm(v)); }
inline double Distance(const Vec3& v1, const Vec3& v2) { return Norm(v1 - v2); }
inline double SquaredDistance(const Vec3& v1, const 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;
}
