/*
 * Copyright (c), Recep Aslantas.
 *
 * MIT License (MIT), http://opensource.org/licenses/MIT
 * Full license can be found in the LICENSE file
 */

/*
 Macros:
   GLMS_VEC4_ONE_INIT
   GLMS_VEC4_BLACK_INIT
   GLMS_VEC4_ZERO_INIT
   GLMS_VEC4_ONE
   GLMS_VEC4_BLACK
   GLMS_VEC4_ZERO

 Functions:
   CGLM_INLINE vec4s glms_vec4(vec3s v3, float last);
   CGLM_INLINE vec3s glms_vec4_copy3(vec4s v);
   CGLM_INLINE vec4s glms_vec4_copy(vec4s v);
   CGLM_INLINE vec4s glms_vec4_ucopy(vec4s v);
   CGLM_INLINE void  glms_vec4_pack(vec4s dst[], vec4 src[], size_t len);
   CGLM_INLINE void  glms_vec4_unpack(vec4 dst[], vec4s src[], size_t len);
   CGLM_INLINE float glms_vec4_dot(vec4s a, vec4s b);
   CGLM_INLINE float glms_vec4_norm2(vec4s v);
   CGLM_INLINE float glms_vec4_norm(vec4s v);
   CGLM_INLINE float glms_vec4_norm_one(vec4s v);
   CGLM_INLINE float glms_vec4_norm_inf(vec4s v);
   CGLM_INLINE vec4s glms_vec4_add(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_adds(vec4s v, float s);
   CGLM_INLINE vec4s glms_vec4_sub(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_subs(vec4s v, float s);
   CGLM_INLINE vec4s glms_vec4_mul(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_scale(vec4s v, float s);
   CGLM_INLINE vec4s glms_vec4_scale_as(vec4s v, float s);
   CGLM_INLINE vec4s glms_vec4_div(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_divs(vec4s v, float s);
   CGLM_INLINE vec4s glms_vec4_addadd(vec4s a, vec4s b, vec4s dest);
   CGLM_INLINE vec4s glms_vec4_subadd(vec4s a, vec4s b, vec4s dest);
   CGLM_INLINE vec4s glms_vec4_muladd(vec4s a, vec4s b, vec4s dest);
   CGLM_INLINE vec4s glms_vec4_muladds(vec4s a, float s, vec4s dest);
   CGLM_INLINE vec4s glms_vec4_maxadd(vec4s a, vec4s b, vec4s dest);
   CGLM_INLINE vec4s glms_vec4_minadd(vec4s a, vec4s b, vec4s dest);
   CGLM_INLINE vec4s glms_vec4_negate(vec4s v);
   CGLM_INLINE vec4s glms_vec4_inv(vec4s v);
   CGLM_INLINE vec4s glms_vec4_normalize(vec4s v);
   CGLM_INLINE float glms_vec4_distance(vec4s a, vec4s b);
   CGLM_INLINE float glms_vec4_distance2(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_maxv(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_minv(vec4s a, vec4s b);
   CGLM_INLINE vec4s glms_vec4_clamp(vec4s v, float minVal, float maxVal);
   CGLM_INLINE vec4s glms_vec4_lerp(vec4s from, vec4s to, float t);
   CGLM_INLINE vec4s glms_vec4_lerpc(vec4s from, vec4s to, float t);
   CGLM_INLINE vec4s glms_vec4_mix(vec4s from, vec4s to, float t);
   CGLM_INLINE vec4s glms_vec4_mixc(vec4s from, vec4s to, float t);
   CGLM_INLINE vec4s glms_vec4_step_uni(float edge, vec4s x);
   CGLM_INLINE vec4s glms_vec4_step(vec4s edge, vec4s x);
   CGLM_INLINE vec4s glms_vec4_smoothstep_uni(float edge0, float edge1, vec4s x);
   CGLM_INLINE vec4s glms_vec4_smoothstep(vec4s edge0, vec4s edge1, vec4s x);
   CGLM_INLINE vec4s glms_vec4_smoothinterp(vec4s from, vec4s to, float t);
   CGLM_INLINE vec4s glms_vec4_smoothinterpc(vec4s from, vec4s to, float t);
   CGLM_INLINE vec4s glms_vec4_cubic(float s);
   CGLM_INLINE vec4s glms_vec4_swizzle(vec4s v, int mask);
 */

