/*
  fft.c
  Ruby/GSL: Ruby extension library for GSL (GNU Scientific Library)
    (C) Copyright 2004 by Yoshiki Tsunesada

  Ruby/GSL is free software: you can redistribute it and/or modify it
  under the terms of the GNU General Public License.
  This library is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY.
*/

#include "rb_gsl_config.h"
#include "rb_gsl_fft.h"

VALUE mgsl_fft_complex;
VALUE mgsl_fft, mgsl_fft_real, mgsl_fft_halfcomplex;
VALUE cgsl_cparray;
VALUE cgsl_fft_wavetable, cgsl_fft_workspace;
VALUE cgsl_fft_wavetable_factor;
VALUE cgsl_fft_complex_wavetable, cgsl_fft_complex_workspace;
VALUE cgsl_fft_real_wavetable, cgsl_fft_halfcomplex_wavetable;
VALUE cgsl_fft_real_workspace;
VALUE cgsl_vector_halfcomplex;

static GSL_FFT_Workspace* GSL_FFT_Workspace_new(size_t n);
static void GSL_FFT_Workspace_free(GSL_FFT_Workspace *space);

static VALUE rb_gsl_fft_complex_wavetable_new(VALUE klass, VALUE n)
{
  CHECK_FIXNUM(n);
  return Data_Wrap_Struct(cgsl_fft_complex_wavetable, 0, 
			  gsl_fft_complex_wavetable_free,
			  gsl_fft_complex_wavetable_alloc(FIX2INT(n)));
}

static VALUE rb_gsl_fft_real_wavetable_new(VALUE klass, VALUE n)
{
  CHECK_FIXNUM(n);
  return Data_Wrap_Struct(klass, 0, gsl_fft_real_wavetable_free,
			  gsl_fft_real_wavetable_alloc(FIX2INT(n)));
}

static VALUE rb_gsl_fft_halfcomplex_wavetable_new(VALUE klass, VALUE n)
{
  CHECK_FIXNUM(n);
  return Data_Wrap_Struct(klass, 0, gsl_fft_halfcomplex_wavetable_free,
			  gsl_fft_halfcomplex_wavetable_alloc(FIX2INT(n)));
  
}

static void GSL_FFT_Wavetable_free(GSL_FFT_Wavetable *table)
{
  gsl_fft_complex_wavetable_free((gsl_fft_complex_wavetable *) table);
}

static GSL_FFT_Workspace* GSL_FFT_Workspace_new(size_t n)
{
  CHECK_FIXNUM(n);
  return (GSL_FFT_Workspace *) gsl_fft_complex_workspace_alloc(n);
}

static VALUE rb_gsl_fft_complex_workspace_new(VALUE klass, VALUE n)
{
  CHECK_FIXNUM(n);
  return Data_Wrap_Struct(klass, 0, gsl_fft_complex_workspace_free,
			  gsl_fft_complex_workspace_alloc(FIX2INT(n)));
}

static VALUE rb_gsl_fft_real_workspace_new(VALUE klass, VALUE n)
{
  CHECK_FIXNUM(n);
  return Data_Wrap_Struct(klass, 0, gsl_fft_real_workspace_free,
			  gsl_fft_real_workspace_alloc(FIX2INT(n)));
}

static void GSL_FFT_Workspace_free(GSL_FFT_Workspace *space)
{
  gsl_fft_complex_workspace_free((gsl_fft_complex_workspace *) space);
}
  
VALUE rb_gsl_vector_new(int argc, VALUE *argv, VALUE klass);
VALUE rb_gsl_permutation_new(VALUE klass, VALUE nn);
static void get_stride_n(int argc, VALUE *argv, int argstart, gsl_vector *v,
			 size_t *stride, size_t *n);

static VALUE cparray_get(VALUE obj, VALUE ii)
{
  gsl_vector *v = NULL;
  gsl_complex *c = NULL;
  size_t i;
  CHECK_FIXNUM(ii);
  CHECK_VECTOR(obj);
  Data_Get_Struct(obj, gsl_vector, v);
  i = FIX2INT(ii);
  c = ALLOC(gsl_complex);
  GSL_SET_REAL(c, gsl_vector_get(v, 2*i));
  GSL_SET_IMAG(c, gsl_vector_get(v, 2*i+1));
  return Data_Wrap_Struct(cgsl_complex, 0, free, c); 
}

static VALUE cparray_set(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL;
  gsl_complex *c = NULL;
  size_t i;
  if (argc < 2) rb_raise(rb_eArgError, "too few arguments");
  CHECK_VECTOR(obj);
  CHECK_FIXNUM(argv[0]);
  i = FIX2INT(argv[0]);
  Data_Get_Struct(obj, gsl_vector, v);
  if (rb_obj_is_kind_of(argv[1], cgsl_complex)) {
    Data_Get_Struct(argv[1], gsl_complex, c);
    gsl_vector_set(v, 2*i, GSL_REAL(*c));
    gsl_vector_set(v, 2*i+1, GSL_IMAG(*c));
    return obj;
  }
  switch (TYPE(argv[1])) {
  case T_ARRAY:
    gsl_vector_set(v, 2*i, NUM2DBL(rb_ary_entry(argv[1], 0)));
    gsl_vector_set(v, 2*i+1, NUM2DBL(rb_ary_entry(argv[1], 1)));
    break;
  case T_FLOAT:
  case T_FIXNUM:
  case T_BIGNUM:
    gsl_vector_set(v, 2*i, NUM2DBL(argv[1]));
    break;
  default:
    rb_raise(rb_eTypeError, "wrong type arguments");
    break;
  }
  return obj;
}

static VALUE cparray_real(VALUE obj, VALUE ii)
{
  gsl_vector *v = NULL;
  CHECK_VECTOR(obj);
  CHECK_FIXNUM(ii);
  Data_Get_Struct(obj, gsl_vector, v);
  return rb_float_new(gsl_vector_get(v, 2*FIX2INT(ii)));
}

static VALUE cparray_re(VALUE obj)
{
  gsl_vector *v = NULL;
  gsl_vector_view *vv = NULL;
  Data_Get_Struct(obj, gsl_vector, v);
  vv = gsl_vector_view_alloc();
  *vv = gsl_vector_subvector_with_stride(v, 0, 2, v->size/2);
  return Data_Wrap_Struct(cgsl_vector_view, 0, gsl_vector_view_free, vv);
}

static VALUE cparray_im(VALUE obj)
{
  gsl_vector *v = NULL;
  gsl_vector_view *vv = NULL;
  Data_Get_Struct(obj, gsl_vector, v);
  vv = gsl_vector_view_alloc();
  *vv = gsl_vector_subvector_with_stride(v, 1, 2, v->size/2);
  return Data_Wrap_Struct(cgsl_vector_view, 0, gsl_vector_view_free, vv);
}

static VALUE cparray_set_real(VALUE obj, VALUE ii, VALUE val)
{
  gsl_vector *v = NULL;
  CHECK_VECTOR(obj);
  CHECK_FIXNUM(ii);
  Need_Float(val);
  Data_Get_Struct(obj, gsl_vector, v);
  gsl_vector_set(v, 2*FIX2INT(ii), NUM2DBL(val));
  return obj;
}

static VALUE cparray_imag(VALUE obj, VALUE ii)
{
  gsl_vector *v = NULL;
  CHECK_VECTOR(obj);
  CHECK_FIXNUM(ii);
  Data_Get_Struct(obj, gsl_vector, v);
  return rb_float_new(gsl_vector_get(v, 2*FIX2INT(ii)+1));
}

