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

static VALUE cgsl_combination_data;

static VALUE rb_gsl_combination_new(VALUE klass, VALUE n, VALUE k)
{
  gsl_combination *c = NULL;
  CHECK_FIXNUM(n); CHECK_FIXNUM(k);
  c = gsl_combination_alloc(FIX2INT(n), FIX2INT(k));
  return Data_Wrap_Struct(klass, 0, gsl_combination_free, c);
}

static VALUE rb_gsl_combination_calloc(VALUE klass, VALUE n, VALUE k)
{
  gsl_combination *c = NULL;
  CHECK_FIXNUM(n); CHECK_FIXNUM(k);
  c = gsl_combination_calloc(FIX2INT(n), FIX2INT(k));
  return Data_Wrap_Struct(klass, 0, gsl_combination_free, c);
}

static VALUE rb_gsl_combination_init_first(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  gsl_combination_init_first(c);
  return obj;
}

static VALUE rb_gsl_combination_init_last(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  gsl_combination_init_last(c);
  return obj;
}

/* singleton */
static VALUE rb_gsl_combination_memcpy(VALUE klass, VALUE dst, VALUE src)
{
  gsl_combination *c, *c2;
  if (!rb_obj_is_kind_of(dst, klass))
    rb_raise(rb_eTypeError, "wrong argument type %s (Combination expected)",
             rb_class2name(CLASS_OF(dst)));
  if (!rb_obj_is_kind_of(src, klass))
    rb_raise(rb_eTypeError, "wrong argument type %s (Combination expected)",
             rb_class2name(CLASS_OF(src)));
  Data_Get_Struct(dst, gsl_combination, c2);
  Data_Get_Struct(src, gsl_combination, c);
  gsl_combination_memcpy(c2, c);
  return dst;
}

static VALUE rb_gsl_combination_clone(VALUE obj)
{
  gsl_combination *c, *c2;
  Data_Get_Struct(obj, gsl_combination, c);
  c2 = gsl_combination_alloc(c->n, c->k);
  gsl_combination_memcpy(c2, c);
  return Data_Wrap_Struct(CLASS_OF(obj), 0, gsl_combination_free, c2);
}

static VALUE rb_gsl_combination_get(VALUE obj, VALUE ii)
{
  gsl_combination *c = NULL;
  size_t i;
  CHECK_FIXNUM(ii);
  Data_Get_Struct(obj, gsl_combination, c);
  i = FIX2INT(ii);
  if (i > c->n) rb_raise(rb_eIndexError, "index out of range");
  return INT2FIX(gsl_combination_get(c, i));
}

static VALUE rb_gsl_combination_set(VALUE obj, VALUE ii, VALUE val)
{
  gsl_combination *c = NULL;
  size_t i;
  CHECK_FIXNUM(ii);
  CHECK_FIXNUM(val);
  Data_Get_Struct(obj, gsl_combination, c);
  i = FIX2INT(ii);
  c->data[i] = FIX2INT(val);
  return obj;
}

static VALUE rb_gsl_combination_n(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  return INT2FIX(gsl_combination_n(c));

}
static VALUE rb_gsl_combination_k(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  return INT2FIX(gsl_combination_k(c));
}

static VALUE rb_gsl_combination_data(VALUE obj)
{
  gsl_combination *c = NULL;
  gsl_permutation *p = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  p = ALLOC(gsl_permutation);
  p->size = c->k;
  p->data = c->data;
  return Data_Wrap_Struct(cgsl_combination_data, 0, free, p);
}

static VALUE rb_gsl_combination_valid(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  return INT2FIX(gsl_combination_valid(c));
}

static VALUE rb_gsl_combination_valid2(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  if(gsl_combination_valid(c)) return Qtrue;
  else return Qfalse;
}

static VALUE rb_gsl_combination_next(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  return INT2FIX(gsl_combination_next(c));
}
static VALUE rb_gsl_combination_prev(VALUE obj)
{
  gsl_combination *c = NULL;
  Data_Get_Struct(obj, gsl_combination, c);
  return INT2FIX(gsl_combination_prev(c));
}

static VALUE rb_gsl_combination_fwrite(VALUE obj, VALUE io)
{
  gsl_combination *h = NULL;
  FILE *f = NULL;
  int status, flag = 0;
  Data_Get_Struct(obj, gsl_combination, h);
  f = rb_gsl_open_writefile(io, &flag);
  status = gsl_combination_fwrite(f, h);
  if (flag == 1) fclose(f);
  return INT2FIX(status);
}

static VALUE rb_gsl_combination_fread(VALUE obj, VALUE io)
{
  gsl_combination *h = NULL;
  FILE *f = NULL;
  int status, flag = 0;

  Data_Get_Struct(obj, gsl_combination, h);
  f = rb_gsl_open_readfile(io, &flag);
  status = gsl_combination_fread(f, h);
  if (flag == 1) fclose(f);
  return INT2FIX(status);
}