#ifndef cglms_vec4s_h
#define cglms_vec4s_h

#include "../common.h"
#include "../types-struct.h"
#include "../util.h"
#include "../vec4.h"
#include "vec4-ext.h"

#define GLMS_VEC4_ONE_INIT   {GLM_VEC4_ONE_INIT}
#define GLMS_VEC4_BLACK_INIT {GLM_VEC4_BLACK_INIT}
#define GLMS_VEC4_ZERO_INIT  {GLM_VEC4_ZERO_INIT}

#define GLMS_VEC4_ONE        ((vec4s)GLM_VEC4_ONE_INIT)
#define GLMS_VEC4_BLACK      ((vec4s)GLM_VEC4_BLACK_INIT)
#define GLMS_VEC4_ZERO       ((vec4s)GLM_VEC4_ZERO_INIT)

/*!
 * @brief init vec4 using vec3
 *
 * @param[in]  v3   vector3
 * @param[in]  last last item
 * @returns         destination
 */
CGLM_INLINE
vec4s
glms_vec4(vec3s v3, float last) {
  vec4s r;
  glm_vec4(v3.raw, last, r.raw);
  return r;
}

/*!
 * @brief copy first 3 members of [a] to [dest]
 *
 * @param[in]  v    source
 * @returns         vec3
 */
CGLM_INLINE
vec3s
glms_vec4_copy3(vec4s v) {
  vec3s r;
  glm_vec4_copy3(v.raw, r.raw);
  return r;
}

/*!
 * @brief copy all members of [a] to [dest]
 *
 * @param[in]  v    source
 * @returns         destination
 */
CGLM_INLINE
vec4s
glms_vec4_copy(vec4s v) {
  vec4s r;
  glm_vec4_copy(v.raw, r.raw);
  return r;
}

/*!
 * @brief copy all members of [a] to [dest]
 *
 * alignment is not required
 *
 * @param[in]  v    source
 * @returns         destination
 */
CGLM_INLINE
vec4s
glms_vec4_ucopy(vec4s v) {
  vec4s r;
  glm_vec4_ucopy(v.raw, r.raw);
  return r;
}

/*!
 * @brief pack an array of vec4 into an array of vec4s
 *
 * @param[out] dst array of vec4
 * @param[in]  src array of vec4s
 * @param[in]  len number of elements
 */
CGLM_INLINE
void
glms_vec4_pack(vec4s dst[], vec4 src[], size_t len) {
  size_t i;

  for (i = 0; i < len; i++) {
    glm_vec4_copy(src[i], dst[i].raw);
  }
}

/*!
 * @brief unpack an array of vec4s into an array of vec4
 *
 * @param[out] dst array of vec4s
 * @param[in]  src array of vec4
 * @param[in]  len number of elements
 */
CGLM_INLINE
void
glms_vec4_unpack(vec4 dst[], vec4s src[], size_t len) {
  size_t i;

  for (i = 0; i < len; i++) {
    glm_vec4_copy(src[i].raw, dst[i]);
  }
}

/*!
 * @brief make vector zero
 *
 * @returns      zero vector
 */
CGLM_INLINE
vec4s
glms_vec4_zero(void) {
  vec4s r;
  glm_vec4_zero(r.raw);
  return r;
}

/*!
 * @brief make vector one
 *
 * @returns      one vector
 */
CGLM_INLINE
vec4s
glms_vec4_one(void) {
  vec4s r;
  glm_vec4_one(r.raw);
  return r;
}