static VALUE cparray_set_imag(VALUE obj, VALUE ii, VALUE val)
{
  gsl_vector *v = NULL;
  CHECK_VECTOR(obj);
  CHECK_FIXNUM(ii);
  Need_Float(val);
  Data_Get_Struct(obj, gsl_vector, v);
  gsl_vector_set(v, 2*FIX2INT(ii)+1, NUM2DBL(val));
  return obj;
}

static VALUE get_cpary_stride_n(int argc, VALUE *argv, VALUE obj,
			       gsl_complex_packed_array *data, size_t *stride, size_t *n)
{
  gsl_vector *v = NULL;
  int itmp = 0;
  VALUE ary;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    if (obj == mgsl_fft_complex) {
      if (CLASS_OF(argv[0]) != cgsl_cparray)
	rb_raise(rb_eTypeError, "wrong argument type %s (expected PackedArray)",
		 rb_class2name(CLASS_OF(argv[0])));
    }
    CHECK_VECTOR(argv[0]);
    Data_Get_Struct(argv[0], gsl_vector, v);
    ary = argv[0];
    itmp = 1;
    break;
  default:
    CHECK_VECTOR(obj);
    Data_Get_Struct(obj, gsl_vector, v);
    ary = obj;
    itmp = 0;
    break;
  }
  *data = (gsl_complex_packed_array) v->data;

  switch (argc-itmp) {
  case 0:
    *stride = v->stride;
    *n = v->size/2;
    break;
  case 1:
    CHECK_FIXNUM(argv[itmp]);
    *n = FIX2INT(argv[itmp]);
    *stride = v->stride;
    break;
  default:
    CHECK_FIXNUM(argv[itmp]);
    CHECK_FIXNUM(argv[itmp+1]);
    *stride = FIX2INT(argv[itmp]);
    *n = FIX2INT(argv[itmp+1]);
    break;
  }
  return ary;
}

static void get_stride_n(int argc, VALUE *argv, int argstart, gsl_vector *v,
			 size_t *stride, size_t *n)
{
  switch (argc-argstart) {
  case 0:
    *stride = v->stride;
    *n = v->size;
    break;
  case 1:
    CHECK_FIXNUM(argv[argstart]);
    *stride = v->stride;
    *n = FIX2INT(argv[argstart]);
    break;
  default:
    CHECK_FIXNUM(argv[argstart]);
    CHECK_FIXNUM(argv[argstart+1]);
    *stride = FIX2INT(argv[argstart]);
    *n = FIX2INT(argv[argstart+1]);
    break;
  }
  return;
}

static VALUE rb_fft_complex_radix2(int argc, VALUE *argv, VALUE obj,
				   int (*trans)(gsl_complex_packed_array, 
						size_t, size_t), int flag)
{
  size_t stride, n;
  gsl_complex_packed_array data;
  gsl_vector *v;
  VALUE ary;
  ary = get_cpary_stride_n(argc, argv, obj, &data, &stride, &n);
  if (flag == RB_GSL_FFT_COPY) {
    v = gsl_vector_alloc(2*n);
    memcpy(v->data, data, sizeof(double)*2*n);
    (*trans)(v->data, stride, n);
    return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, v);
  } else { /* in-place */
    (*trans)(data, stride, n);
    return ary;
  }
}

static VALUE rb_gsl_fft_complex_radix2_forward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, gsl_fft_complex_radix2_forward,
			       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_radix2_forward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, gsl_fft_complex_radix2_forward,
			       RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_complex_radix2_transform(int argc, VALUE *argv, VALUE obj)
{
  size_t stride, n;
  gsl_complex_packed_array data;
  gsl_fft_direction sign;
  gsl_vector *v;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  get_cpary_stride_n(argc-1, argv, obj, &data, &stride, &n);
  v = gsl_vector_alloc(2*n);
  memcpy(v->data, data, sizeof(double)*2*n);
  gsl_fft_complex_radix2_transform(v->data, stride, n, sign);
  return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, v);
}

static VALUE rb_gsl_fft_complex_radix2_transform2(int argc, VALUE *argv, VALUE obj)
{
  size_t stride, n;
  gsl_complex_packed_array data;
  gsl_fft_direction sign;
  VALUE ary;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  ary = get_cpary_stride_n(argc-1, argv, obj, &data, &stride, &n);
  gsl_fft_complex_radix2_transform(data, stride, n, sign);
  return ary;
}

static VALUE rb_gsl_fft_complex_radix2_backward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, gsl_fft_complex_radix2_backward,
			       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_radix2_inverse(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, gsl_fft_complex_radix2_inverse,
			       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_radix2_dif_forward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, 
			       gsl_fft_complex_radix2_dif_forward,
			       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_radix2_backward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, gsl_fft_complex_radix2_backward,
			       RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_complex_radix2_inverse2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, gsl_fft_complex_radix2_inverse,
			       RB_GSL_FFT_INPLACE);
}


static VALUE rb_gsl_fft_complex_radix2_dif_forward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, 
			       gsl_fft_complex_radix2_dif_forward,
			       RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_complex_radix2_dif_transform(int argc, VALUE *argv, VALUE obj)
{
  size_t stride, n;
  gsl_complex_packed_array data;
  gsl_vector *v;
  gsl_fft_direction sign;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  get_cpary_stride_n(argc-1, argv, obj, &data, &stride, &n);
  v = gsl_vector_alloc(2*n);
  memcpy(v->data, data, sizeof(double)*2*n);
  gsl_fft_complex_radix2_dif_transform(v->data, stride, n, sign);
  return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, v);
}

/* in-place */
static VALUE rb_gsl_fft_complex_radix2_dif_transform2(int argc, VALUE *argv, VALUE obj)
{
  size_t stride, n;
  gsl_complex_packed_array data;
  gsl_fft_direction sign;
  VALUE ary;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  ary = get_cpary_stride_n(argc-1, argv, obj, &data, &stride, &n);
  gsl_fft_complex_radix2_dif_transform(data, stride, n, sign);
  return ary;
}

static VALUE rb_gsl_fft_complex_radix2_dif_backward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, 
			       gsl_fft_complex_radix2_dif_backward,
			       RB_GSL_FFT_COPY);
}
static VALUE rb_gsl_fft_complex_radix2_dif_inverse(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, 
			       gsl_fft_complex_radix2_dif_inverse,
			       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_radix2_dif_backward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, 
			       gsl_fft_complex_radix2_dif_backward,
			       RB_GSL_FFT_INPLACE);
}
static VALUE rb_gsl_fft_complex_radix2_dif_inverse2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_radix2(argc, argv, obj, 
			       gsl_fft_complex_radix2_dif_inverse,
			       RB_GSL_FFT_INPLACE);
}

static VALUE rb_GSL_FFT_Wavetable_new(VALUE klass, VALUE n)
{
  GSL_FFT_Wavetable *table;
  CHECK_FIXNUM(n);
  table = (GSL_FFT_Wavetable *) gsl_fft_complex_wavetable_alloc(FIX2INT(n));
  return Data_Wrap_Struct(klass, 0, GSL_FFT_Wavetable_free, table);
}

static VALUE rb_GSL_FFT_Wavetable_n(VALUE obj)
{
  GSL_FFT_Wavetable *table;
  Data_Get_Struct(obj, GSL_FFT_Wavetable, table);
  return INT2FIX(table->n);
}

static VALUE rb_GSL_FFT_Wavetable_nf(VALUE obj)
{
  GSL_FFT_Wavetable *table;
  Data_Get_Struct(obj, GSL_FFT_Wavetable, table);
  return INT2FIX(table->nf);
}

