/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifdef __clang__
#  ifdef __SSE2__
#    include <xmmintrin.h>
#    define USE_SSE2 1
#  endif
#  ifdef __ARM_NEON
#    include <arm_neon.h>
#    define USE_NEON 1
#  endif
#endif

namespace glsl {

#ifdef __clang__
template <typename T, int N>
using VectorType = T __attribute__((ext_vector_type(N)));

#  define CONVERT(vector, type) __builtin_convertvector(vector, type)
#  define SHUFFLE(a, b, ...) __builtin_shufflevector(a, b, __VA_ARGS__)

template <typename T>
SI VectorType<T, 4> combine(VectorType<T, 2> a, VectorType<T, 2> b) {
  return __builtin_shufflevector(a, b, 0, 1, 2, 3);
}

template <typename T>
SI VectorType<T, 8> combine(VectorType<T, 4> a, VectorType<T, 4> b) {
  return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7);
}

template <typename T>
SI VectorType<T, 16> combine(VectorType<T, 8> a, VectorType<T, 8> b) {
  return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
                                 13, 14, 15);
}

template <typename T>
SI VectorType<T, 2> lowHalf(VectorType<T, 4> a) {
  return __builtin_shufflevector(a, a, 0, 1);
}

template <typename T>
SI VectorType<T, 2> highHalf(VectorType<T, 4> a) {
  return __builtin_shufflevector(a, a, 2, 3);
}

template <typename T>
SI VectorType<T, 4> lowHalf(VectorType<T, 8> a) {
  return __builtin_shufflevector(a, a, 0, 1, 2, 3);
}

template <typename T>
SI VectorType<T, 4> highHalf(VectorType<T, 8> a) {
  return __builtin_shufflevector(a, a, 4, 5, 6, 7);
}

template <typename T>
SI VectorType<T, 8> lowHalf(VectorType<T, 16> a) {
  return __builtin_shufflevector(a, a, 0, 1, 2, 3, 4, 5, 6, 7);
}

template <typename T>
SI VectorType<T, 8> highHalf(VectorType<T, 16> a) {
  return __builtin_shufflevector(a, a, 8, 9, 10, 11, 12, 13, 14, 15);
}

template <typename T>
SI VectorType<T, 8> expand(VectorType<T, 4> a) {
  return __builtin_shufflevector(a, a, 0, 1, 2, 3, -1, -1, -1, -1);
}
#else
template <typename T>
struct VectorMask {
  typedef T type;
};
template <>
struct VectorMask<uint32_t> {
  typedef int32_t type;
};
template <>
struct VectorMask<uint16_t> {
  typedef int16_t type;
};
template <>
struct VectorMask<uint8_t> {
  typedef int8_t type;
};
template <>
struct VectorMask<float> {
  typedef int type;
};

template <typename T, int N>
struct VectorType {
  enum { SIZE = N };

  typedef T data_type __attribute__((vector_size(sizeof(T) * N)));
  typedef typename VectorMask<T>::type mask_index;
  typedef mask_index mask_type
      __attribute__((vector_size(sizeof(mask_index) * N)));
  typedef T half_type __attribute__((vector_size(sizeof(T) * (N / 2))));
  union {
    data_type data;
    struct {
      T x, y, z, w;
    };
    T elements[N];
    struct {
      half_type low_half, high_half;
    };
  };

  VectorType() : data{0} {}

