/*
  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_fft.h"
#include "rb_gsl_complex.h"
#include "rb_gsl_array.h"

typedef struct
{
  size_t n;
  size_t nf;
  size_t factor[64];
  gsl_complex *twiddle[64];
  gsl_complex *trig;
} GSL_FFT_Wavetable;

typedef struct
{
  size_t n;
  double *scratch;
} GSL_FFT_Workspace;

static VALUE mgsl_fft_complex;

static VALUE cgsl_cparray;
static VALUE cgsl_fft_wavetable, cgsl_fft_workspace;
static VALUE cgsl_fft_wavetable_factor;
static VALUE cgsl_fft_complex_wavetable, cgsl_fft_complex_workspace;
static VALUE cgsl_fft_real_wavetable, cgsl_fft_halfcomplex_wavetable;
static VALUE cgsl_fft_real_workspace;

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);

void rb_gsl_complex_free(gsl_complex *c);
static VALUE cparray_get(VALUE obj, VALUE ii)
{
  gsl_vector *v = NULL;
  gsl_complex *c = NULL;
  int 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, rb_gsl_complex_free, c); 
}

static VALUE cparray_set(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL;
  gsl_complex *c = NULL;
  int 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_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 void get_cpary_stride_n(int argc, VALUE *argv, VALUE obj,
			       gsl_complex_packed_array *data, 
			       size_t *stride, size_t *n);

static void 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;

  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);
    itmp = 1;
    break;
  default:
    CHECK_VECTOR(obj);
    Data_Get_Struct(obj, gsl_vector, v);
    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;
}

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))
{
  size_t stride, n;
  gsl_complex_packed_array data;
  get_cpary_stride_n(argc, argv, obj, &data, &stride, &n);
  return INT2FIX((*trans)(data, stride, n));
}

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);
}

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;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  get_cpary_stride_n(argc-1, argv, obj, &data, &stride, &n);
  return INT2FIX(gsl_fft_complex_radix2_transform(data, stride, n, sign));
}

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);
}

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);
}


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);
}

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_fft_direction sign;
  CHECK_FIXNUM(argv[argc-1]);
  sign = FIX2INT(argv[argc-1]);
  get_cpary_stride_n(argc-1, argv, obj, &data, &stride, &n);
  return INT2FIX(gsl_fft_complex_radix2_dif_transform(data, stride, n, sign));
}
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);
}
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);
}

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;
  int 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_argv2(int argc, VALUE *argv, VALUE obj,
			     gsl_vector **v, size_t *stride,
			     size_t *n, gsl_fft_real_wavetable **table,
			     gsl_fft_real_workspace **space);

static int gsl_fft_get_argv3(int argc, VALUE *argv, VALUE obj,
			    gsl_vector **v, size_t *stride,
			    size_t *n, gsl_fft_halfcomplex_wavetable **table,
			     gsl_fft_real_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;
  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;
  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;
      itmp = i;
      ccc--;
      break;
    }
  }
  if (flagtmp == 0) {
    *space = gsl_fft_complex_workspace_alloc(v->size/2);
    flag += ALLOC_SPACE;
  }
  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;
    }
  }
  if (flagtmp == 0) {
    *table = gsl_fft_complex_wavetable_alloc(v->size/2);
    flag += ALLOC_TABLE;
  }
  get_cpary_stride_n(ccc, argv, obj, data, stride, n);
  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,
			    gsl_vector **v, size_t *stride,
			    size_t *n, gsl_fft_real_wavetable **table,
			    gsl_fft_real_workspace **space)
{
  int flag = NONE_OF_TWO, flagtmp, i, itmp = argc, itmp2 = 0, ccc;
  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;
  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;
      itmp = i;
      ccc--;
      break;
    }
  }
  if (flagtmp == 0) {
    *space = gsl_fft_real_workspace_alloc((*v)->size);
    flag += ALLOC_SPACE;
  }
  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 (flagtmp == 0) {
    *table = gsl_fft_real_wavetable_alloc((*v)->size);
    flag += ALLOC_TABLE;
  }
  get_stride_n(ccc, argv, itmp2, *v, stride, n);
  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,
			    gsl_vector **v, size_t *stride,
			    size_t *n, gsl_fft_halfcomplex_wavetable **table,
			    gsl_fft_real_workspace **space)
{
  int flag = NONE_OF_TWO, flagtmp, i, itmp = argc, itmp2 = 0, ccc;

  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;
  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;
      itmp = i;
      ccc--;
      break;
    }
  }
  if (flagtmp == 0) {
    *space = gsl_fft_real_workspace_alloc((*v)->size);
    flag += ALLOC_SPACE;
  }
  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 (flagtmp == 0) {
    *table = gsl_fft_halfcomplex_wavetable_alloc((*v)->size);
    flag += ALLOC_TABLE;
  }
  get_stride_n(ccc, argv, itmp2, *v, stride, n);
  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;
  }
}

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 *));

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 flag = 0, status;
  size_t stride, n;
  gsl_complex_packed_array data;
  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);
  status = (*transform)(data, stride, n, table, space);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
  return INT2FIX(status);
}

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);
}

static VALUE rb_gsl_fft_complex_transform(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 INT2FIX(status);
}

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);
}

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);
}

static void get_vector_stride_n(int argc, VALUE *argv, VALUE obj,
				gsl_vector **v, size_t *stride, size_t *n);
static void get_vector_stride_n(int argc, VALUE *argv, VALUE obj,
				gsl_vector **v, size_t *stride, size_t *n)
{
  int itmp = 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])));
    }
    CHECK_VECTOR(argv[0]);
    Data_Get_Struct(argv[0], gsl_vector, *v);
    itmp = 1;
    break;
  default:
    CHECK_VECTOR(obj);
    Data_Get_Struct(obj, gsl_vector, *v);
    itmp = 0;
    break;
  }
  switch (argc-itmp) {
  case 0:
    *stride = (*v)->stride;
    *n = (*v)->size;
    break;
  case 1:
    CHECK_FIXNUM(argv[itmp]);
    *stride = (*v)->stride;
    *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;
}

static VALUE rb_fft_radix2(int argc, VALUE *argv, VALUE obj,
				 int (*trans)(double [], size_t, size_t));
static void rb_fft_radix2_check_arg(int argc, VALUE obj);
static VALUE rb_fft_radix2(int argc, VALUE *argv, VALUE obj,
				 int (*trans)(double [], size_t, size_t))
{
  size_t stride, n;
  gsl_vector *v = NULL;
  get_vector_stride_n(argc, argv, obj, &v, &stride, &n);
  return INT2FIX((*trans)(v->data, stride, n));
}

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);
}

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);
}

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);
}

/*****/
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 *));

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 flag = 0, status;
  size_t stride, n;
  gsl_vector *v = NULL;
  gsl_fft_real_wavetable *table = NULL;
  gsl_fft_real_workspace *space = NULL;
  flag = gsl_fft_get_argv2(argc, argv, obj, &v, &stride, &n, &table, &space);
  status = (*trans)(v->data, stride, n, table, space);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
  return INT2FIX(status);
}

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);
}

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 flag = 0, status;
  size_t stride, n;
  gsl_vector *v = NULL;
  gsl_fft_halfcomplex_wavetable *table = NULL;
  gsl_fft_real_workspace *space = NULL;
  flag = gsl_fft_get_argv3(argc, argv, obj, &v, &stride, &n, 
			   &table, &space);
  status = (*trans)(v->data, stride, n, table, space);
  gsl_fft_free(flag, (GSL_FFT_Wavetable *) table, (GSL_FFT_Workspace *) space);
  return INT2FIX(status);
}
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);
}

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);
}

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);
}

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;
  int 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 mgsl_fft, mgsl_fft_real, mgsl_fft_halfcomplex;
  VALUE mgsl_fft_complex_radix2, mgsl_fft_real_radix2;
  VALUE mgsl_fft_halfcomplex_radix2;

  mgsl_fft = rb_define_module_under(mgsl, "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");

  /*****/

  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, "new", 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_alias(cgsl_cparray, "re", "real");
  rb_define_method(cgsl_cparray, "imag", cparray_imag, 1);
  rb_define_alias(cgsl_cparray, "im", "imag");
  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);

  cgsl_fft_wavetable = rb_define_class_under(mgsl_fft, "Wavetable", rb_cObject);
  rb_define_singleton_method(cgsl_fft_wavetable, "new",
			     rb_GSL_FFT_Wavetable_new, 1);
  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, "new",
			     rb_gsl_fft_complex_wavetable_new, 1);
  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", rb_cObject);
  rb_define_singleton_method(cgsl_fft_workspace, "new",
			     rb_GSL_FFT_Workspace_new, 1);
  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, "new",
			     rb_gsl_fft_complex_workspace_new, 1);
  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_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_method(cgsl_vector, "real_radix2_backward", rb_gsl_fft_halfcomplex_radix2_backward, -1);
  rb_define_alias(cgsl_vector, "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, "new",
			     rb_gsl_fft_real_wavetable_new, 1);
  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, "new",
			     rb_gsl_fft_halfcomplex_wavetable_new, 1);
  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, "new",
			     rb_gsl_fft_real_workspace_new, 1);
  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_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, "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_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",
			     rb_gsl_fft_halfcomplex_to_nrc, -1);
}