static VALUE rb_GSL_FFT_Wavetable_factor(VALUE obj)
{
  GSL_FFT_Wavetable *table;
  gsl_permutation *p;
  size_t i;
  Data_Get_Struct(obj, GSL_FFT_Wavetable, table);
  p = gsl_permutation_alloc(64);
  for (i = 0; i < table->nf; i++) p->data[i] = table->factor[i];
  return Data_Wrap_Struct(cgsl_fft_wavetable_factor, 0, gsl_permutation_free, p);
}

static VALUE rb_GSL_FFT_Workspace_new(VALUE klass, VALUE n)
{
  GSL_FFT_Workspace *space;
  CHECK_FIXNUM(n);
  space = GSL_FFT_Workspace_new(FIX2INT(n));
  return Data_Wrap_Struct(klass, 0, GSL_FFT_Workspace_free, space);
}

enum {
  NONE_OF_TWO = 0,
  ALLOC_SPACE = 1,
  ALLOC_TABLE = 2,
  BOTH_OF_TWO = 3,
} FFTComplexStructAllocFlag;

static void gsl_fft_free(int flag, GSL_FFT_Wavetable *table,
			 GSL_FFT_Workspace *space);
static int gsl_fft_get_argv(int argc, VALUE *argv, VALUE obj,
			    gsl_complex_packed_array *data, size_t *stride,
			    size_t *n, gsl_fft_complex_wavetable **table,
			    gsl_fft_complex_workspace **space);

static int gsl_fft_get_argv(int argc, VALUE *argv, VALUE obj,
			    gsl_complex_packed_array *data, size_t *stride,
			    size_t *n, gsl_fft_complex_wavetable **table,
			    gsl_fft_complex_workspace **space)
{
  int flag = NONE_OF_TWO, flagtmp, i, itmp = argc, itmp2 = 0, ccc;
  int flagw = 0;
  gsl_vector *v = NULL;

  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    if (obj == mgsl_fft_complex) {
      if (CLASS_OF(argv[0]) != cgsl_cparray)
	rb_raise(rb_eTypeError, "wrong argument type %s (expected PackedArray)",
		 rb_class2name(CLASS_OF(argv[0])));
    }
    CHECK_VECTOR(argv[0]);
    Data_Get_Struct(argv[0], gsl_vector, v);
    itmp2 = 1;
    break;
  default:
    CHECK_VECTOR(obj);
    Data_Get_Struct(obj, gsl_vector, v);
    break;
  }
  ccc = argc;
  flagtmp = 0;
  flagw = 0;
  for (i = argc-1; i >= itmp2; i--) {
    if (rb_obj_is_kind_of(argv[i], cgsl_fft_workspace)
	|| rb_obj_is_kind_of(argv[i], cgsl_fft_complex_workspace)) {
      Data_Get_Struct(argv[i], gsl_fft_complex_workspace, *space);
      flagtmp = 1;
      flagw = 1;
      itmp = i;
      ccc--;
      break;
    }
  }
  flagtmp = 0;
  for (i = itmp-1; i >= itmp2; i--) {
    if (rb_obj_is_kind_of(argv[i], cgsl_fft_wavetable)
	|| rb_obj_is_kind_of(argv[i], cgsl_fft_complex_wavetable)) {
      Data_Get_Struct(argv[i], gsl_fft_complex_wavetable, *table);
      flagtmp = 1;
      ccc--;
      break;
    }
  }
  get_cpary_stride_n(ccc, argv, obj, data, stride, n);
  if (flagw == 0) {
    *space = gsl_fft_complex_workspace_alloc(*n);
    flag += ALLOC_SPACE;
  }
  if (flagtmp == 0) {
    *table = gsl_fft_complex_wavetable_alloc(*n);
    flag += ALLOC_TABLE;
  }
  if (*table == NULL) {
    rb_raise(rb_eRuntimeError, "something wrong with wavetable");
  }
  if (*space == NULL) {
    rb_raise(rb_eRuntimeError, "something wrong with workspace");
  }
  return flag;
}

static int gsl_fft_get_argv2(int argc, VALUE *argv, VALUE obj,
			     double **ptr, size_t *stride,
			     size_t *n, gsl_fft_real_wavetable **table,
			     gsl_fft_real_workspace **space, int *naflag)
{
  int flag = NONE_OF_TWO, flagtmp, i, itmp = argc, itmp2 = 0, ccc;
  int flagw = 0;
  *naflag = 0;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    if (obj == mgsl_fft_complex) {
      if (CLASS_OF(argv[0]) != cgsl_cparray)
	rb_raise(rb_eTypeError, "wrong argument type %s (expected PackedArray)",
		 rb_class2name(CLASS_OF(argv[0])));
    }
    *ptr = get_ptr_double3(argv[0], n, stride, naflag);
    itmp2 = 1;
    break;
  default:
    *ptr = get_ptr_double3(obj, n, stride, naflag);
    break;
  }
  ccc = argc;
  flagtmp = 0;
  flagw = 0;
  for (i = argc-1; i >= itmp2; i--) {
    if (rb_obj_is_kind_of(argv[i], cgsl_fft_real_workspace)) {
      Data_Get_Struct(argv[i], gsl_fft_real_workspace, *space);
      flagtmp = 1;
      flagw = 1;
      itmp = i;
      ccc--;
      break;
    }
  }
  flagtmp = 0;
  for (i = itmp-1; i >= itmp2; i--) {
    if (rb_obj_is_kind_of(argv[i], cgsl_fft_real_wavetable)) {
      Data_Get_Struct(argv[i], gsl_fft_real_wavetable, *table);
      flagtmp = 1;
      ccc--;
      break;
    }
  }
  if (flagw == 0) {
    *space = gsl_fft_real_workspace_alloc(*n);
    flag += ALLOC_SPACE;
  }
  if (flagtmp == 0) {
    *table = gsl_fft_real_wavetable_alloc(*n);
    flag += ALLOC_TABLE;
  }
  if (*table == NULL) {
    rb_raise(rb_eRuntimeError, "something wrong with wavetable");
  }
  if (*space == NULL) {
    rb_raise(rb_eRuntimeError, "something wrong with workspace");
  }
  return flag;
}

static int gsl_fft_get_argv3(int argc, VALUE *argv, VALUE obj,
			   double **ptr, size_t *stride,
			    size_t *n, gsl_fft_halfcomplex_wavetable **table,
			    gsl_fft_real_workspace **space, int *naflag)
{
  int flag = NONE_OF_TWO, flagtmp, i, itmp = argc, itmp2 = 0, ccc;
  int flagw = 0;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    if (obj == mgsl_fft_complex) {
      if (CLASS_OF(argv[0]) != cgsl_cparray)
	rb_raise(rb_eTypeError, "wrong argument type %s (expected PackedArray)",
		 rb_class2name(CLASS_OF(argv[0])));
    }
    *ptr = get_ptr_double3(argv[0], n, stride, naflag);
    itmp2 = 1;
    break;
  default:
    *ptr = get_ptr_double3(obj, n, stride, naflag);
    break;
  }
  ccc = argc;
  flagtmp = 0;
  flagw = 0;
  for (i = argc-1; i >= itmp2; i--) {
    if (rb_obj_is_kind_of(argv[i], cgsl_fft_real_workspace)) {
      Data_Get_Struct(argv[i], gsl_fft_real_workspace, *space);
      flagtmp = 1;
      flagw = 1;
      itmp = i;
      ccc--;
      break;
    }
  }
  flagtmp = 0;
  for (i = itmp-1; i >= itmp2; i--) {
    if (rb_obj_is_kind_of(argv[i], cgsl_fft_halfcomplex_wavetable)) {
      Data_Get_Struct(argv[i], gsl_fft_halfcomplex_wavetable, *table);
      flagtmp = 1;
      ccc--;
      break;
    }
  }
  if (flagw == 0) {
    *space = gsl_fft_real_workspace_alloc(*n);
    flag += ALLOC_SPACE;
  }
  if (flagtmp == 0) {
    *table = gsl_fft_halfcomplex_wavetable_alloc(*n);
    flag += ALLOC_TABLE;
  }
  if (*table == NULL) {
    rb_raise(rb_eRuntimeError, "something wrong with wavetable");
  }
  if (*space == NULL) {
    rb_raise(rb_eRuntimeError, "something wrong with workspace");
  }
  return flag;
}