/*!
 * @brief vec4 dot product
 *
 * @param[in] a vector1
 * @param[in] b vector2
 *
 * @return dot product
 */
CGLM_INLINE
float
glms_vec4_dot(vec4s a, vec4s b) {
  return glm_vec4_dot(a.raw, b.raw);
}

/*!
 * @brief norm * norm (magnitude) of vec
 *
 * we can use this func instead of calling norm * norm, because it would call
 * sqrtf fuction twice but with this func we can avoid func call, maybe this is
 * not good name for this func
 *
 * @param[in] v vec4
 *
 * @return norm * norm
 */
CGLM_INLINE
float
glms_vec4_norm2(vec4s v) {
  return glm_vec4_norm2(v.raw);
}

/*!
 * @brief norm (magnitude) of vec4
 *
 * @param[in] v vector
 *
 * @return norm
 */
CGLM_INLINE
float
glms_vec4_norm(vec4s v) {
  return glm_vec4_norm(v.raw);
}

/*!
 * @brief L1 norm of vec4
 * Also known as Manhattan Distance or Taxicab norm.
 * L1 Norm is the sum of the magnitudes of the vectors in a space.
 * It is calculated as the sum of the absolute values of the vector components.
 * In this norm, all the components of the vector are weighted equally.
 *
 * This computes:
 * R = |v[0]| + |v[1]| + |v[2]| + |v[3]|
 *
 * @param[in] v vector
 *
 * @return L1 norm
 */
CGLM_INLINE
float
glms_vec4_norm_one(vec4s v) {
  return glm_vec4_norm_one(v.raw);
}

/*!
 * @brief Infinity norm of vec4
 * Also known as Maximum norm.
 * Infinity Norm is the largest magnitude among each element of a vector.
 * It is calculated as the maximum of the absolute values of the vector components.
 *
 * This computes:
 * inf norm = max(|v[0]|, |v[1]|, |v[2]|, |v[3]|)
 *
 * @param[in] v vector
 *
 * @return Infinity norm
 */
CGLM_INLINE
float
glms_vec4_norm_inf(vec4s v) {
  return glm_vec4_norm_inf(v.raw);
}

/*!
 * @brief add b vector to a vector store result in dest
 *
 * @param[in]  a    vector1
 * @param[in]  b    vector2
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_add(vec4s a, vec4s b) {
  vec4s r;
  glm_vec4_add(a.raw, b.raw, r.raw);
  return r;
}

/*!
 * @brief add scalar to v vector store result in dest (d = v + vec(s))
 *
 * @param[in]  v    vector
 * @param[in]  s    scalar
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_adds(vec4s v, float s) {
  vec4s r;
  glm_vec4_adds(v.raw, s, r.raw);
  return r;
}

/*!
 * @brief subtract b vector from a vector store result in dest (d = a - b)
 *
 * @param[in]  a    vector1
 * @param[in]  b    vector2
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_sub(vec4s a, vec4s b) {
  vec4s r;
  glm_vec4_sub(a.raw, b.raw, r.raw);
  return r;
}

/*!
 * @brief subtract scalar from v vector store result in dest (d = v - vec(s))
 *
 * @param[in]  v    vector
 * @param[in]  s    scalar
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_subs(vec4s v, float s) {
  vec4s r;
  glm_vec4_subs(v.raw, s, r.raw);
  return r;
}

/*!
 * @brief multiply two vector (component-wise multiplication)
 *
 * @param a    vector1
 * @param b    vector2
 * @returns    dest = (a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3])
 */
CGLM_INLINE
vec4s
glms_vec4_mul(vec4s a, vec4s b) {
  vec4s r;
  glm_vec4_mul(a.raw, b.raw, r.raw);
  return r;
}

