/* SPDX-FileCopyrightText: 2022 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

#pragma once

/** \file
 * \ingroup bli
 */

#include <array>
#include <ostream>
#include <type_traits>

#include "BLI_build_config.h"
#include "BLI_hash.hh"
#include "BLI_math_vector_swizzle.hh"
#include "BLI_math_vector_unroll.hh"
#include "BLI_unique_hash.hh"
#include "BLI_utildefines.h"

namespace blender {

template<typename T, int Size, bool is_trivial_type> struct vec_struct_base {
  std::array<T, Size> values;
};

template<typename T> struct vec_struct_base<T, 1, false> {
  T x;
};

template<typename T> struct vec_struct_base<T, 2, false> : VecSwizzleFunc<T, 2> {
  T x, y;
};

template<typename T> struct vec_struct_base<T, 3, false> : VecSwizzleFunc<T, 3> {
  T x, y, z;
};

template<typename T> struct vec_struct_base<T, 4, false> : VecSwizzleFunc<T, 4> {
  T x, y, z, w;
};

/**
 * Avoid warning caused by anonymous struct in unions.
 */
#if COMPILER_GCC
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wpedantic"
#elif COMPILER_CLANG
#  pragma clang diagnostic push
#  pragma clang diagnostic ignored "-Wgnu-anonymous-struct"
#  pragma clang diagnostic ignored "-Wnested-anon-types"
#elif COMPILER_MSVC
#  pragma warning(push)
#  pragma warning(disable : 4201)  // nonstandard extension used : nameless struct/union
#endif

template<typename T> struct vec_struct_base<T, 1, true> {
  union {
#ifndef NDEBUG
    /* Easier to read inside a debugger. */
    std::array<T, 1> debug_values_;
#endif
    T x;
    /* Nesting to avoid readability issues inside a debugger. */
    X_SWIZZLES;
  };
};

template<typename T> struct vec_struct_base<T, 2, true> {
  union {
#ifndef NDEBUG
    /* Easier to read inside a debugger. */
    std::array<T, 2> debug_values_;
#endif
    struct {
      T x;
      union {
        struct {
          /* This still needs to be wrapped in a struct to avoid a MSVC 16.5+ bug. */
          T y;
        };
        Y_SWIZZLES;
      };
    };
    /* Nesting to avoid readability issues inside a debugger. */
    union {
      X_SWIZZLES;
      XY_SWIZZLES;
    };
  };
};

template<typename T> struct vec_struct_base<T, 3, true> {
  union {
#ifndef NDEBUG
    /* Easier to read inside a debugger. */
    std::array<T, 3> debug_values_;
#endif
    struct {
      T x;
      union {
        struct {
          T y;
          union {
            struct {
              /* This still needs to be wrapped in a struct to avoid a MSVC 16.5+ bug. */
              T z;
            };
            Z_SWIZZLES;
          };
        };
        Y_SWIZZLES;
        YZ_SWIZZLES;
      };
    };
    /* Nesting to avoid readability issues inside a debugger. */
    union {
      X_SWIZZLES;
      XY_SWIZZLES;
      XYZ_SWIZZLES;
    };
  };
};

template<typename T> struct vec_struct_base<T, 4, true> {
  union {
#ifndef NDEBUG
    /* Useful for debugging. */
    std::array<T, 4> debug_values_;
#endif
    struct {
      T x;
      union {
        struct {
          T y;
          union {
            struct {
              T z;
              union {
                struct {
                  /* This still needs to be wrapped in a struct to avoid a MSVC 16.5+ bug. */
                  T w;
                };
                W_SWIZZLES;
              };
            };
            Z_SWIZZLES;
            ZW_SWIZZLES;
          };
        };
        Y_SWIZZLES;
        YZ_SWIZZLES;
        YZW_SWIZZLES;
      };
    };
    /* Nesting to avoid readability issues inside a debugger. */
    union {
      X_SWIZZLES;
      XY_SWIZZLES;
      XYZ_SWIZZLES;
      XYZW_SWIZZLES;
    };
  };
};

#if COMPILER_GCC
#  pragma GCC diagnostic pop
#elif COMPILER_CLANG
#  pragma clang diagnostic pop
#elif COMPILER_MSVC
#  pragma warning(pop)
#endif

namespace math {

template<typename T> constexpr uint64_t vector_hash(const T &vec)
{
  BLI_STATIC_ASSERT(T::type_length <= 4, "Longer types need to implement vector_hash themself.");
  if constexpr (T::type_length == 1) {
    return get_default_hash(vec.x);
  }
  else if constexpr (T::type_length == 2) {
    return get_default_hash(vec.x, vec.y);
  }
  else if constexpr (T::type_length == 3) {
    return get_default_hash(vec.x, vec.y, vec.z);
  }
  else if constexpr (T::type_length == 4) {
    return get_default_hash(vec.x, vec.y, vec.z, vec.w);
  }
  else {
    BLI_assert_unreachable_static();
  }
}

}  // namespace math

template<typename T, int Size>
struct VecBase : public vec_struct_base<T, Size, std::is_trivial_v<T>> {