  constexpr VectorType(const VectorType& rhs) : data(rhs.data) {}
  // GCC vector extensions only support broadcasting scalars on arithmetic ops,
  // but not on initializers, hence the following...
  constexpr VectorType(T n) : data((data_type){0} + n) {}
  constexpr VectorType(T a, T b, T c, T d) : data{a, b, c, d} {}
  constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h)
      : data{a, b, c, d, e, f, g, h} {}
  constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h, T i, T j, T k,
                       T l, T m, T n, T o, T p)
      : data{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p} {}

  SI VectorType wrap(const data_type& data) {
    VectorType v;
    v.data = data;
    return v;
  }

  T& operator[](size_t i) { return elements[i]; }
  T operator[](size_t i) const { return elements[i]; }

  template <typename U>
  operator VectorType<U, 2>() const {
    return VectorType<U, 2>::wrap(
        (typename VectorType<U, N>::data_type){U(x), U(y)});
  }
  template <typename U>
  operator VectorType<U, 4>() const {
    return VectorType<U, 4>::wrap(
        (typename VectorType<U, N>::data_type){U(x), U(y), U(z), U(w)});
  }
  template <typename U>
  operator VectorType<U, 8>() const {
    return VectorType<U, 8>::wrap((typename VectorType<U, N>::data_type){
        U(elements[0]), U(elements[1]), U(elements[2]), U(elements[3]),
        U(elements[4]), U(elements[5]), U(elements[6]), U(elements[7])});
  }
  template <typename U>
  operator VectorType<U, 16>() const {
    return VectorType<U, 16>::wrap((typename VectorType<U, N>::data_type){
        U(elements[0]),
        U(elements[1]),
        U(elements[2]),
        U(elements[3]),
        U(elements[4]),
        U(elements[5]),
        U(elements[6]),
        U(elements[7]),
        U(elements[8]),
        U(elements[9]),
        U(elements[10]),
        U(elements[11]),
        U(elements[12]),
        U(elements[13]),
        U(elements[14]),
        U(elements[15]),
    });
  }

  VectorType operator-() const { return wrap(-data); }
  VectorType operator~() const { return wrap(~data); }

  VectorType operator&(VectorType x) const { return wrap(data & x.data); }
  VectorType operator&(T x) const { return wrap(data & x); }
  VectorType operator|(VectorType x) const { return wrap(data | x.data); }
  VectorType operator|(T x) const { return wrap(data | x); }
  VectorType operator^(VectorType x) const { return wrap(data ^ x.data); }
  VectorType operator^(T x) const { return wrap(data ^ x); }
  VectorType operator<<(int x) const { return wrap(data << x); }
  VectorType operator>>(int x) const { return wrap(data >> x); }
  VectorType operator+(VectorType x) const { return wrap(data + x.data); }
  VectorType operator+(T x) const { return wrap(data + x); }
  friend VectorType operator+(T x, VectorType y) { return wrap(x + y.data); }
  VectorType operator-(VectorType x) const { return wrap(data - x.data); }
  VectorType operator-(T x) const { return wrap(data - x); }
  friend VectorType operator-(T x, VectorType y) { return wrap(x - y.data); }
  VectorType operator*(VectorType x) const { return wrap(data * x.data); }
  VectorType operator*(T x) const { return wrap(data * x); }
  friend VectorType operator*(T x, VectorType y) { return wrap(x * y.data); }
  VectorType operator/(VectorType x) const { return wrap(data / x.data); }
  VectorType operator/(T x) const { return wrap(data / x); }
  friend VectorType operator/(T x, VectorType y) { return wrap(x / y.data); }
  VectorType operator%(int x) const { return wrap(data % x); }

  VectorType& operator&=(VectorType x) {
    data &= x.data;
    return *this;
  }
  VectorType& operator|=(VectorType x) {
    data |= x.data;
    return *this;
  }
  VectorType& operator^=(VectorType x) {
    data ^= x.data;
    return *this;
  }
  VectorType& operator<<=(int x) {
    data <<= x;
    return *this;
  }
  VectorType& operator>>=(int x) {
    data >>= x;
    return *this;
  }
  VectorType& operator+=(VectorType x) {
    data += x.data;
    return *this;
  }
  VectorType& operator-=(VectorType x) {
    data -= x.data;
    return *this;
  }
  VectorType& operator*=(VectorType x) {
    data *= x.data;
    return *this;
  }
  VectorType& operator/=(VectorType x) {
    data /= x.data;
    return *this;
  }
  VectorType& operator%=(int x) {
    data %= x;
    return *this;
  }

  VectorType<mask_type, N> operator==(VectorType x) const {
    return VectorType<mask_type, N>::wrap(data == x.data);
  }
  VectorType<mask_type, N> operator!=(VectorType x) const {
    return VectorType<mask_type, N>::wrap(data != x.data);
  }
  VectorType<mask_type, N> operator<(VectorType x) const {
    return VectorType<mask_type, N>::wrap(data < x.data);
  }
  VectorType<mask_type, N> operator>(VectorType x) const {
    return VectorType<mask_type, N>::wrap(data > x.data);
  }
  VectorType<mask_type, N> operator<=(VectorType x) const {
    return VectorType<mask_type, N>::wrap(data <= x.data);
  }
  VectorType<mask_type, N> operator>=(VectorType x) const {
    return VectorType<mask_type, N>::wrap(data >= x.data);
  }

  VectorType operator!() const { return wrap(!data); }
  VectorType operator&&(VectorType x) const { return wrap(data & x.data); }
  VectorType operator||(VectorType x) const { return wrap(data | x.data); }

  VectorType& operator=(VectorType x) {
    data = x.data;
    return *this;
  }

  VectorType<T, 4> shuffle(VectorType b, mask_index x, mask_index y,
                           mask_index z, mask_index w) const {
    return VectorType<T, 4>::wrap(__builtin_shuffle(
        data, b.data, (typename VectorType<T, 4>::mask_type){x, y, z, w}));
  }
  VectorType<T, 8> shuffle(VectorType b, mask_index x, mask_index y,
                           mask_index z, mask_index w, mask_index s,
                           mask_index t, mask_index u, mask_index v) const {
    return VectorType<T, 8>::wrap(__builtin_shuffle(
        data, b.data,
        (typename VectorType<T, 8>::mask_type){x, y, z, w, s, t, u, v}));
  }
  VectorType<T, 16> shuffle(VectorType b, mask_index x, mask_index y,
                            mask_index z, mask_index w, mask_index s,
                            mask_index t, mask_index u, mask_index v,
                            mask_index i, mask_index j, mask_index k,
                            mask_index l, mask_index m, mask_index n,
                            mask_index o, mask_index p) const {
    return VectorType<T, 16>::wrap(
        __builtin_shuffle(data, b.data,
                          (typename VectorType<T, 16>::mask_type){
                              x, y, z, w, s, t, u, v, i, j, k, l, m, n, o, p}));
  }

  VectorType<T, 4> swizzle(mask_index x, mask_index y, mask_index z,
                           mask_index w) const {
    return VectorType<T, 4>::wrap(__builtin_shuffle(
        data, (typename VectorType<T, 4>::mask_type){x, y, z, w}));
  }
  VectorType<T, 8> swizzle(mask_index x, mask_index y, mask_index z,
                           mask_index w, mask_index s, mask_index t,
                           mask_index u, mask_index v) const {
    return VectorType<T, 8>::wrap(__builtin_shuffle(
        data, (typename VectorType<T, 8>::mask_type){x, y, z, w, s, t, u, v}));
  }

  SI VectorType wrap(half_type low, half_type high) {
    VectorType v;
    v.low_half = low;
    v.high_half = high;
    return v;
  }

  VectorType<T, N * 2> combine(VectorType high) const {
    return VectorType<T, N * 2>::wrap(data, high.data);
  }

