#include "include/rb_gsl.h"

/*!
  Counter-clockwise rotation around the X-axis

  / x' \    /  1     0      0    \ / x \
  | y' |  = |  0  cos_th -sin_th | | y |
  \ z' /    \  0  sin_th  cos_th / \ z /

 */
void vector3_rotateX(const double x[3] /*!< Input */,
                     double theta /*!< Rotation angle */,
                     double xout[3] /*!< Output */)
{
  double a, b, c;
  double costheta, sintheta;

  costheta = cos(theta);
  sintheta = sin(theta);

  a = x[0];
  b = x[1]*costheta - x[2]*sintheta;
  c = x[1]*sintheta + x[2]*costheta;

  xout[0] = a;
  xout[1] = b;
  xout[2] = c;
}

/*!
  Counter-clockwise rotation around the Y-axis
  (Note the sign of the matrix element.)

  / x' \    /  cos_th     0   sin_th \ / x \
  | y' |  = |    0        1      0   | | y |
  \ z' /    \ -sin_th     0   cos_th / \ z /

 */
void vector3_rotateY(const double x[3] /*!< Input */,
                     double theta /*!< Rotation angle */,
                     double xout[3] /*!< Output */)
{
  double a, b, c;
  double costheta, sintheta;

  costheta = cos(theta);
  sintheta = sin(theta);

  a =  x[0]*costheta + x[2]*sintheta;
  b =  x[1];
  c = -x[0]*sintheta + x[2]*costheta;

  xout[0] = a;
  xout[1] = b;
  xout[2] = c;
}

/*!
  Counter-clockwise rotation around the Z-axis

  / x' \    /  cos_th  -sin_th  0 \ / x \
  | y' |  = |  sin_th   cos_th  0 | | y |
  \ z' /    \    0        0     1 / \ z /

 */
void vector3_rotateZ(const double x[3] /*!< Input */,
                     double theta /*!< Rotation angle */,
                     double xout[3] /*!< Output */)
{
  double a, b, c;
  double costheta, sintheta;

  costheta = cos(theta);
  sintheta = sin(theta);

  a = x[0]*costheta - x[1]*sintheta;
  b = x[0]*sintheta + x[1]*costheta;
  c = x[2];

  xout[0] = a;
  xout[1] = b;
  xout[2] = c;
}

/*!
  Rotate a 3-vector.

  If flag != 1, cos/sin values stored in the static variables are used
  for efficiency.
  The input and the output vectors (x, xout) can be the same pointer.

  / x' \     /   cos_phi -sin_phi   0  \ /  cos_th   0   sin_th \  / x \
  | y' |  =  |   sin_phi  cos_phi   0  | |     0     1          |  | y |
  \ z' /     \      0        0      1  / \ -sin_th   0   cos_th /  \ z /

 */
void vector3_rotate(const double x[3], double theta, double phi,
                    double xout[3])
{
  vector3_rotateY(x, theta,xout);
  vector3_rotateZ(xout, phi, xout);
}

static VALUE rb_gsl_vector_rotateX(VALUE obj, VALUE angle)
{
  gsl_vector *v;
  Data_Get_Struct(obj, gsl_vector, v);
  vector3_rotateX(v->data, NUM2DBL(angle), v->data);
  return obj;
}
static VALUE rb_gsl_vector_rotateY(VALUE obj, VALUE angle)
{
  gsl_vector *v;
  Data_Get_Struct(obj, gsl_vector, v);
  vector3_rotateY(v->data, NUM2DBL(angle), v->data);
  return obj;
}
static VALUE rb_gsl_vector_rotateZ(VALUE obj, VALUE angle)
{
  gsl_vector *v;
  Data_Get_Struct(obj, gsl_vector, v);
  vector3_rotateZ(v->data, NUM2DBL(angle), v->data);
  return obj;
}
static VALUE rb_gsl_vector_rotate(VALUE obj, VALUE theta, VALUE phi)
{
  gsl_vector *v;
  Data_Get_Struct(obj, gsl_vector, v);
  vector3_rotate(v->data, NUM2DBL(theta), NUM2DBL(phi), v->data);
  return obj;
}

void Init_geometry(VALUE module)
{
  rb_define_method(cgsl_vector, "rotateX", rb_gsl_vector_rotateX, 1);
  rb_define_method(cgsl_vector, "rotateY", rb_gsl_vector_rotateY, 1);
  rb_define_method(cgsl_vector, "rotateZ", rb_gsl_vector_rotateZ, 1);
  rb_define_method(cgsl_vector, "rotate", rb_gsl_vector_rotate, 2);
}