  BLI_STATIC_ASSERT(alignof(T) <= sizeof(T),
                    "VecBase is not compatible with aligned type for now.");

  static constexpr int type_length = Size;

  using base_type = T;

  VecBase() = default;

#if defined(__GNUC__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wdeprecated-copy"
#endif

  /** Make assignment on swizzle result an error. */
  VecBase &operator=(const VecBase &) & = default;

#if defined(__GNUC__)
#  pragma GCC diagnostic pop
#endif

  explicit VecBase(T value)
    requires(Size > 1)
  {
    for (int i = 0; i < Size; i++) {
      (*this)[i] = value;
    }
  }

  template<typename U>
  explicit VecBase(U value)
    requires(std::is_convertible_v<U, T>)
      : VecBase(T(value))
  {
  }

  constexpr VecBase(T _x)
    requires(Size == 1)
  {
    this->x = _x;
  }

  constexpr VecBase(T _x, T _y)
    requires(Size == 2)
  {
    this->x = _x;
    this->y = _y;
  }

  constexpr VecBase(T _x, T _y, T _z)
    requires(Size == 3)
  {
    this->x = _x;
    this->y = _y;
    this->z = _z;
  }

  constexpr VecBase(T _x, T _y, T _z, T _w)
    requires(Size == 4)
  {
    this->x = _x;
    this->y = _y;
    this->z = _z;
    this->w = _w;
  }

  /** Mixed scalar-vector constructors. */

  template<typename U>
  constexpr VecBase(const VecBase<U, 2> &xy, T z)
    requires(Size == 3)
      : VecBase(T(xy.x), T(xy.y), z)
  {
  }

  template<typename U>
  constexpr VecBase(T x, const VecBase<U, 2> &yz)
    requires(Size == 3)
      : VecBase(x, T(yz.x), T(yz.y))
  {
  }

  template<typename U>
  VecBase(VecBase<U, 3> xyz, T w)
    requires(Size == 4)
      : VecBase(T(xyz.x), T(xyz.y), T(xyz.z), T(w))
  {
  }

  template<typename U>
  VecBase(T x, VecBase<U, 3> yzw)
    requires(Size == 4)
      : VecBase(T(x), T(yzw.x), T(yzw.y), T(yzw.z))
  {
  }

  template<typename U, typename V>
  VecBase(VecBase<U, 2> xy, VecBase<V, 2> zw)
    requires(Size == 4)
      : VecBase(T(xy.x), T(xy.y), T(zw.x), T(zw.y))
  {
  }

  template<typename U>
  VecBase(VecBase<U, 2> xy, T z, T w)
    requires(Size == 4)
      : VecBase(T(xy.x), T(xy.y), T(z), T(w))
  {
  }

  template<typename U>
  VecBase(T x, VecBase<U, 2> yz, T w)
    requires(Size == 4)
      : VecBase(T(x), T(yz.x), T(yz.y), T(w))
  {
  }

  template<typename U>
  VecBase(T x, T y, VecBase<U, 2> zw)
    requires(Size == 4)
      : VecBase(T(x), T(y), T(zw.x), T(zw.y))
  {
  }

  /**
   * Prevent up-cast of dimensions (creating a bigger vector initialized with data
   * from a smaller one) by deleting all copy constructors accepting smaller vectors
   * as source.
   */
  template<typename U, int OtherSize>
  VecBase(const VecBase<U, OtherSize> &other)
    requires(OtherSize < Size)
  = delete;

  /** Masking. */

  template<typename U, int OtherSize>
  explicit VecBase(const VecBase<U, OtherSize> &other)
    requires(OtherSize > Size)
  {
    for (int i = 0; i < Size; i++) {
      (*this)[i] = T(other[i]);
    }
  }

  /** Conversion from pointers (from C-style vectors). */