#  define xxxx swizzle(0, 0, 0, 0)
#  define yyyy swizzle(1, 1, 1, 1)
#  define zzzz swizzle(2, 2, 2, 2)
#  define wwww swizzle(3, 3, 3, 3)
#  define xxyy swizzle(0, 0, 1, 1)
#  define xxzz swizzle(0, 0, 2, 2)
#  define yyww swizzle(1, 1, 3, 3)
#  define zzww swizzle(2, 2, 3, 3)
#  define xyxy swizzle(0, 1, 0, 1)
#  define xzxz swizzle(0, 2, 0, 2)
#  define ywyw swizzle(1, 3, 1, 3)
#  define zwzw swizzle(2, 3, 2, 3)
#  define zwxy swizzle(2, 3, 0, 1)
#  define zyxw swizzle(2, 1, 0, 3)
#  define xxyz swizzle(0, 0, 1, 2)
#  define xyyz swizzle(0, 1, 1, 2)
#  define xyzz swizzle(0, 1, 2, 2)
#  define xzyw swizzle(0, 2, 1, 3)
#  define yzwx swizzle(1, 2, 3, 0)
#  define wxyz swizzle(3, 0, 1, 2)
#  define wzyx swizzle(3, 2, 1, 0)
#  define xxxxyyyy XXXXYYYY()
  VectorType<T, 8> XXXXYYYY() const {
    return swizzle(0, 0, 0, 0).combine(swizzle(1, 1, 1, 1));
  }
