#ifndef VEC3_H
#define VEC3_H

#include <cmath>
#include <ostream>

class Vec3 {
  std::array<double, 3> e{};

public:
  constexpr Vec3() = default;
  constexpr Vec3(double x, double y, double z) noexcept : e{x, y, z} {}

  constexpr double x() const noexcept { return e[0]; }
  constexpr double y() const noexcept { return e[1]; }
  constexpr double z() const noexcept { return e[2]; }

  constexpr double &x() { return e[0]; }
  constexpr double &y() { return e[1]; }
  constexpr double &z() { return e[2]; }

  constexpr double operator[](int i) const noexcept { return e[i]; }
  constexpr double &operator[](int i) noexcept { return e[i]; }

  constexpr Vec3 operator-() const noexcept {
    return Vec3(-e[0], -e[1], -e[2]);
  }

  constexpr Vec3 &operator+=(const Vec3 &v) noexcept {
    e[0] += v.e[0];
    e[1] += v.e[1];
    e[2] += v.e[2];
    return *this;
  }

  constexpr Vec3 &operator*=(double t) noexcept {
    e[0] *= t;
    e[1] *= t;
    e[2] *= t;
    return *this;
  }

  constexpr Vec3 &operator/=(double t) noexcept { return *this *= 1.0 / t; }

  [[nodiscard]]
  constexpr double length_squared() const noexcept {
    return e[0] * e[0] + e[1] * e[1] + e[2] * e[2];
  }

  [[nodiscard]]
  double length() const noexcept {
    return std::sqrt(length_squared());
  }

  friend constexpr Vec3 operator+(Vec3 u, const Vec3 &v) noexcept {
    return u += v;
  }

  friend constexpr Vec3 operator-(Vec3 u, const Vec3 &v) noexcept {
    u.e[0] -= v.e[0];
    u.e[1] -= v.e[1];
    u.e[2] -= v.e[2];
    return u;
  }

  friend constexpr Vec3 operator*(Vec3 u, const Vec3 &v) noexcept {
    u.e[0] *= v.e[0];
    u.e[1] *= v.e[1];
    u.e[2] *= v.e[2];
    return u;
  }

  friend constexpr Vec3 operator*(double t, Vec3 v) noexcept { return v *= t; }

  friend constexpr Vec3 operator*(Vec3 v, double t) noexcept { return v *= t; }

  friend constexpr Vec3 operator/(Vec3 v, double t) noexcept { return v /= t; }

  friend constexpr double dot(const Vec3 &u, const Vec3 &v) noexcept {
    return u.e[0] * v.e[0] + u.e[1] * v.e[1] + u.e[2] * v.e[2];
  }

  friend constexpr Vec3 cross(const Vec3 &u, const Vec3 &v) noexcept {
    return Vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1],
                u.e[2] * v.e[0] - u.e[0] * v.e[2],
                u.e[0] * v.e[1] - u.e[1] * v.e[0]);
  }

  [[nodiscard]]
  friend Vec3 unit_vector(Vec3 v) noexcept {
    return v / v.length();
  }

  friend std::ostream &operator<<(std::ostream &out, const Vec3 &v) {
    return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2];
  }
};

using Point3 = Vec3;

#endif