  /* False positive warning with GCC: it sees array access like [3] but
   * input is only a 3-element array. But it fails to realize that the
   * [3] access is within "if constexpr (Size == 4)" check already. */
#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Warray-bounds"
#endif

  VecBase(const T *ptr)
  {
    BLI_UNROLL_MATH_VEC_OP_INIT_INDEX(ptr);
  }

  template<typename U>
  explicit VecBase(const U *ptr)
    requires(std::is_convertible_v<U, T>)
  {
    BLI_UNROLL_MATH_VEC_OP_INIT_INDEX(ptr);
  }

  VecBase(const T (*ptr)[Size]) : VecBase(static_cast<const T *>(ptr[0])) {}

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

  /** Conversion from other vector types. */

  template<typename U> explicit VecBase(const VecBase<U, Size> &vec)
  {
    BLI_UNROLL_MATH_VEC_OP_INIT_VECTOR(vec);
  }

  /** C-style pointer dereference. */

  operator const T *() const
  {
    return reinterpret_cast<const T *>(this);
  }

  operator T *()
  {
    return reinterpret_cast<T *>(this);
  }

  /** Array access. */

  const T &operator[](int index) const
  {
    BLI_assert(index >= 0);
    BLI_assert(index < Size);
    return reinterpret_cast<const T *>(this)[index];
  }

  T &operator[](int index)
  {
    BLI_assert(index >= 0);
    BLI_assert(index < Size);
    return reinterpret_cast<T *>(this)[index];
  }

  /** Arithmetic operators. */

  friend VecBase operator+(const VecBase &a, const VecBase &b)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(+, a, b);
  }

  friend VecBase operator+(const VecBase &a, const T &b)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(+, a, b);
  }

  friend VecBase operator+(const T &a, const VecBase &b)
  {
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(+, a, b);
  }

  VecBase &operator+=(const VecBase &b) &
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(+=, b);
  }

  VecBase &operator+=(const T &b) &
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(+=, b);
  }

  friend VecBase operator-(const VecBase &a)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC(-, a);
  }

  friend VecBase operator-(const VecBase &a, const VecBase &b)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(-, a, b);
  }

  friend VecBase operator-(const VecBase &a, const T &b)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(-, a, b);
  }

  friend VecBase operator-(const T &a, const VecBase &b)
  {
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(-, a, b);
  }

  VecBase &operator-=(const VecBase &b) &
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(-=, b);
  }

  VecBase &operator-=(const T &b) &
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(-=, b);
  }

  friend VecBase operator*(const VecBase &a, const VecBase &b)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(*, a, b);
  }

  template<typename FactorT> friend VecBase operator*(const VecBase &a, FactorT b)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(*, a, b);
  }

  friend VecBase operator*(T a, const VecBase &b)
  {
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(*, a, b);
  }

  VecBase &operator*=(T b) &
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(*=, b);
  }

  VecBase &operator*=(const VecBase &b) &
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(*=, b);
  }

  friend VecBase operator/(const VecBase &a, const VecBase &b)
  {
    for (int i = 0; i < Size; i++) {
      BLI_assert(b[i] != T(0));
    }
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(/, a, b);
  }

  friend VecBase operator/(const VecBase &a, T b)
  {
    BLI_assert(b != T(0));
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(/, a, b);
  }

  friend VecBase operator/(T a, const VecBase &b)
  {
    for (int i = 0; i < Size; i++) {
      BLI_assert(b[i] != T(0));
    }
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(/, a, b);
  }

  VecBase &operator/=(T b) &
  {
    BLI_assert(b != T(0));
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(/=, b);
  }

  VecBase &operator/=(const VecBase &b) &
  {
    for (int i = 0; i < Size; i++) {
      BLI_assert(b[i] != T(0));
    }
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(/=, b);
  }

  /** Binary operators. */

  friend VecBase operator&(const VecBase &a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(&, a, b);
  }

  friend VecBase operator&(const VecBase &a, T b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(&, a, b);
  }

  friend VecBase operator&(T a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(&, a, b);
  }

  VecBase &operator&=(T b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(&=, b);
  }

  VecBase &operator&=(const VecBase &b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(&=, b);
  }

  friend VecBase operator|(const VecBase &a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(|, a, b);
  }

  friend VecBase operator|(const VecBase &a, T b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(|, a, b);
  }

  friend VecBase operator|(T a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(|, a, b);
  }

  VecBase &operator|=(T b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(|=, b);
  }

  VecBase &operator|=(const VecBase &b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(|=, b);
  }

  friend VecBase operator^(const VecBase &a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(^, a, b);
  }

  friend VecBase operator^(const VecBase &a, T b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(^, a, b);
  }

  friend VecBase operator^(T a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(^, a, b);
  }

  VecBase &operator^=(T b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(^=, b);
  }

  VecBase &operator^=(const VecBase &b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(^=, b);
  }

  friend VecBase operator~(const VecBase &a)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC(~, a);
  }

  /** Bit-shift operators. */

  friend VecBase operator<<(const VecBase &a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(<<, a, b);
  }

  friend VecBase operator<<(const VecBase &a, T b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(<<, a, b);
  }

  VecBase &operator<<=(T b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(<<=, b);
  }

  VecBase &operator<<=(const VecBase &b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(<<=, b);
  }

  friend VecBase operator>>(const VecBase &a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(>>, a, b);
  }

  friend VecBase operator>>(const VecBase &a, T b)
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(>>, a, b);
  }

  VecBase &operator>>=(T b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_SCALAR(>>=, b);
  }

  VecBase &operator>>=(const VecBase &b) &
    requires(std::is_integral_v<T>)
  {
    BLI_UNROLL_MATH_VEC_OP_ASSIGN_VEC(>>=, b);
  }

  /** Modulo operators. */

  friend VecBase operator%(const VecBase &a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    for (int i = 0; i < Size; i++) {
      BLI_assert(b[i] != T(0));
    }
    BLI_UNROLL_MATH_VEC_OP_VEC_VEC(%, a, b);
  }

  friend VecBase operator%(const VecBase &a, T b)
    requires(std::is_integral_v<T>)
  {
    BLI_assert(b != 0);
    BLI_UNROLL_MATH_VEC_OP_VEC_SCALAR(%, a, b);
  }

  friend VecBase operator%(T a, const VecBase &b)
    requires(std::is_integral_v<T>)
  {
    for (int i = 0; i < Size; i++) {
      BLI_assert(b[i] != T(0));
    }
    BLI_UNROLL_MATH_VEC_OP_SCALAR_VEC(%, a, b);
  }

  /** Compare. */

  friend bool operator==(const VecBase &a, const VecBase &b)
  {
    for (int i = 0; i < Size; i++) {
      if (a[i] != b[i]) {
        return false;
      }
    }
    return true;
  }

  friend bool operator!=(const VecBase &a, const VecBase &b)
  {
    return !(a == b);
  }

  /** Misc. */

  constexpr uint64_t hash() const
  {
    return math::vector_hash(*this);
  }

  void hash_unique(UniqueHashBytes &hash) const
  {
    unroll<Size>([&](auto i) {
      const T &value = (*this)[i];
      hash_unique_default(value, hash);
    });
  }

  friend std::ostream &operator<<(std::ostream &stream, const VecBase &v)
  {
    stream << "(";
    for (int i = 0; i < Size; i++) {
      stream << v[i];
      if (i != Size - 1) {
        stream << ", ";
      }
    }
    stream << ")";
    return stream;
  }
};