#  define zzzzwwww ZZZZWWWW()
  VectorType<T, 8> ZZZZWWWW() const {
    return swizzle(2, 2, 2, 2).combine(swizzle(3, 3, 3, 3));
  }
#  define xyzwxyzw XYZWXYZW()
  VectorType<T, 8> XYZWXYZW() const { return combine(*this); }
#  define xyxyxyxy XYXYXYXY()
  VectorType<T, 8> XYXYXYXY() const {
    return swizzle(0, 1, 0, 1).combine(swizzle(0, 1, 0, 1));
  }
#  define zwzwzwzw ZWZWZWZW()
  VectorType<T, 8> ZWZWZWZW() const {
    return swizzle(2, 3, 2, 3).combine(swizzle(2, 3, 2, 3));
  }
#  define xxyyzzww XXYYZZWW()
  VectorType<T, 8> XXYYZZWW() const {
    return swizzle(0, 0, 1, 1).combine(swizzle(2, 2, 3, 3));
  }
#  define xxxxyyyyzzzzwwww XXXXYYYYZZZZWWWW()
  VectorType<T, 16> XXXXYYYYZZZZWWWW() {
    return XXXXYYYY().combine(ZZZZWWWW());
  }
};

template <typename T>
struct VectorType<T, 2> {
  typedef T data_type __attribute__((vector_size(sizeof(T) * 2)));
  union {
    data_type data;
    struct {
      T x, y;
    };
    T elements[2];
  };

  SI VectorType wrap(const data_type& data) {
    VectorType v;
    v.data = data;
    return v;
  }

  VectorType operator&(VectorType x) const { return wrap(data & x.data); }
  VectorType operator&(T x) const { return wrap(data & x); }
  VectorType operator|(VectorType x) const { return wrap(data | x.data); }
  VectorType operator|(T x) const { return wrap(data | x); }
};

#  define CONVERT(vector, type) ((type)(vector))
#  define SHUFFLE(a, b, ...) a.shuffle(b, __VA_ARGS__)

template <typename T, int N>
SI VectorType<T, N * 2> combine(VectorType<T, N> a, VectorType<T, N> b) {
  return VectorType<T, N * 2>::wrap(a.data, b.data);
}

template <typename T, int N>
SI VectorType<T, N / 2> lowHalf(VectorType<T, N> a) {
  return VectorType<T, N / 2>::wrap(a.low_half);
}

template <typename T, int N>
SI VectorType<T, N / 2> highHalf(VectorType<T, N> a) {
  return VectorType<T, N / 2>::wrap(a.high_half);
}

template <typename T, int N>
SI VectorType<T, N * 2> expand(VectorType<T, N> a) {
  return combine(a, a);
}
#endif

template <typename T, int N>
SI VectorType<T, N * 4> combine(VectorType<T, N> a, VectorType<T, N> b,
                                VectorType<T, N> c, VectorType<T, N> d) {
  return combine(combine(a, b), combine(c, d));
}

template <typename T, int N>
SI VectorType<T, N> combineLow(VectorType<T, N> a, VectorType<T, N> b) {
  return combine(lowHalf(a), lowHalf(b));
}

template <typename T, int N>
SI VectorType<T, N> combineHigh(VectorType<T, N> a, VectorType<T, N> b) {
  return combine(highHalf(a), highHalf(b));
}

template <typename T, int N>
SI VectorType<T, N * 2> repeat2(VectorType<T, N> a) {
  return combine(a, a);
}

template <typename T, int N>
SI VectorType<T, N * 4> repeat4(VectorType<T, N> a) {
  return combine(a, a, a, a);
}