/*!
 * @brief multiply/scale vec4 vector with scalar: result = v * s
 *
 * @param[in]  v    vector
 * @param[in]  s    scalar
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_scale(vec4s v, float s) {
  vec4s r;
  glm_vec4_scale(v.raw, s, r.raw);
  return r;
}

/*!
 * @brief make vec4 vector scale as specified: result = unit(v) * s
 *
 * @param[in]  v    vector
 * @param[in]  s    scalar
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_scale_as(vec4s v, float s) {
  vec4s r;
  glm_vec4_scale_as(v.raw, s, r.raw);
  return r;
}

/*!
 * @brief div vector with another component-wise division: d = a / b
 *
 * @param[in]  a    vector 1
 * @param[in]  b    vector 2
 * @returns         result = (a[0]/b[0], a[1]/b[1], a[2]/b[2], a[3]/b[3])
 */
CGLM_INLINE
vec4s
glms_vec4_div(vec4s a, vec4s b) {
  vec4s r;
  glm_vec4_div(a.raw, b.raw, r.raw);
  return r;
}

/*!
 * @brief div vec4 vector with scalar: d = v / s
 *
 * @param[in]  v    vector
 * @param[in]  s    scalar
 * @returns         destination vector
 */
CGLM_INLINE
vec4s
glms_vec4_divs(vec4s v, float s) {
  vec4s r;
  glm_vec4_divs(v.raw, s, r.raw);
  return r;
}

/*!
 * @brief add two vectors and add result to sum
 *
 * it applies += operator so dest must be initialized
 *
 * @param[in]  a    vector 1
 * @param[in]  b    vector 2
 * @returns         dest += (a + b)
 */
CGLM_INLINE
vec4s
glms_vec4_addadd(vec4s a, vec4s b, vec4s dest) {
  glm_vec4_addadd(a.raw, b.raw, dest.raw);
  return dest;
}

/*!
 * @brief sub two vectors and add result to dest
 *
 * it applies += operator so dest must be initialized
 *
 * @param[in]  a    vector 1
 * @param[in]  b    vector 2
 * @returns         dest += (a - b)
 */
CGLM_INLINE
vec4s
glms_vec4_subadd(vec4s a, vec4s b, vec4s dest) {
  glm_vec4_subadd(a.raw, b.raw, dest.raw);
  return dest;
}

/*!
 * @brief mul two vectors and add result to dest
 *
 * it applies += operator so dest must be initialized
 *
 * @param[in]  a    vector 1
 * @param[in]  b    vector 2
 * @returns         dest += (a * b)
 */
CGLM_INLINE
vec4s
glms_vec4_muladd(vec4s a, vec4s b, vec4s dest) {
  glm_vec4_muladd(a.raw, b.raw, dest.raw);
  return dest;
}

/*!
 * @brief mul vector with scalar and add result to sum
 *
 * it applies += operator so dest must be initialized
 *
 * @param[in]  a    vector
 * @param[in]  s    scalar
 * @returns         dest += (a * b)
 */
CGLM_INLINE
vec4s
glms_vec4_muladds(vec4s a, float s, vec4s dest) {
  glm_vec4_muladds(a.raw, s, dest.raw);
  return dest;
}

/*!
 * @brief add max of two vector to result/dest
 *
 * it applies += operator so dest must be initialized
 *
 * @param[in]  a    vector 1
 * @param[in]  b    vector 2
 * @returns         dest += max(a, b)
 */
CGLM_INLINE
vec4s
glms_vec4_maxadd(vec4s a, vec4s b, vec4s dest) {
  glm_vec4_maxadd(a.raw, b.raw, dest.raw);
  return dest;
}

/*!
 * @brief add min of two vector to result/dest
 *
 * it applies += operator so dest must be initialized
 *
 * @param[in]  a    vector 1
 * @param[in]  b    vector 2
 * @returns         dest += min(a, b)
 */
CGLM_INLINE
vec4s
glms_vec4_minadd(vec4s a, vec4s b, vec4s dest) {
  glm_vec4_minadd(a.raw, b.raw, dest.raw);
  return dest;
}