static void gsl_fft_free(int flag, GSL_FFT_Wavetable *table,
			 GSL_FFT_Workspace *space)
{
  switch (flag) {
  case ALLOC_TABLE:
    GSL_FFT_Wavetable_free(table);
    break;
  case ALLOC_SPACE:
    GSL_FFT_Workspace_free(space);
    break;
  case BOTH_OF_TWO:
    GSL_FFT_Wavetable_free(table);
    GSL_FFT_Workspace_free(space);
    break;
  default:
    /* never happens */
    break;
  }
}

static VALUE rb_gsl_fft_getary(int argc, VALUE *argv, VALUE obj)
{
  switch (TYPE(obj)) {
  case T_MODULE: case T_CLASS: case T_OBJECT:
    return argv[0];
    break;
  default:
    return obj;
    break;
  }
}

static VALUE rb_fft_complex_trans(int argc, VALUE *argv, VALUE obj,
				  int (*transform)(gsl_complex_packed_array, 
						   size_t, size_t, 
						   const gsl_fft_complex_wavetable *,
						   gsl_fft_complex_workspace *),
				  int sss)
{
  int flag = 0, status;
  size_t stride, n;
  gsl_complex_packed_array data;
  gsl_vector *v;
  gsl_fft_complex_wavetable *table = NULL;
  gsl_fft_complex_workspace *space = NULL;
  flag = gsl_fft_get_argv(argc, argv, obj, &data, &stride, &n, &table, &space);
  if (sss == RB_GSL_FFT_COPY) {
    v = gsl_vector_alloc(2*n);
    memcpy(v->data, data, sizeof(double)*2*n);
    status = (*transform)(v->data, stride, n, table, space);
    gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
    return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, v);
  } else {    /* in-place */
    status = (*transform)(data, stride, n, table, space);
    gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
    return rb_gsl_fft_getary(argc, argv, obj);
  }
}