namespace math {

template<typename T> struct AssertUnitEpsilon {
  /** \note Copy of BLI_ASSERT_UNIT_EPSILON_DB to avoid dragging the entire header. */
  static constexpr T value = T(0.0002);
};

}  // namespace math

using char2 = VecBase<int8_t, 2>;
using char3 = VecBase<int8_t, 3>;
using char4 = VecBase<int8_t, 4>;

using uchar2 = VecBase<uint8_t, 2>;
using uchar3 = VecBase<uint8_t, 3>;
using uchar4 = VecBase<uint8_t, 4>;

using int2 = VecBase<int32_t, 2>;
using int3 = VecBase<int32_t, 3>;
using int4 = VecBase<int32_t, 4>;

using uint2 = VecBase<uint32_t, 2>;
using uint3 = VecBase<uint32_t, 3>;
using uint4 = VecBase<uint32_t, 4>;

using short2 = VecBase<int16_t, 2>;
using short3 = VecBase<int16_t, 3>;
using short4 = VecBase<int16_t, 4>;

using ushort2 = VecBase<uint16_t, 2>;
using ushort3 = VecBase<uint16_t, 3>;
using ushort4 = VecBase<uint16_t, 4>;

using float1 = VecBase<float, 1>;
using float2 = VecBase<float, 2>;
using float3 = VecBase<float, 3>;
using float4 = VecBase<float, 4>;

using double2 = VecBase<double, 2>;
using double3 = VecBase<double, 3>;
using double4 = VecBase<double, 4>;

}  // namespace blender