/*!
 * @brief negate vector components and store result in dest
 *
 * @param[in]  v     vector
 * @returns          result vector
 */
CGLM_INLINE
vec4s
glms_vec4_negate(vec4s v) {
  glm_vec4_negate(v.raw);
  return v;
}

/*!
 * @brief normalize vec4 and store result in same vec
 *
 * @param[in] v   vector
 * @returns       normalized vector
 */
CGLM_INLINE
vec4s
glms_vec4_normalize(vec4s v) {
  glm_vec4_normalize(v.raw);
  return v;
}

/**
 * @brief distance between two vectors
 *
 * @param[in] a vector1
 * @param[in] b vector2
 * @return returns distance
 */
CGLM_INLINE
float
glms_vec4_distance(vec4s a, vec4s b) {
  return glm_vec4_distance(a.raw, b.raw);
}

/**
 * @brief squared distance between two vectors
 *
 * @param[in] a vector1
 * @param[in] b vector2
 * @return returns squared distance
 */
CGLM_INLINE
float
glms_vec4_distance2(vec4s a, vec4s b) {
  return glm_vec4_distance2(a.raw, b.raw);
}

/*!
 * @brief max values of vectors
 *
 * @param[in]  a    vector1
 * @param[in]  b    vector2
 * @returns         destination
 */
CGLM_INLINE
vec4s
glms_vec4_maxv(vec4s a, vec4s b) {
  vec4s r;
  glm_vec4_maxv(a.raw, b.raw, r.raw);
  return r;
}

/*!
 * @brief min values of vectors
 *
 * @param[in]  a    vector1
 * @param[in]  b    vector2
 * @returns         destination
 */
CGLM_INLINE
vec4s
glms_vec4_minv(vec4s a, vec4s b) {
  vec4s r;
  glm_vec4_minv(a.raw, b.raw, r.raw);
  return r;
}

/*!
 * @brief clamp vector's individual members between min and max values
 *
 * @param[in]       v       vector
 * @param[in]       minVal  minimum value
 * @param[in]       maxVal  maximum value
 * @returns                 clamped vector
 */
CGLM_INLINE
vec4s
glms_vec4_clamp(vec4s v, float minVal, float maxVal) {
  glm_vec4_clamp(v.raw, minVal, maxVal);
  return v;
}

/*!
 * @brief linear interpolation between two vectors
 *
 * formula:  from + s * (to - from)
 *
 * @param[in]   from  from value
 * @param[in]   to    to value
 * @param[in]   t     interpolant (amount)
 * @returns           destination
 */
CGLM_INLINE
vec4s
glms_vec4_lerp(vec4s from, vec4s to, float t) {
  vec4s r;
  glm_vec4_lerp(from.raw, to.raw, t, r.raw);
  return r;
}

/*!
 * @brief linear interpolation between two vectors (clamped)
 *
 * formula:  from + s * (to - from)
 *
 * @param[in]   from  from value
 * @param[in]   to    to value
 * @param[in]   t     interpolant (amount) clamped between 0 and 1
 * @returns           destination
 */
CGLM_INLINE
vec4s
glms_vec4_lerpc(vec4s from, vec4s to, float t) {
  vec4s r;
  glm_vec4_lerpc(from.raw, to.raw, t, r.raw);
  return r;
}

/*!
 * @brief linear interpolation between two vectors
 *
 * formula:  from + s * (to - from)
 *
 * @param[in]   from  from value
 * @param[in]   to    to value
 * @param[in]   t     interpolant (amount)
 * @returns           destination
 */
CGLM_INLINE
vec4s
glms_vec4_mix(vec4s from, vec4s to, float t) {
  vec4s r;
  glm_vec4_mix(from.raw, to.raw, t, r.raw);
  return r;
}

/*!
 * @brief linear interpolation between two vectors (clamped)
 *
 * formula:  from + s * (to - from)
 *
 * @param[in]   from  from value
 * @param[in]   to    to value
 * @param[in]   t     interpolant (amount) clamped between 0 and 1
 * @returns           destination
 */