static VALUE rb_gsl_combination_fprintf(int argc, VALUE *argv, VALUE obj)
{
  gsl_combination *h = NULL;
  FILE *fp = NULL;
  int status, flag = 0;

  if (argc != 1 && argc != 2) rb_raise(rb_eArgError,
                                       "wrong number of arguments (%d for 1 or 2)", argc);
  Data_Get_Struct(obj, gsl_combination, h);
  fp = rb_gsl_open_writefile(argv[0], &flag);
  switch (argc) {
  case 1:
    status = gsl_combination_fprintf(fp, h, "%u\n");
    break;
  default:
    Check_Type(argv[1], T_STRING);
    status = gsl_combination_fprintf(fp, h, STR2CSTR(argv[1]));
    break;
  }
  if (flag == 1) fclose(fp);
  return INT2FIX(status);
}

static VALUE rb_gsl_combination_printf(int argc, VALUE *argv, VALUE obj)
{
  gsl_combination *h = NULL;
  int status;
  Data_Get_Struct(obj, gsl_combination, h);
  switch (argc) {
  case 0:
    status = gsl_combination_fprintf(stdout, h, "%u\n");
    break;
  default:
    Check_Type(argv[0], T_STRING);
    status = gsl_combination_fprintf(stdout, h, STR2CSTR(argv[0]));
    break;
  }
  return INT2FIX(status);
}


static VALUE rb_gsl_combination_fscanf(VALUE obj, VALUE io)
{
  gsl_combination *h = NULL;
  FILE *f = NULL;
  int status, flag = 0;
  Data_Get_Struct(obj, gsl_combination, h);
  f = rb_gsl_open_readfile(io, &flag);
  status = gsl_combination_fscanf(f, h);
  if (flag == 1) fclose(f);
  return INT2FIX(status);
}

static VALUE rb_gsl_combination_equal(VALUE obj, VALUE other)
{
  gsl_combination *p1 = NULL, *p2 = NULL;
  size_t i;
  Data_Get_Struct(obj, gsl_combination, p1);
  Data_Get_Struct(other, gsl_combination, p2);
  if (p1->k != p2->k) return Qfalse;
  for (i = 0; i < p1->k; i++)
    if (p1->data[i] != p2->data[i]) return Qfalse;
  return Qtrue;
}

void Init_gsl_combination(VALUE module)
{
  VALUE cgsl_combination;
  cgsl_combination = rb_define_class_under(module, "Combination", cGSL_Object);
  cgsl_combination_data = rb_define_class_under(cgsl_combination, "Data",
                                                cgsl_permutation);
  rb_define_singleton_method(cgsl_combination, "new", rb_gsl_combination_new, 2);
  rb_define_singleton_method(cgsl_combination, "alloc", rb_gsl_combination_new, 2);
  rb_define_singleton_method(cgsl_combination, "calloc", rb_gsl_combination_calloc, 2);
  rb_define_method(cgsl_combination, "init_first", rb_gsl_combination_init_first, 0);
  rb_define_method(cgsl_combination, "init_last", rb_gsl_combination_init_last, 0);
  rb_define_singleton_method(cgsl_combination, "memcpy", rb_gsl_combination_memcpy, 2);
  rb_define_method(cgsl_combination, "clone", rb_gsl_combination_clone, 0);
  rb_define_method(cgsl_combination, "get", rb_gsl_combination_get, 1);
  rb_define_alias(cgsl_combination, "[]", "get");
  rb_define_method(cgsl_combination, "set", rb_gsl_combination_set, 2);
  rb_define_alias(cgsl_combination, "[]=", "set");

  rb_define_method(cgsl_combination, "n", rb_gsl_combination_n, 0);
  rb_define_method(cgsl_combination, "k", rb_gsl_combination_k, 0);
  rb_define_method(cgsl_combination, "data", rb_gsl_combination_data, 0);
  rb_define_method(cgsl_combination, "valid", rb_gsl_combination_valid, 0);
  rb_define_method(cgsl_combination, "valid?", rb_gsl_combination_valid2, 0);
  rb_define_method(cgsl_combination, "next", rb_gsl_combination_next, 0);
  rb_define_method(cgsl_combination, "prev", rb_gsl_combination_prev, 0);

  rb_define_method(cgsl_combination, "fwrite", rb_gsl_combination_fwrite, 1);
  rb_define_method(cgsl_combination, "fread", rb_gsl_combination_fread, 1);
  rb_define_method(cgsl_combination, "fprintf", rb_gsl_combination_fprintf, -1);
  rb_define_method(cgsl_combination, "printf", rb_gsl_combination_printf, -1);
  rb_define_method(cgsl_combination, "fscanf", rb_gsl_combination_fscanf, 1);

  rb_define_method(cgsl_combination, "equal?", rb_gsl_combination_equal, 1);
  rb_define_alias(cgsl_combination, "==", "equal?");
}