template <typename T>
SI VectorType<T, 4> zipLow(VectorType<T, 4> a, VectorType<T, 4> b) {
  return SHUFFLE(a, b, 0, 4, 1, 5);
}

template <typename T>
SI VectorType<T, 4> zipHigh(VectorType<T, 4> a, VectorType<T, 4> b) {
  return SHUFFLE(a, b, 2, 6, 3, 7);
}

template <typename T>
SI VectorType<T, 8> zipLow(VectorType<T, 8> a, VectorType<T, 8> b) {
  return SHUFFLE(a, b, 0, 8, 1, 9, 2, 10, 3, 11);
}

template <typename T>
SI VectorType<T, 8> zipHigh(VectorType<T, 8> a, VectorType<T, 8> b) {
  return SHUFFLE(a, b, 4, 12, 5, 13, 6, 14, 7, 15);
}

template <typename T>
SI VectorType<T, 16> zipLow(VectorType<T, 16> a, VectorType<T, 16> b) {
  return SHUFFLE(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23);
}

template <typename T>
SI VectorType<T, 16> zipHigh(VectorType<T, 16> a, VectorType<T, 16> b) {
  return SHUFFLE(a, b, 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30,
                 31);
}

template <typename T>
SI VectorType<T, 8> zip2Low(VectorType<T, 8> a, VectorType<T, 8> b) {
  return SHUFFLE(a, b, 0, 1, 8, 9, 2, 3, 10, 11);
}

template <typename T>
SI VectorType<T, 8> zip2High(VectorType<T, 8> a, VectorType<T, 8> b) {
  return SHUFFLE(a, b, 4, 5, 12, 13, 6, 7, 14, 15);
}

#ifdef __clang__
template <typename T>
SI VectorType<T, 8> zip(VectorType<T, 4> a, VectorType<T, 4> b) {
  return SHUFFLE(a, b, 0, 4, 1, 5, 2, 6, 3, 7);
}

template <typename T>
SI VectorType<T, 16> zip(VectorType<T, 8> a, VectorType<T, 8> b) {
  return SHUFFLE(a, b, 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15);
}
#else
template <typename T, int N>
SI VectorType<T, N * 2> zip(VectorType<T, N> a, VectorType<T, N> b) {
  return combine(zipLow(a, b), zipHigh(a, b));
}
#endif

template <typename T>
struct Unaligned {
  template <typename P>
  SI T load(const P* p) {
    T v;
    memcpy(&v, p, sizeof(v));
    return v;
  }

  template <typename P>
  SI void store(P* p, T v) {
    memcpy(p, &v, sizeof(v));
  }
};

#ifndef __clang__
template <typename T, int N>
struct Unaligned<VectorType<T, N>> {
  template <typename P>
  SI VectorType<T, N> load(const P* p) {
    VectorType<T, N> v;
    memcpy(v.elements, p, sizeof(v));
    return v;
  }

  template <typename P>
  SI void store(P* p, VectorType<T, N> v) {
    memcpy(p, v.elements, sizeof(v));
  }
};
#endif

template <typename T, typename P>
SI T unaligned_load(const P* p) {
  return Unaligned<T>::load(p);
}

template <typename T, typename P>
SI void unaligned_store(P* p, T v) {
  Unaligned<T>::store(p, v);
}

template <typename D, typename S>
SI D bit_cast(const S& src) {
  static_assert(sizeof(D) == sizeof(S), "");
  return unaligned_load<D>(&src);
}

template <typename T>
using V2 = VectorType<T, 2>;
template <typename T>
using V4 = VectorType<T, 4>;
using Float = V4<float>;
using I32 = V4<int32_t>;
using I16 = V4<int16_t>;
using U64 = V4<uint64_t>;
using U32 = V4<uint32_t>;
using U16 = V4<uint16_t>;
using U8 = V4<uint8_t>;
using Bool = V4<int>;
template <typename T>
using V8 = VectorType<T, 8>;
template <typename T>
using V16 = VectorType<T, 16>;

}  // namespace glsl