CGLM_INLINE
vec4s
glms_vec4_mixc(vec4s from, vec4s to, float t) {
  vec4s r;
  glm_vec4_mixc(from.raw, to.raw, t, r.raw);
  return r;
}

/*!
 * @brief threshold function (unidimensional)
 *
 * @param[in]   edge    threshold
 * @param[in]   x       value to test against threshold
 * @returns             0.0 if x < edge, else 1.0
 */
CGLM_INLINE
vec4s
glms_vec4_step_uni(float edge, vec4s x) {
  vec4s r;
  glm_vec4_step_uni(edge, x.raw, r.raw);
  return r;
}

/*!
 * @brief threshold function
 *
 * @param[in]   edge    threshold
 * @param[in]   x       value to test against threshold
 * @returns             0.0 if x < edge, else 1.0
 */
CGLM_INLINE
vec4s
glms_vec4_step(vec4s edge, vec4s x) {
  vec4s r;
  glm_vec4_step(edge.raw, x.raw, r.raw);
  return r;
}

/*!
 * @brief threshold function with a smooth transition (unidimensional)
 *
 * @param[in]   edge0   low threshold
 * @param[in]   edge1   high threshold
 * @param[in]   x       value to test against threshold
 * @returns             destination
 */
CGLM_INLINE
vec4s
glms_vec4_smoothstep_uni(float edge0, float edge1, vec4s x) {
  vec4s r;
  glm_vec4_smoothstep_uni(edge0, edge1, x.raw, r.raw);
  return r;
}

/*!
 * @brief threshold function with a smooth transition
 *
 * @param[in]   edge0   low threshold
 * @param[in]   edge1   high threshold
 * @param[in]   x       value to test against threshold
 * @returns             destination
 */
CGLM_INLINE
vec4s
glms_vec4_smoothstep(vec4s edge0, vec4s edge1, vec4s x) {
  vec4s r;
  glm_vec4_smoothstep(edge0.raw, edge1.raw, x.raw, r.raw);
  return r;
}

/*!
 * @brief smooth Hermite interpolation between two vectors
 *
 * formula:  from + s * (to - from)
 *
 * @param[in]   from    from value
 * @param[in]   to      to value
 * @param[in]   t       interpolant (amount)
 * @returns             destination
 */
CGLM_INLINE
vec4s
glms_vec4_smoothinterp(vec4s from, vec4s to, float t) {
  vec4s r;
  glm_vec4_smoothinterp(from.raw, to.raw, t, r.raw);
  return r;
}

/*!
 * @brief smooth Hermite interpolation between two vectors (clamped)
 *
 * formula:  from + s * (to - from)
 *
 * @param[in]   from    from value
 * @param[in]   to      to value
 * @param[in]   t       interpolant (amount) clamped between 0 and 1
 * @returns             destination
 */
CGLM_INLINE
vec4s
glms_vec4_smoothinterpc(vec4s from, vec4s to, float t) {
  vec4s r;
  glm_vec4_smoothinterpc(from.raw, to.raw, t, r.raw);
  return r;
}

/*!
 * @brief helper to fill vec4 as [S^3, S^2, S, 1]
 *
 * @param[in]   s     parameter
 * @returns           destination
 */
CGLM_INLINE
vec4s
glms_vec4_cubic(float s) {
  vec4s r;
  glm_vec4_cubic(s, r.raw);
  return r;
}

/*!
 * @brief swizzle vector components
 *
 * you can use existin masks e.g. GLM_XXXX, GLM_WZYX
 *
 * @param[in]  v    source
 * @param[in]  mask mask
 * @returns swizzled vector
 */
CGLM_INLINE
vec4s
glms_vec4_swizzle(vec4s v, int mask) {
  vec4s dest;
  glm_vec4_swizzle(v.raw, mask, dest.raw);
  return dest;
}

#endif /* cglms_vec4s_h */