/*
  sort.c
  Ruby/GSL: Ruby extension library for GSL (GNU Scientific Library)
    (C) Copyright 2001-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_array.h"
#include <gsl/gsl_heapsort.h>
#include <gsl/gsl_sort.h>
EXTERN ID ID_call;
EXTERN VALUE cgsl_complex;

int rb_gsl_comparison_double(const void *aa, const void *bb);
int rb_gsl_comparison_complex(const void *aa, const void *bb);
int rb_gsl_comparison_double(const void *aa, const void *bb)
{
  double *a = NULL, *b = NULL;
  a = (double *) aa;
  b = (double *) bb;
  return FIX2INT(rb_funcall(RB_GSL_MAKE_PROC, ID_call, 2, rb_float_new(*a), rb_float_new(*b)));
}

int rb_gsl_comparison_complex(const void *aa, const void *bb)
{
  gsl_complex *a = NULL, *b = NULL;
  a = (gsl_complex *) aa;
  b = (gsl_complex *) bb;
  return FIX2INT(rb_funcall(RB_GSL_MAKE_PROC, ID_call, 2, 
			    Data_Wrap_Struct(cgsl_complex, 0, NULL, a),
			    Data_Wrap_Struct(cgsl_complex, 0, NULL, b)));
}

static VALUE rb_gsl_heapsort_vector(VALUE obj)
{
  gsl_vector *v = NULL;
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  Data_Get_Struct(obj, gsl_vector, v);
  gsl_heapsort(v->data, v->size, sizeof(double), rb_gsl_comparison_double);
  return obj;
}

static VALUE rb_gsl_heapsort_vector2(VALUE obj)
{
  gsl_vector *v = NULL, *vnew = NULL;
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  Data_Get_Struct(obj, gsl_vector, v);
  vnew = gsl_vector_alloc(v->size);
  gsl_vector_memcpy(vnew, v);
  gsl_heapsort(vnew->data, vnew->size, sizeof(double), rb_gsl_comparison_double);
  return Data_Wrap_Struct(cgsl_vector, 0, gsl_vector_free, vnew);
}

static VALUE rb_gsl_heapsort_index_vector(VALUE obj)
{
  gsl_vector *v = NULL;
  gsl_permutation *p = NULL;
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  Data_Get_Struct(obj, gsl_vector, v);
  p = gsl_permutation_alloc(v->size);
  gsl_heapsort_index(p->data, v->data, v->size, sizeof(double), rb_gsl_comparison_double);
  return Data_Wrap_Struct(cgsl_permutation, 0, gsl_permutation_free, p);
}

static VALUE rb_gsl_heapsort_vector_complex(VALUE obj)
{
  gsl_vector_complex *v = NULL;
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  Data_Get_Struct(obj, gsl_vector_complex, v);
  gsl_heapsort(v->data, v->size, sizeof(gsl_complex), rb_gsl_comparison_complex);
  return obj;
}

static VALUE rb_gsl_heapsort_vector_complex2(VALUE obj)
{
  gsl_vector_complex *v = NULL, *vnew = NULL;
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  Data_Get_Struct(obj, gsl_vector_complex, v);
  vnew = gsl_vector_complex_alloc(v->size);
  gsl_vector_complex_memcpy(vnew, v);
  gsl_heapsort(vnew->data, vnew->size, sizeof(gsl_complex), rb_gsl_comparison_complex);
  return Data_Wrap_Struct(cgsl_vector_complex, 0, gsl_vector_complex_free, vnew);
}

static VALUE rb_gsl_heapsort_index_vector_complex(VALUE obj)
{
  gsl_vector_complex *v = NULL;
  gsl_permutation *p = NULL;
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  Data_Get_Struct(obj, gsl_vector_complex, v);
  p = gsl_permutation_alloc(v->size);
  gsl_heapsort_index(p->data, v->data, v->size, sizeof(gsl_complex), rb_gsl_comparison_complex);
  return Data_Wrap_Struct(cgsl_permutation, 0, gsl_permutation_free, p);
}

/* singleton */
static VALUE rb_gsl_heapsort(VALUE obj, VALUE vv)
{
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  if (rb_obj_is_kind_of(vv, cgsl_vector_complex)) {
    return rb_gsl_heapsort_vector_complex(vv);
  } else if (rb_obj_is_kind_of(vv, cgsl_vector)) {
    return rb_gsl_heapsort_vector(vv);
  } else {
    rb_raise(rb_eTypeError, "wrong argument type %s (Vector or Vector::Complex expected)", rb_class2name(CLASS_OF(vv)));
  }
  return vv;
}

static VALUE rb_gsl_heapsort2(VALUE obj, VALUE vv)
{
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  if (rb_obj_is_kind_of(vv, cgsl_vector_complex)) {
    return rb_gsl_heapsort_vector_complex2(vv);
  } else if (rb_obj_is_kind_of(vv, cgsl_vector)) {
    return rb_gsl_heapsort_vector2(vv);
  } else {
    rb_raise(rb_eTypeError, "wrong argument type %s (Vector or Vector::Complex expected)", rb_class2name(CLASS_OF(vv)));
  }
  return vv;
}

static VALUE rb_gsl_heapsort_index(VALUE obj, VALUE vv)
{
  if (!rb_block_given_p()) rb_raise(rb_eRuntimeError, "Proc is not given");
  if (rb_obj_is_kind_of(vv, cgsl_vector_complex)) {
    return rb_gsl_heapsort_index_vector_complex(vv);
  } else if (rb_obj_is_kind_of(vv, cgsl_vector)) {
    return rb_gsl_heapsort_index_vector(vv);
  } else {
    rb_raise(rb_eTypeError, "wrong argument type %s (Vector or Vector::Complex expected)", rb_class2name(CLASS_OF(vv)));
  }
  return vv;
}

/*****/

void Init_gsl_sort(VALUE module)
{
  rb_define_singleton_method(module, "heapsort!", rb_gsl_heapsort, 1);
  rb_define_singleton_method(module, "heapsort", rb_gsl_heapsort2, 1);
  rb_define_singleton_method(module, "heapsort_index", rb_gsl_heapsort_index, 1);

  rb_define_method(cgsl_vector, "heapsort!", rb_gsl_heapsort_vector, 0);
  rb_define_method(cgsl_vector, "heapsort", rb_gsl_heapsort_vector2, 0);
  rb_define_method(cgsl_vector, "heapsort_index", rb_gsl_heapsort_index_vector, 0);

  rb_define_method(cgsl_vector_complex, "heapsort!", rb_gsl_heapsort_vector_complex, 0);
  rb_define_method(cgsl_vector_complex, "heapsort", rb_gsl_heapsort_vector_complex2, 0);
  rb_define_method(cgsl_vector_complex, "heapsort_index", rb_gsl_heapsort_index_vector_complex, 0);
}