static VALUE rb_gsl_fft_complex_forward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_trans(argc, argv, obj, gsl_fft_complex_forward,
			      RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_forward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_trans(argc, argv, obj, gsl_fft_complex_forward,
			      RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_complex_transform(int argc, VALUE *argv, VALUE obj)
{
  int flag = 0, status;
  size_t stride, n;
  gsl_vector *v;
  gsl_fft_direction sign;
  gsl_complex_packed_array data;
  gsl_fft_complex_wavetable *table = NULL;
  gsl_fft_complex_workspace *space = NULL;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  flag = gsl_fft_get_argv(argc-1, argv, obj, &data, &stride, &n, &table, &space);
  v = gsl_vector_alloc(2*n);
  memcpy(v->data, data, sizeof(double)*2*n);
  status = gsl_fft_complex_transform(v->data, stride, n, table, space, sign);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
  return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, v);
}

/* in-place */
static VALUE rb_gsl_fft_complex_transform2(int argc, VALUE *argv, VALUE obj)
{
  int flag = 0, status;
  size_t stride, n;
  gsl_fft_direction sign;
  gsl_complex_packed_array data;
  gsl_fft_complex_wavetable *table = NULL;
  gsl_fft_complex_workspace *space = NULL;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  flag = gsl_fft_get_argv(argc-1, argv, obj, &data, &stride, &n, &table, &space);
  status = gsl_fft_complex_transform(data, stride, n, table, space, sign);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
  return rb_gsl_fft_getary(argc, argv, obj);
}

static VALUE rb_gsl_fft_complex_backward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_trans(argc, argv, obj, gsl_fft_complex_backward,
			      RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_backward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_trans(argc, argv, obj, gsl_fft_complex_backward,
			      RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_complex_inverse(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_trans(argc, argv, obj, gsl_fft_complex_inverse,
			      RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_complex_inverse2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_complex_trans(argc, argv, obj, gsl_fft_complex_inverse,
			      RB_GSL_FFT_INPLACE);
}

static VALUE get_ptr_stride_n(int argc, VALUE *argv, VALUE obj,
			     double **ptr, size_t *stride, size_t *n, int *flag)
{
  int itmp = 0;
  VALUE ary;
  *flag = 0;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    if (obj == mgsl_fft_complex) {
      if (CLASS_OF(argv[0]) != cgsl_cparray)
	rb_raise(rb_eTypeError, "wrong argument type %s (expected PackedArray)",
		 rb_class2name(CLASS_OF(argv[0])));
    }
    *ptr = get_ptr_double3(argv[0], n, stride, flag);
    itmp = 1;
    ary = argv[0];
    break;
  default:
    *ptr = get_ptr_double3(obj, n, stride, flag);
    ary = obj;
    itmp = 0;
    break;
  }
  switch (argc-itmp) {
  case 0:
    /* do nothing */
    break;
  case 1:
    CHECK_FIXNUM(argv[itmp]);
    *n = FIX2INT(argv[itmp]);
    break;
  default:
    CHECK_FIXNUM(argv[itmp]);
    CHECK_FIXNUM(argv[itmp+1]);
    *stride = FIX2INT(argv[itmp]);
    *n = FIX2INT(argv[itmp+1]);
    break;
  }
  return ary;
}

static VALUE rb_fft_radix2(int argc, VALUE *argv, VALUE obj,
			   int (*trans)(double [], size_t, size_t),
			   int sss)
{
  size_t stride, n;
  gsl_vector *vnew;
  gsl_vector_view vv;
  double *ptr1, *ptr2;
  int flag;
#ifdef HAVE_NARRAY_H
  int shape[1];
#endif
  VALUE ary;
  get_ptr_stride_n(argc, argv, obj, &ptr1, &stride, &n, &flag);
  if (flag == 0) {
    if (sss == RB_GSL_FFT_COPY) {
      vnew = gsl_vector_alloc(n);
      vv.vector.data = ptr1;
      vv.vector.stride = stride;
      vv.vector.size = n;
      gsl_vector_memcpy(vnew, &vv.vector);
      ptr2 = vnew->data;
      ary = Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    } else {
      ary = rb_gsl_fft_getary(argc, argv, obj);
      ptr2 = ptr1;
    }
#ifdef HAVE_NARRAY_H
  } else if (flag == 1) {
    if (sss == RB_GSL_FFT_COPY) {
      shape[0] = n;
      ary = na_make_object(NA_DFLOAT, 1, shape, cNArray);
      ptr2 = NA_PTR_TYPE(ary, double*);
      memcpy(ptr2, ptr1, sizeof(double)*n);
    } else {
      ary = rb_gsl_fft_getary(argc, argv, obj);
      ptr2 = NA_PTR_TYPE(ary, double*);
    }
#endif
  } else {
    rb_raise(rb_eRuntimeError, "something wrong");
  }
  (*trans)(ptr2, stride, n);
  return ary;
}

static void rb_fft_radix2_check_arg(int argc, VALUE obj)
{
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc != 1 && argc != 3) rb_raise(rb_eArgError, 
					 "wrong number of arguments (%d for 1 or 3)", argc);
    break;
  default:
    /*    CHECK_VECTOR(obj);*/
    if (argc != 0 && argc != 2) rb_raise(rb_eArgError, 
					 "wrong number of arguments (%d for 0 or 2)", argc);
    break;
  }
  return;
}

static VALUE rb_gsl_fft_real_radix2_transform(int argc, VALUE *argv, VALUE obj)
{
  rb_fft_radix2_check_arg(argc, obj);
  return rb_fft_radix2(argc, argv, obj, gsl_fft_real_radix2_transform,
		       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_real_radix2_transform2(int argc, VALUE *argv, VALUE obj)
{
  rb_fft_radix2_check_arg(argc, obj);
  return rb_fft_radix2(argc, argv, obj, gsl_fft_real_radix2_transform,
		       RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_halfcomplex_radix2_inverse(int argc, VALUE *argv, VALUE obj)
{
  rb_fft_radix2_check_arg(argc, obj);
  return rb_fft_radix2(argc, argv, obj, gsl_fft_halfcomplex_radix2_inverse,
		       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_halfcomplex_radix2_inverse2(int argc, VALUE *argv, VALUE obj)
{
  rb_fft_radix2_check_arg(argc, obj);
  return rb_fft_radix2(argc, argv, obj, gsl_fft_halfcomplex_radix2_inverse,
		       RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_halfcomplex_radix2_backward(int argc, VALUE *argv, VALUE obj)
{
  rb_fft_radix2_check_arg(argc, obj);
  return rb_fft_radix2(argc, argv, obj, gsl_fft_halfcomplex_radix2_backward,
		       RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_halfcomplex_radix2_backward2(int argc, VALUE *argv, VALUE obj)
{
  rb_fft_radix2_check_arg(argc, obj);
  return rb_fft_radix2(argc, argv, obj, gsl_fft_halfcomplex_radix2_backward,
		       RB_GSL_FFT_INPLACE);
}

/*****/

static VALUE rb_fft_real_trans(int argc, VALUE *argv, VALUE obj,
			       int (*trans)(double [], size_t, size_t, 
					    const gsl_fft_real_wavetable *, 
					    gsl_fft_real_workspace *),
			       int sss)
{
  int flag = 0, status, naflag = 0;
  size_t stride, n;
  gsl_vector *vnew;
  gsl_vector_view vv;
  double *ptr1, *ptr2;
#ifdef HAVE_NARRAY_H
  int shape[1];
#endif
  gsl_fft_real_wavetable *table = NULL;
  gsl_fft_real_workspace *space = NULL;
  VALUE ary;
  flag = gsl_fft_get_argv2(argc, argv, obj, &ptr1, &stride, &n, &table, &space, &naflag);
  if (naflag == 0) {
    if (sss == RB_GSL_FFT_COPY) {
      vnew = gsl_vector_alloc(n);
      vv.vector.data = ptr1;
      vv.vector.stride = stride;
      vv.vector.size = n;
      gsl_vector_memcpy(vnew, &vv.vector);
      ptr2 = vnew->data;
      ary = Data_Wrap_Struct(cgsl_vector_halfcomplex, 0, gsl_vector_free, vnew);
    } else {
      ptr2 = ptr1;
      ary = rb_gsl_fft_getary(argc, argv, obj);
    }
#ifdef HAVE_NARRAY_H
  } else if (naflag == 1) {
    if (sss == RB_GSL_FFT_COPY) {
      shape[0] = n;
      ary = na_make_object(NA_DFLOAT, 1, shape, cNArray);
      ptr2 = NA_PTR_TYPE(ary, double*);
      memcpy(ptr2, ptr1, sizeof(double)*n);
    } else {
      ptr2 = ptr1;
      ary = rb_gsl_fft_getary(argc, argv, obj);
    }
#endif
  } else {
    rb_raise(rb_eRuntimeError, "something wrong");
  }
  status = (*trans)(ptr2, stride, n, table, space);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
return ary;
}

static VALUE rb_gsl_fft_real_transform(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_real_trans(argc, argv, obj, gsl_fft_real_transform,
			   RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_real_transform2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_real_trans(argc, argv, obj, gsl_fft_real_transform,
			   RB_GSL_FFT_INPLACE);
}

static VALUE rb_fft_halfcomplex_trans(int argc, VALUE *argv, VALUE obj,
				      int (*trans)(double [], size_t, size_t, 
						   const gsl_fft_halfcomplex_wavetable *, gsl_fft_real_workspace *),
				      int sss)
{
  int flag = 0, status, naflag = 0;
  size_t stride, n;
  gsl_vector *vnew;
  gsl_vector_view vv;
  double *ptr1, *ptr2;
#ifdef HAVE_NARRAY_H
  int shape[1];
#endif
  gsl_fft_halfcomplex_wavetable *table = NULL;
  gsl_fft_real_workspace *space = NULL;
  VALUE ary;
  flag = gsl_fft_get_argv3(argc, argv, obj, &ptr1, &stride, &n, 
			   &table, &space, &naflag);
  if (naflag == 0) {
    if (sss == RB_GSL_FFT_COPY) {
      vnew = gsl_vector_alloc(n);
      vv.vector.data = ptr1;
      vv.vector.stride = stride;
      vv.vector.size = n;
      gsl_vector_memcpy(vnew, &vv.vector);
      ptr2 = vnew->data;
      ary = Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
    } else {
      ptr2 = ptr1;
      ary = rb_gsl_fft_getary(argc, argv, obj);
    }
#ifdef HAVE_NARRAY_H
  } else if (naflag == 1) {
    if (sss == RB_GSL_FFT_COPY) {
      shape[0] = n;
      ary = na_make_object(NA_DFLOAT, 1, shape, cNArray);
      ptr2 = NA_PTR_TYPE(ary, double*);
      memcpy(ptr2, ptr1, sizeof(double)*n);
    } else {
      ptr2 = ptr1;
      ary = rb_gsl_fft_getary(argc, argv, obj);
    }
#endif
  } else {
    rb_raise(rb_eRuntimeError, "something wrong");
  }
  status = (*trans)(ptr2, stride, n, table, space);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
  return ary;
}

static VALUE rb_gsl_fft_halfcomplex_transform(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_halfcomplex_trans(argc, argv, obj, 
				  gsl_fft_halfcomplex_transform,
				  RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_halfcomplex_transform2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_halfcomplex_trans(argc, argv, obj, 
				  gsl_fft_halfcomplex_transform,
				  RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_halfcomplex_backward(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_halfcomplex_trans(argc, argv, obj, 
				  gsl_fft_halfcomplex_backward,
				  RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_halfcomplex_backward2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_halfcomplex_trans(argc, argv, obj, 
				  gsl_fft_halfcomplex_backward,
				  RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_halfcomplex_inverse(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_halfcomplex_trans(argc, argv, obj, 
				  gsl_fft_halfcomplex_inverse,
				  RB_GSL_FFT_COPY);
}

static VALUE rb_gsl_fft_halfcomplex_inverse2(int argc, VALUE *argv, VALUE obj)
{
  return rb_fft_halfcomplex_trans(argc, argv, obj, 
				  gsl_fft_halfcomplex_inverse,
				  RB_GSL_FFT_INPLACE);
}

static VALUE rb_gsl_fft_real_unpack(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v, *cpary;
  size_t stride, n;

  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    CHECK_VECTOR(argv[0]);
    Data_Get_Struct(argv[0], gsl_vector, v);
    get_stride_n(argc-1, argv, 1, v, &stride, &n);
    break;
  default:
    CHECK_VECTOR(obj);
    Data_Get_Struct(obj, gsl_vector, v);
    get_stride_n(argc, argv, 0, v, &stride, &n);
    break;
  }
  cpary = gsl_vector_alloc(2*n);
  gsl_fft_real_unpack(v->data, (gsl_complex_packed_array) cpary->data, stride, n);
  return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, cpary);
}

static VALUE rb_gsl_fft_halfcomplex_unpack(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v, *cpary;
  size_t stride, n;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for >= 1)",
			   argc);
    CHECK_VECTOR(argv[0]);
    Data_Get_Struct(argv[0], gsl_vector, v);
    get_stride_n(argc-1, argv, 1, v, &stride, &n);
    break;
  default:
    Data_Get_Struct(obj, gsl_vector, v);
    get_stride_n(argc, argv, 0, v, &stride, &n);
    break;
  }
  cpary = gsl_vector_alloc(2*n);
  gsl_fft_halfcomplex_unpack(v->data, (gsl_complex_packed_array) cpary->data, stride, n);
  return Data_Wrap_Struct(cgsl_cparray, 0, gsl_vector_free, cpary);
}

/* Convert a halfcomplex data to Numerical Recipes style */
static VALUE rb_gsl_fft_halfcomplex_to_nrc(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v, *vnew;
  size_t i, k;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc != 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)",
			    argc);
    CHECK_VECTOR(argv[0]);
    Data_Get_Struct(argv[0], gsl_vector, v);
    break;
  default:
    if (argc != 0) rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)",
			    argc);
    CHECK_VECTOR(obj);
    Data_Get_Struct(obj, gsl_vector, v);
    break;
  }
  vnew = gsl_vector_alloc(v->size);
  gsl_vector_set(vnew, 0, gsl_vector_get(v, 0));  /* DC */
  gsl_vector_set(vnew, 1, gsl_vector_get(v, v->size/2));  /* Nyquist freq */
  for (i = 2, k = 1; i < vnew->size; i+=2, k++) {
    gsl_vector_set(vnew, i, gsl_vector_get(v, k));
    gsl_vector_set(vnew, i+1, -gsl_vector_get(v, v->size-k));
  }
  return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
}

void Init_gsl_fft(VALUE module)
{
  VALUE mgsl_fft_complex_radix2, mgsl_fft_real_radix2;
  VALUE mgsl_fft_halfcomplex_radix2;

  mgsl_fft = rb_define_module_under(module, "FFT");
  mgsl_fft_complex = rb_define_module_under(mgsl_fft, "Complex");
  mgsl_fft_complex_radix2 = rb_define_module_under(mgsl_fft_complex, "Radix2");
  mgsl_fft_real = rb_define_module_under(mgsl_fft, "Real");
  mgsl_fft_real_radix2 = rb_define_module_under(mgsl_fft_real, "Radix2");
  mgsl_fft_halfcomplex = rb_define_module_under(mgsl_fft, "HalfComplex");
  mgsl_fft_halfcomplex_radix2 = rb_define_module_under(mgsl_fft_halfcomplex, "Radix2");

  cgsl_vector_halfcomplex = rb_define_class_under(cgsl_vector, "HalfComplex",
						  cgsl_vector);

  /*****/

  rb_define_const(mgsl_fft, "Forward", INT2FIX(forward));
  rb_define_const(mgsl_fft, "FORWARD", INT2FIX(forward));
  rb_define_const(mgsl_fft, "Backward", INT2FIX(backward));
  rb_define_const(mgsl_fft, "BACKWARD", INT2FIX(backward));

  cgsl_cparray = rb_define_class_under(mgsl_fft_complex, "PackedArray", cgsl_vector);

  rb_define_singleton_method(cgsl_cparray, "alloc", rb_gsl_vector_new, -1);

  rb_define_method(cgsl_cparray, "get", cparray_get, 1);
  rb_define_alias(cgsl_cparray, "[]", "get");
  rb_define_method(cgsl_cparray, "real", cparray_real, 1);
  rb_define_method(cgsl_cparray, "re", cparray_re, 0);
  rb_define_method(cgsl_cparray, "imag", cparray_imag, 1);
  rb_define_method(cgsl_cparray, "im", cparray_im, 0);
  rb_define_method(cgsl_cparray, "set", cparray_set, -1);
  rb_define_alias(cgsl_cparray, "[]=", "set");
  rb_define_method(cgsl_cparray, "set_real", cparray_set_real, 2);
  rb_define_method(cgsl_cparray, "set_imag", cparray_set_imag, 2);

  rb_define_method(cgsl_cparray, "radix2_forward", 
		   rb_gsl_fft_complex_radix2_forward, -1);
  rb_define_method(cgsl_cparray, "radix2_transform", 
		   rb_gsl_fft_complex_radix2_transform, -1);
  rb_define_method(cgsl_cparray, "radix2_backward", 
		   rb_gsl_fft_complex_radix2_backward, -1);
  rb_define_method(cgsl_cparray, "radix2_inverse", 
		   rb_gsl_fft_complex_radix2_inverse, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_forward", 
		   rb_gsl_fft_complex_radix2_dif_forward, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_transform", 
		   rb_gsl_fft_complex_radix2_dif_transform, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_backward", 
		   rb_gsl_fft_complex_radix2_dif_backward, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_inverse", 
		   rb_gsl_fft_complex_radix2_dif_inverse, -1);

  rb_define_method(cgsl_cparray, "radix2_forward!", 
		   rb_gsl_fft_complex_radix2_forward2, -1);
  rb_define_method(cgsl_cparray, "radix2_transform!",
		   rb_gsl_fft_complex_radix2_transform2, -1);
  rb_define_method(cgsl_cparray, "radix2_backward!", 
		   rb_gsl_fft_complex_radix2_backward2, -1);
  rb_define_method(cgsl_cparray, "radix2_inverse!", 
		   rb_gsl_fft_complex_radix2_inverse2, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_forward!", 
		   rb_gsl_fft_complex_radix2_dif_forward2, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_transform!",
		   rb_gsl_fft_complex_radix2_dif_transform2, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_backward!", 
		   rb_gsl_fft_complex_radix2_dif_backward2, -1);
  rb_define_method(cgsl_cparray, "radix2_dif_inverse!", 
		   rb_gsl_fft_complex_radix2_dif_inverse2, -1);

  cgsl_fft_wavetable = rb_define_class_under(mgsl_fft, "Wavetable", cGSL_Object);
  rb_define_singleton_method(cgsl_fft_wavetable, "alloc",
			     rb_GSL_FFT_Wavetable_new, 1);
  rb_define_method(cgsl_fft_wavetable, "n",
			     rb_GSL_FFT_Wavetable_n, 0);
  rb_define_method(cgsl_fft_wavetable, "nf",
			     rb_GSL_FFT_Wavetable_nf, 0);
  rb_define_method(cgsl_fft_wavetable, "factor",
		   rb_GSL_FFT_Wavetable_factor, 0);
  cgsl_fft_wavetable_factor = rb_define_class_under(mgsl_fft, 
						    "WavetableFactor", 
						    cgsl_permutation);

  cgsl_fft_complex_wavetable = rb_define_class_under(mgsl_fft_complex, "Wavetable",
						     cgsl_fft_wavetable);
  rb_define_singleton_method(cgsl_fft_complex_wavetable, "alloc",
			     rb_gsl_fft_complex_wavetable_new, 1);

  cgsl_fft_workspace = rb_define_class_under(mgsl_fft, "Workspace", cGSL_Object);
  rb_define_singleton_method(cgsl_fft_workspace, "alloc",
			     rb_GSL_FFT_Workspace_new, 1);

  cgsl_fft_complex_workspace = rb_define_class_under(mgsl_fft_complex, "Workspace", 
						     cgsl_fft_workspace);
  rb_define_singleton_method(cgsl_fft_complex_workspace, "alloc",
			     rb_gsl_fft_complex_workspace_new, 1);

  rb_define_method(cgsl_cparray, "forward", rb_gsl_fft_complex_forward, -1);
  rb_define_method(cgsl_cparray, "transform", rb_gsl_fft_complex_transform, -1);
  rb_define_method(cgsl_cparray, "backward", rb_gsl_fft_complex_backward, -1);
  rb_define_method(cgsl_cparray, "inverse", rb_gsl_fft_complex_inverse, -1);

  rb_define_method(cgsl_cparray, "forward!", rb_gsl_fft_complex_forward2, -1);
  rb_define_method(cgsl_cparray, "transform!", rb_gsl_fft_complex_transform2, -1);
  rb_define_method(cgsl_cparray, "backward!", rb_gsl_fft_complex_backward2, -1);
  rb_define_method(cgsl_cparray, "inverse!", rb_gsl_fft_complex_inverse2, -1);

  /*****/

  rb_define_method(cgsl_vector, "real_radix2_transform", 
		   rb_gsl_fft_real_radix2_transform, -1);
  rb_define_alias(cgsl_vector, "radix2_transform", "real_radix2_transform");
  rb_define_alias(cgsl_vector, "radix2_forward", "real_radix2_transform");
  rb_define_method(cgsl_vector, "real_radix2_inverse", 
		   rb_gsl_fft_halfcomplex_radix2_inverse, -1);
  rb_define_alias(cgsl_vector, "radix2_inverse", "real_radix2_inverse");
  rb_define_alias(cgsl_vector, "halfcomplex_radix2_inverse", 
		  "real_radix2_inverse");
  rb_define_method(cgsl_vector, "real_radix2_backward", 
		   rb_gsl_fft_halfcomplex_radix2_backward, -1);
  rb_define_alias(cgsl_vector, "radix2_backward", "real_radix2_backward");
  rb_define_alias(cgsl_vector, "halfcomplex_radix2_backward", 
		  "real_radix2_backward");


  rb_define_method(cgsl_vector, "real_radix2_transform!", 
		   rb_gsl_fft_real_radix2_transform2, -1);
  rb_define_alias(cgsl_vector, "radix2_transform!", "real_radix2_transform!");
  rb_define_alias(cgsl_vector, "radix2_forward!", "real_radix2_transform!");
  rb_define_method(cgsl_vector, "real_radix2_inverse!", 
		   rb_gsl_fft_halfcomplex_radix2_inverse2, -1);
  rb_define_alias(cgsl_vector, "radix2_inverse!", "real_radix2_inverse!");
  rb_define_alias(cgsl_vector, "halfcomplex_radix2_inverse!", 
		  "real_radix2_inverse!");
  rb_define_method(cgsl_vector, "real_radix2_backward!", 
		   rb_gsl_fft_halfcomplex_radix2_backward2, -1);
  rb_define_alias(cgsl_vector, "radix2_backward!", "real_radix2_backward!");
  rb_define_alias(cgsl_vector, "halfcomplex_radix2_backward!", 
		  "real_radix2_backward!");

  /*****/
  cgsl_fft_real_wavetable = rb_define_class_under(mgsl_fft_real, "Wavetable", 
						  cgsl_fft_wavetable);
  rb_define_singleton_method(cgsl_fft_real_wavetable, "alloc",
			     rb_gsl_fft_real_wavetable_new, 1);

  cgsl_fft_halfcomplex_wavetable = rb_define_class_under(mgsl_fft_halfcomplex, 
							 "Wavetable", cgsl_fft_wavetable);
  rb_define_singleton_method(cgsl_fft_halfcomplex_wavetable, "alloc",
			     rb_gsl_fft_halfcomplex_wavetable_new, 1);

  /*****/
  cgsl_fft_real_workspace = rb_define_class_under(mgsl_fft_real, "Workspace", 
						  cgsl_fft_workspace);
  rb_define_singleton_method(cgsl_fft_real_workspace, "alloc",
			     rb_gsl_fft_real_workspace_new, 1);

  /*****/
  rb_define_method(cgsl_vector, "real_transform", rb_gsl_fft_real_transform, -1);
  rb_define_alias(cgsl_vector, "transform", "real_transform");
  rb_define_alias(cgsl_vector, "forward", "real_transform");
  rb_define_alias(cgsl_vector, "fft_forward", "real_transform");
  rb_define_alias(cgsl_vector, "fft", "real_transform");
  rb_define_method(cgsl_vector, "halfcomplex_transform", 
		   rb_gsl_fft_halfcomplex_transform, -1);
  rb_define_method(cgsl_vector, "halfcomplex_backward", 
		   rb_gsl_fft_halfcomplex_backward, -1);
  rb_define_alias(cgsl_vector, "backward", "halfcomplex_backward");
  rb_define_alias(cgsl_vector, "fft_backward", "halfcomplex_backward");
  rb_define_method(cgsl_vector, "halfcomplex_inverse", 
		   rb_gsl_fft_halfcomplex_inverse, -1); 
  rb_define_alias(cgsl_vector, "fft_inverse", "halfcomplex_inverse");
  rb_define_alias(cgsl_vector, "ifft", "halfcomplex_inverse");
  rb_define_alias(cgsl_vector, "inverse", "halfcomplex_inverse");

  rb_define_method(cgsl_vector, "real_transform!", rb_gsl_fft_real_transform2, -1);
  rb_define_alias(cgsl_vector, "transform!", "real_transform!");
  rb_define_alias(cgsl_vector, "forward!", "real_transform!");
  rb_define_alias(cgsl_vector, "fft_forward!", "real_transform!");
  rb_define_alias(cgsl_vector, "fft!", "real_transform!");
  rb_define_method(cgsl_vector, "halfcomplex_transform!", 
		   rb_gsl_fft_halfcomplex_transform2, -1);
  rb_define_method(cgsl_vector, "halfcomplex_backward!",
		   rb_gsl_fft_halfcomplex_backward2, -1);
  rb_define_alias(cgsl_vector, "backward!", "halfcomplex_backward!");
  rb_define_alias(cgsl_vector, "fft_backward!", "halfcomplex_backward!");
  rb_define_method(cgsl_vector, "halfcomplex_inverse!", 
		   rb_gsl_fft_halfcomplex_inverse2, -1); 
  rb_define_alias(cgsl_vector, "fft_inverse!", "halfcomplex_inverse!");
  rb_define_alias(cgsl_vector, "ifft!", "halfcomplex_inverse!");
  rb_define_alias(cgsl_vector, "inverse!", "halfcomplex_inverse!");

  /***/
  rb_define_method(cgsl_vector, "fft_real_unpack", rb_gsl_fft_real_unpack, -1);
  rb_define_alias(cgsl_vector, "real_unpack", "fft_real_unpack");
  rb_define_method(cgsl_vector, "fft_halfcomplex_unpack", 
		   rb_gsl_fft_halfcomplex_unpack, -1);
  rb_define_alias(cgsl_vector, "halfcomplex_unpack", "fft_halfcomplex_unpack");

  /*****/

  rb_define_singleton_method(mgsl_fft_complex, "radix2_forward", 
			     rb_gsl_fft_complex_radix2_forward, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_transform", 
			     rb_gsl_fft_complex_radix2_transform, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_backward", 
			     rb_gsl_fft_complex_radix2_backward, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_inverse", 
			     rb_gsl_fft_complex_radix2_inverse, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_forward", 
			     rb_gsl_fft_complex_radix2_dif_forward, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_transform", 
			     rb_gsl_fft_complex_radix2_dif_transform, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_backward", 
			     rb_gsl_fft_complex_radix2_dif_backward, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_inverse", 
			     rb_gsl_fft_complex_radix2_dif_inverse, -1);

  rb_define_singleton_method(mgsl_fft_complex_radix2, "forward", 
			     rb_gsl_fft_complex_radix2_forward, -1);
 rb_define_singleton_method(mgsl_fft_complex_radix2, "transform", 
			     rb_gsl_fft_complex_radix2_transform, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "backward", 
			     rb_gsl_fft_complex_radix2_backward, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "inverse", 
			     rb_gsl_fft_complex_radix2_inverse, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_forward", 
			     rb_gsl_fft_complex_radix2_dif_forward, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_transform", 
			     rb_gsl_fft_complex_radix2_dif_transform, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_backward", 
			     rb_gsl_fft_complex_radix2_dif_backward, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_inverse", 
			     rb_gsl_fft_complex_radix2_dif_inverse, -1);

  rb_define_singleton_method(mgsl_fft_complex, "forward", 
			     rb_gsl_fft_complex_forward, -1);
  rb_define_singleton_method(mgsl_fft_complex, "transform", 
			     rb_gsl_fft_complex_transform, -1);
  rb_define_singleton_method(mgsl_fft_complex, "backward", 
			     rb_gsl_fft_complex_backward, -1);
  rb_define_singleton_method(mgsl_fft_complex, "inverse", 
			     rb_gsl_fft_complex_inverse, -1);

  rb_define_singleton_method(mgsl_fft_real, "radix2_transform",
			     rb_gsl_fft_real_radix2_transform, -1);
  rb_define_singleton_method(mgsl_fft_real, "radix2_forward",
			     rb_gsl_fft_real_radix2_transform, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "radix2_inverse",
			     rb_gsl_fft_halfcomplex_radix2_inverse, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "radix2_backward",
			     rb_gsl_fft_halfcomplex_radix2_backward, -1);

  rb_define_singleton_method(mgsl_fft_real_radix2, "transform",
			     rb_gsl_fft_real_radix2_transform, -1);
  rb_define_singleton_method(mgsl_fft_real_radix2, "forward",
			     rb_gsl_fft_real_radix2_transform, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex_radix2, "inverse",
			     rb_gsl_fft_halfcomplex_radix2_inverse, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex_radix2, "backward",
			     rb_gsl_fft_halfcomplex_radix2_backward, -1);

  rb_define_singleton_method(mgsl_fft_real, "transform",
			     rb_gsl_fft_real_transform, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "transform",
			     rb_gsl_fft_halfcomplex_transform, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "inverse",
			     rb_gsl_fft_halfcomplex_inverse, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "backward",
			     rb_gsl_fft_halfcomplex_backward, -1);

  /***/

  rb_define_singleton_method(mgsl_fft_complex, "radix2_forward!", 
			     rb_gsl_fft_complex_radix2_forward2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_transform!", 
			     rb_gsl_fft_complex_radix2_transform2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_backward!", 
			     rb_gsl_fft_complex_radix2_backward2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_inverse!", 
			     rb_gsl_fft_complex_radix2_inverse2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_forward!", 
			     rb_gsl_fft_complex_radix2_dif_forward2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_transform!", 
			     rb_gsl_fft_complex_radix2_dif_transform2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_backward!", 
			     rb_gsl_fft_complex_radix2_dif_backward2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "radix2_dif_inverse!", 
			     rb_gsl_fft_complex_radix2_dif_inverse2, -1);

  rb_define_singleton_method(mgsl_fft_complex_radix2, "forward!", 
			     rb_gsl_fft_complex_radix2_forward2, -1);
 rb_define_singleton_method(mgsl_fft_complex_radix2, "transform!", 
			     rb_gsl_fft_complex_radix2_transform2, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "backward!", 
			     rb_gsl_fft_complex_radix2_backward2, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "inverse!", 
			     rb_gsl_fft_complex_radix2_inverse2, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_forward!", 
			     rb_gsl_fft_complex_radix2_dif_forward2, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_transform!", 
			     rb_gsl_fft_complex_radix2_dif_transform2, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_backward!", 
			     rb_gsl_fft_complex_radix2_dif_backward2, -1);
  rb_define_singleton_method(mgsl_fft_complex_radix2, "dif_inverse!", 
			     rb_gsl_fft_complex_radix2_dif_inverse2, -1);

  rb_define_singleton_method(mgsl_fft_complex, "forward!", 
			     rb_gsl_fft_complex_forward2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "transform!", 
			     rb_gsl_fft_complex_transform2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "backward!", 
			     rb_gsl_fft_complex_backward2, -1);
  rb_define_singleton_method(mgsl_fft_complex, "inverse!", 
			     rb_gsl_fft_complex_inverse2, -1);

  rb_define_singleton_method(mgsl_fft_real, "radix2_transform!",
			     rb_gsl_fft_real_radix2_transform2, -1);
  rb_define_singleton_method(mgsl_fft_real, "radix2_forward!",
			     rb_gsl_fft_real_radix2_transform2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "radix2_inverse!",
			     rb_gsl_fft_halfcomplex_radix2_inverse2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "radix2_backward!",
			     rb_gsl_fft_halfcomplex_radix2_backward2, -1);

  rb_define_singleton_method(mgsl_fft_real_radix2, "transform!",
			     rb_gsl_fft_real_radix2_transform2, -1);
  rb_define_singleton_method(mgsl_fft_real_radix2, "forward!",
			     rb_gsl_fft_real_radix2_transform2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex_radix2, "inverse!",
			     rb_gsl_fft_halfcomplex_radix2_inverse2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex_radix2, "backward!",
			     rb_gsl_fft_halfcomplex_radix2_backward2, -1);

  rb_define_singleton_method(mgsl_fft_real, "transform!",
			     rb_gsl_fft_real_transform2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "transform!",
			     rb_gsl_fft_halfcomplex_transform2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "inverse!",
			     rb_gsl_fft_halfcomplex_inverse2, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "backward!",
			     rb_gsl_fft_halfcomplex_backward2, -1);
  /***/
  rb_define_singleton_method(mgsl_fft_real, "unpack", 
			     rb_gsl_fft_real_unpack, -1);
  rb_define_singleton_method(mgsl_fft_halfcomplex, "unpack", 
			     rb_gsl_fft_halfcomplex_unpack, -1);

  /*****/
  rb_define_singleton_method(mgsl_fft_halfcomplex, "to_nrc_order",
			     rb_gsl_fft_halfcomplex_to_nrc, -1);
  rb_define_method(cgsl_vector, "to_nrc_order",
			     rb_gsl_fft_halfcomplex_to_nrc, -1);

}


syntax highlighted by Code2HTML, v. 0.9.1