/*
  histogram2d.c
  Ruby/GSL: Ruby extension library for GSL (GNU Scientific Library)
    (C) Copyright 2001-2006 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_histogram.h"
#include "include/rb_gsl_common.h"
#include "include/rb_gsl_array.h"

VALUE cgsl_histogram2d;
VALUE cgsl_histogram2d_view;
static VALUE cgsl_histogram2d_integ;

static VALUE rb_gsl_histogram2d_alloc_uniform(int argc, VALUE *argv, VALUE klass);
static VALUE rb_gsl_histogram2d_alloc(int argc, VALUE *argv, VALUE klass)
{
  gsl_histogram2d *h = NULL;
  size_t xsize, ysize;
  gsl_vector *vx, *vy;
  switch (argc) {
  case 2:
    switch (TYPE(argv[0])) {
    case T_FIXNUM:
      CHECK_FIXNUM(argv[1]);
      h = gsl_histogram2d_calloc(FIX2INT(argv[0]), FIX2INT(argv[1]));
      break;
    case T_ARRAY:
      vx = make_cvector_from_rarray(argv[0]);
      vy = make_cvector_from_rarray(argv[1]);
      h = gsl_histogram2d_alloc(vx->size-1, vy->size-1);
      gsl_histogram2d_set_ranges(h, vx->data, vx->size, vy->data, vy->size);
      gsl_vector_free(vx);
      gsl_vector_free(vy);
      break;
    default:
      CHECK_VECTOR(argv[0]); CHECK_VECTOR(argv[1]);
      Data_Get_Struct(argv[0], gsl_vector, vx);
      Data_Get_Struct(argv[1], gsl_vector, vy);
      h = gsl_histogram2d_alloc(vx->size-1, vy->size-1);
      gsl_histogram2d_set_ranges(h, vx->data, vx->size, vy->data, vy->size);
      break;
    }
    return Data_Wrap_Struct(klass, 0, gsl_histogram2d_free, h);
    break;
  case 4:
    if (VECTOR_P(argv[0]) && VECTOR_P(argv[2])) {
      CHECK_FIXNUM(argv[1]); CHECK_FIXNUM(argv[3]);
      Data_Get_Struct(argv[0], gsl_vector, vx);
      Data_Get_Struct(argv[2], gsl_vector, vy);
      xsize = (size_t) FIX2UINT(argv[1]); ysize = (size_t) FIX2UINT(argv[3]);
      h = gsl_histogram2d_alloc(xsize-1, ysize-1);
      gsl_histogram2d_set_ranges(h, vx->data, xsize, vy->data, ysize);
      return Data_Wrap_Struct(klass, 0, gsl_histogram2d_free, h);
    } else {
      return rb_gsl_histogram2d_alloc_uniform(argc, argv, klass);
    }
    break;
  case 6:
    return rb_gsl_histogram2d_alloc_uniform(argc, argv, klass);
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments %d", argc);
    break;
  }
  return Qnil;  /* never reach here */
}

static VALUE rb_gsl_histogram2d_alloc_uniform(int argc, VALUE *argv, VALUE klass)
{
  gsl_histogram2d *h = NULL;
  double xmin, xmax, ymin, ymax;
  switch (argc) {
  case 4:
    CHECK_FIXNUM(argv[0]);     CHECK_FIXNUM(argv[2]);
    Check_Type(argv[1], T_ARRAY); Check_Type(argv[3], T_ARRAY);
    //    if (RARRAY(argv[1])->len != 2 || RARRAY(argv[3])->len != 2)
    if (RARRAY_LEN(argv[1]) != 2 || RARRAY_LEN(argv[3]) != 2)
      rb_raise(rb_eArgError, "array size must be 2");
    xmin = NUM2DBL(rb_ary_entry(argv[1], 0));
    xmax = NUM2DBL(rb_ary_entry(argv[1], 1));
    ymin = NUM2DBL(rb_ary_entry(argv[3], 0));
    ymax = NUM2DBL(rb_ary_entry(argv[3], 1));
    h = gsl_histogram2d_alloc(FIX2INT(argv[0]), FIX2INT(argv[2]));
    gsl_histogram2d_set_ranges_uniform(h, xmin, xmax, ymin, ymax);
    return Data_Wrap_Struct(klass, 0, gsl_histogram2d_free, h);
    break;
  case 6:
    CHECK_FIXNUM(argv[0]);
    Need_Float(argv[1]); Need_Float(argv[2]);
    CHECK_FIXNUM(argv[3]);
    Need_Float(argv[4]); Need_Float(argv[5]);
    h = gsl_histogram2d_alloc(FIX2INT(argv[0]), FIX2INT(argv[3]));
    gsl_histogram2d_set_ranges_uniform(h, NUM2DBL(argv[1]), NUM2DBL(argv[2]),
                                       NUM2DBL(argv[4]), NUM2DBL(argv[5]));
    return Data_Wrap_Struct(klass, 0, gsl_histogram2d_free, h);
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments %d", argc);
    break;
  }
  return Qnil;  /* never reach here */
}

static VALUE rb_gsl_histogram2d_set_ranges(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h = NULL;
  gsl_vector *vx, *vy;
  size_t xsize, ysize;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  switch (argc) {
  case 4:
    CHECK_VECTOR(argv[0]); CHECK_VECTOR(argv[2]);
    CHECK_FIXNUM(argv[1]); CHECK_FIXNUM(argv[3]);
    Data_Get_Struct(argv[0], gsl_vector, vx);
    Data_Get_Struct(argv[2], gsl_vector, vy);
    xsize = (size_t) FIX2UINT(argv[1]); ysize = (size_t) FIX2UINT(argv[3]);
    break;
  case 2:
    CHECK_VECTOR(argv[0]); CHECK_VECTOR(argv[1]);
    Data_Get_Struct(argv[0], gsl_vector, vx);
    Data_Get_Struct(argv[1], gsl_vector, vy);
    xsize = vx->size; ysize = vy->size;
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 4)", argc);
  }
  gsl_histogram2d_set_ranges(h, vx->data, xsize, vy->data, ysize);
  return obj;
}

static VALUE rb_gsl_histogram2d_set_ranges_uniform(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h = NULL;
  double xmin, xmax, ymin, ymax;
  switch (argc) {
  case 2:
    Check_Type(argv[0], T_ARRAY); Check_Type(argv[1], T_ARRAY);
    xmin = NUM2DBL(rb_ary_entry(argv[0], 0));
    xmax = NUM2DBL(rb_ary_entry(argv[0], 1));
    ymin = NUM2DBL(rb_ary_entry(argv[1], 0));
    ymax = NUM2DBL(rb_ary_entry(argv[1], 1));
    break;
  case 4:
    xmin = NUM2DBL(argv[0]); xmax = NUM2DBL(argv[1]);
    ymin = NUM2DBL(argv[2]); ymax = NUM2DBL(argv[3]);
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 4)", argc);
    break;
  }
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_set_ranges_uniform(h, xmin, xmax, ymin, ymax);
  return obj;
}

static VALUE rb_gsl_histogram2d_clone(VALUE obj)
{
  gsl_histogram2d *h, *h2 = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  h2 = gsl_histogram2d_clone(h);
  return Data_Wrap_Struct(CLASS_OF(obj), 0, gsl_histogram2d_free, h2);
}

/* singleton */
static VALUE rb_gsl_histogram2d_memcpy(VALUE obj, VALUE vhdest, VALUE vhsrc)
{
  gsl_histogram2d *hdest, *hsrc;
  CHECK_HISTOGRAM2D(vhdest); CHECK_HISTOGRAM2D(vhsrc);
  Data_Get_Struct(vhdest, gsl_histogram2d, hdest);
  Data_Get_Struct(vhsrc, gsl_histogram2d, hsrc);
  gsl_histogram2d_memcpy(hdest, hsrc);
  return vhdest;
}

static VALUE rb_gsl_histogram2d_accumulate(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h = NULL;
  gsl_vector *vx, *vy;
  size_t n, i;
  double weight = 1;
  switch (argc) {
  case 3:
    Need_Float(argv[2]);
    weight = NUM2DBL(argv[2]);
    break;
  case 2:
    weight = 1;
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 3)", argc);
    break;
  }
  Data_Get_Struct(obj, gsl_histogram2d, h);
  if (VECTOR_P(argv[0]) && VECTOR_P(argv[1])) {
    Data_Get_Struct(argv[0], gsl_vector, vx);
    Data_Get_Struct(argv[1], gsl_vector, vy);
    n = (size_t) GSL_MIN_INT((int) vx->size, (int) vy->size);
    for (i = 0; i < n; i++)
      gsl_histogram2d_accumulate(h, gsl_vector_get(vx, i), gsl_vector_get(vy, i),
                                 weight);
  } else {
    gsl_histogram2d_accumulate(h, NUM2DBL(argv[0]), NUM2DBL(argv[1]), weight);
  }
  return obj;
}

static VALUE rb_gsl_histogram2d_accumulate2(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h = NULL;
  double x, y, weight = 1;
  switch (argc) {
  case 3:
    Need_Float(argv[2]);
    weight = NUM2DBL(argv[2]);
  /* no break */
  case 2:
    Need_Float(argv[0]); Need_Float(argv[1]);
    x = NUM2DBL(argv[0]);    y = NUM2DBL(argv[1]);
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 3)", argc);
    break;
  }
  Data_Get_Struct(obj, gsl_histogram2d, h);
  if (x < h->xrange[0]) x = h->xrange[0] + 4*GSL_DBL_EPSILON;
  if (x > h->xrange[h->nx]) x = h->xrange[h->nx] - 4*GSL_DBL_EPSILON;
  if (y < h->yrange[0]) y = h->yrange[0] + 4*GSL_DBL_EPSILON;
  if (y > h->yrange[h->ny]) y = h->yrange[h->ny] - 4*GSL_DBL_EPSILON;
  gsl_histogram2d_accumulate(h, x, y, weight);
  return obj;
}

static VALUE rb_gsl_histogram2d_get(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h2 = NULL;
  mygsl_histogram2d_view *h1 = NULL;
  size_t i;
  switch (argc) {
  case 2:
    CHECK_FIXNUM(argv[0]); CHECK_FIXNUM(argv[1]);
    Data_Get_Struct(obj, gsl_histogram2d, h2);
    return rb_float_new(gsl_histogram2d_get(h2, (size_t) FIX2INT(argv[0]), (size_t) FIX2INT(argv[1])));
    break;
  case 1:
    Data_Get_Struct(obj, gsl_histogram2d, h2);
    switch (TYPE(argv[0])) {
    case T_ARRAY:
      return rb_float_new(gsl_histogram2d_get(h2, FIX2INT(rb_ary_entry(argv[0], 0)),
                                              FIX2INT(rb_ary_entry(argv[0], 1))));
      break;
    case T_FIXNUM:
      CHECK_FIXNUM(argv[0]);
      i = (size_t) FIX2INT(argv[0]);
      if (i >= h2->ny)
        rb_raise(rb_eIndexError, "wrong index");
      h1 = ALLOC(mygsl_histogram2d_view);
      h1->h.n = h2->ny;
      h1->h.range = h2->yrange;
      h1->h.bin = h2->bin + i*h2->ny;
      return Data_Wrap_Struct(cgsl_histogram2d_view, 0, free, h1);
      break;
    default:
      rb_raise(rb_eTypeError, "wrong argument type %s (Array or Fixnum expected)",
               rb_class2name(CLASS_OF(argv[0])));
      break;
    }
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 2 or 1)",argc);
    break;
  }
}

static VALUE rb_gsl_histogram2d_get_xrange(VALUE obj, VALUE i)
{
  gsl_histogram2d *h = NULL;
  double x1, x2;
  CHECK_FIXNUM(i);
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_get_xrange(h, FIX2INT(i), &x1, &x2);
  return rb_ary_new3(2, rb_float_new(x1), rb_float_new(x2));
}

static VALUE rb_gsl_histogram2d_get_yrange(VALUE obj, VALUE j)
{
  gsl_histogram2d *h = NULL;
  double y1, y2;
  CHECK_FIXNUM(j);
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_get_yrange(h, FIX2INT(j), &y1, &y2);
  return rb_ary_new3(2, rb_float_new(y1), rb_float_new(y2));
}

static VALUE rb_gsl_histogram2d_xmax(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_xmax(h));
}

static VALUE rb_gsl_histogram2d_xmin(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_xmin(h));
}

static VALUE rb_gsl_histogram2d_nx(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return INT2FIX(gsl_histogram2d_nx(h));
}

static VALUE rb_gsl_histogram2d_ymax(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_ymax(h));
}

static VALUE rb_gsl_histogram2d_ymin(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_ymin(h));
}

static VALUE rb_gsl_histogram2d_ny(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return INT2FIX(gsl_histogram2d_ny(h));
}

static VALUE rb_gsl_histogram2d_find(VALUE obj, VALUE x, VALUE y)
{
  gsl_histogram2d *h = NULL;
  size_t i, j;
  Need_Float(x); Need_Float(y);
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_find(h, NUM2DBL(x), NUM2DBL(y), &i, &j);
  return rb_ary_new3(2, INT2FIX(i), INT2FIX(j));
}

static VALUE rb_gsl_histogram2d_max_val(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_max_val(h));
}

static VALUE rb_gsl_histogram2d_max_bin(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  size_t i, j;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_max_bin(h, &i, &j);
  return rb_ary_new3(2, INT2FIX(i), INT2FIX(j));
}

static VALUE rb_gsl_histogram2d_min_val(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_min_val(h));
}

static VALUE rb_gsl_histogram2d_min_bin(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  size_t i, j;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_min_bin(h, &i, &j);
  return rb_ary_new3(2, INT2FIX(i), INT2FIX(j));
}

static VALUE rb_gsl_histogram2d_xmean(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_xmean(h));
}

static VALUE rb_gsl_histogram2d_ymean(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_ymean(h));
}

static VALUE rb_gsl_histogram2d_xsigma(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_xsigma(h));
}

static VALUE rb_gsl_histogram2d_ysigma(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_ysigma(h));
}

static VALUE rb_gsl_histogram2d_cov(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_cov(h));
}

static VALUE rb_gsl_histogram2d_sum(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  return rb_float_new(gsl_histogram2d_sum(h));
}

/* singleton */
static VALUE rb_gsl_histogram2d_equal_bins_p(VALUE obj, VALUE hh1, VALUE hh2)
{
  gsl_histogram2d *h1 = NULL, *h2 = NULL;
  CHECK_HISTOGRAM2D(hh1); CHECK_HISTOGRAM2D(hh2);
  Data_Get_Struct(hh1, gsl_histogram2d, h1);
  Data_Get_Struct(hh2, gsl_histogram2d, h2);
  return INT2FIX(gsl_histogram2d_equal_bins_p(h1, h2));
}

static VALUE rb_gsl_histogram2d_equal_bins_p2(VALUE obj, VALUE hh1, VALUE hh2)
{
  gsl_histogram2d *h1 = NULL, *h2 = NULL;
  CHECK_HISTOGRAM2D(hh1); CHECK_HISTOGRAM2D(hh2);
  Data_Get_Struct(hh1, gsl_histogram2d, h1);
  Data_Get_Struct(hh2, gsl_histogram2d, h2);
  if (gsl_histogram2d_equal_bins_p(h1, h2)) return Qtrue;
  else return Qfalse;
}

static VALUE rb_gsl_histogram2d_scale(VALUE obj, VALUE s)
{
  gsl_histogram2d *h = NULL;
  Need_Float(s);
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_scale(h, NUM2DBL(s));
  return obj;
}

static VALUE rb_gsl_histogram2d_shift(VALUE obj, VALUE s)
{
  gsl_histogram2d *h = NULL;
  Need_Float(s);
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_shift(h, NUM2DBL(s));
  return obj;
}

/*****/
static VALUE rb_gsl_histogram2d_add(VALUE obj, VALUE hh2)
{
  gsl_histogram2d *h1 = NULL, *h2 = NULL, *hnew = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h1);
  hnew = gsl_histogram2d_clone(h1);
  if (HISTOGRAM2D_P(hh2)) {
    Data_Get_Struct(hh2, gsl_histogram2d, h2);
    gsl_histogram2d_add(hnew, h2);
  } else {
    Need_Float(hh2);
    gsl_histogram2d_shift(hnew, NUM2DBL(hh2));
  }
  return Data_Wrap_Struct(CLASS_OF(h1), 0, gsl_histogram2d_free, hnew);
}

static VALUE rb_gsl_histogram2d_sub(VALUE obj, VALUE hh2)
{
  gsl_histogram2d *h1 = NULL, *h2 = NULL, *hnew = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h1);
  hnew = gsl_histogram2d_clone(h1);
  if (HISTOGRAM2D_P(hh2)) {
    Data_Get_Struct(hh2, gsl_histogram2d, h2);
    gsl_histogram2d_sub(hnew, h2);
  } else {
    Need_Float(hh2);
    gsl_histogram2d_shift(hnew, -NUM2DBL(hh2));
  }
  return Data_Wrap_Struct(CLASS_OF(h1), 0, gsl_histogram2d_free, hnew);
}

static VALUE rb_gsl_histogram2d_mul(VALUE obj, VALUE hh2)
{
  gsl_histogram2d *h1 = NULL, *h2 = NULL, *hnew = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h1);
  hnew = gsl_histogram2d_clone(h1);
  if (HISTOGRAM2D_P(hh2)) {
    Data_Get_Struct(hh2, gsl_histogram2d, h2);
    gsl_histogram2d_mul(hnew, h2);
  } else {
    Need_Float(hh2);
    gsl_histogram2d_scale(hnew, NUM2DBL(hh2));
  }
  return Data_Wrap_Struct(CLASS_OF(h1), 0, gsl_histogram2d_free, hnew);
}

static VALUE rb_gsl_histogram2d_div(VALUE obj, VALUE hh2)
{
  gsl_histogram2d *h1 = NULL, *h2 = NULL, *hnew = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h1);
  hnew = gsl_histogram2d_clone(h1);
  if (HISTOGRAM2D_P(hh2)) {
    Data_Get_Struct(hh2, gsl_histogram2d, h2);
    gsl_histogram2d_div(hnew, h2);
  } else {
    Need_Float(hh2);
    gsl_histogram2d_scale(hnew, 1.0/NUM2DBL(hh2));
  }
  return Data_Wrap_Struct(CLASS_OF(h1), 0, gsl_histogram2d_free, hnew);
}

static VALUE rb_gsl_histogram2d_scale2(VALUE obj, VALUE val)
{
  gsl_histogram2d *h1 = NULL, *hnew = NULL;
  Need_Float(val);
  Data_Get_Struct(obj, gsl_histogram2d, h1);
  hnew = gsl_histogram2d_clone(h1);
  gsl_histogram2d_scale(hnew, NUM2DBL(val));
  return Data_Wrap_Struct(CLASS_OF(h1), 0, gsl_histogram2d_free, hnew);
}

static VALUE rb_gsl_histogram2d_shift2(VALUE obj, VALUE val)
{
  gsl_histogram2d *h1 = NULL, *hnew = NULL;
  Need_Float(val);
  Data_Get_Struct(obj, gsl_histogram2d, h1);
  hnew = gsl_histogram2d_clone(h1);
  gsl_histogram2d_shift(hnew, NUM2DBL(val));
  return Data_Wrap_Struct(CLASS_OF(h1), 0, gsl_histogram2d_free, hnew);
}

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

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

static VALUE rb_gsl_histogram2d_fprintf(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h = NULL;
  FILE *fp;
  int status, flag = 0;
  if (argc != 1 && argc != 3) {
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 1 or 3)", argc);
  }
  Data_Get_Struct(obj, gsl_histogram2d, h);
  fp = rb_gsl_open_writefile(argv[0], &flag);
  if (argc == 3) {
    Check_Type(argv[1], T_STRING);
    Check_Type(argv[2], T_STRING);
    status = gsl_histogram2d_fprintf(fp, h, STR2CSTR(argv[1]), STR2CSTR(argv[2]));
  } else {
    status = gsl_histogram2d_fprintf(fp, h, "%g", "%g");
  }
  if (flag == 1) fclose(fp);
  return INT2FIX(status);
}

static VALUE rb_gsl_histogram2d_fscanf(VALUE obj, VALUE io)
{
  gsl_histogram2d *h = NULL;
  FILE *fp;
  int status, flag = 0;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  fp = rb_gsl_open_readfile(io, &flag);
  status = gsl_histogram2d_fscanf(fp, h);
  if (flag == 1) fclose(fp);
  return INT2FIX(status);
}

static VALUE rb_gsl_histogram2d_reset(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  gsl_histogram2d_reset(h);
  return obj;
}

static VALUE rb_gsl_histogram2d_pdf_alloc(VALUE klass, VALUE nx, VALUE ny)
{
  gsl_histogram2d_pdf *h = NULL;
  CHECK_FIXNUM(nx); CHECK_FIXNUM(ny);
  h = gsl_histogram2d_pdf_alloc(FIX2INT(nx), FIX2INT(ny));
  return Data_Wrap_Struct(klass, 0, gsl_histogram2d_pdf_free, h);
}

static VALUE rb_gsl_histogram2d_pdf_init(VALUE obj, VALUE hh)
{
  gsl_histogram2d_pdf *pdf = NULL;
  gsl_histogram2d *h = NULL;
  CHECK_HISTOGRAM2D(hh);
  Data_Get_Struct(obj, gsl_histogram2d_pdf, pdf);
  Data_Get_Struct(hh, gsl_histogram2d, h);
  gsl_histogram2d_pdf_init(pdf, h);
  return obj;
}

static VALUE rb_gsl_histogram2d_pdf_sample(VALUE obj, VALUE r1, VALUE r2)
{
  gsl_histogram2d_pdf *pdf = NULL;
  double x, y;
  Need_Float(r1); Need_Float(r2);
  Data_Get_Struct(obj, gsl_histogram2d_pdf, pdf);
  gsl_histogram2d_pdf_sample(pdf, NUM2DBL(r1), NUM2DBL(r2), &x, &y);
  return rb_ary_new3(2, rb_float_new(x), rb_float_new(y));
}

static VALUE rb_gsl_histogram2d_xrange(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  gsl_vector_view *v = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  v = gsl_vector_view_alloc(h->nx);
  v->vector.data = h->xrange;
  v->vector.size = h->nx + 1;
  v->vector.stride = 1;
  return Data_Wrap_Struct(cgsl_histogram_range, 0, gsl_vector_view_free, v);
}

static VALUE rb_gsl_histogram2d_yrange(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  gsl_vector_view *v = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  v = gsl_vector_view_alloc(h->ny);
  v->vector.data = h->yrange;
  v->vector.size = h->ny + 1;
  v->vector.stride = 1;
  return Data_Wrap_Struct(cgsl_histogram_range, 0, gsl_vector_view_free, v);
}

static VALUE rb_gsl_histogram2d_bin(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  gsl_vector_view *v = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  v = gsl_vector_view_alloc(h->nx*h->ny);
  v->vector.data = h->bin;
  v->vector.size = h->nx*h->ny;
  v->vector.stride = 1;
  return Data_Wrap_Struct(cgsl_histogram_bin, 0, gsl_vector_view_free, v);
}

void mygsl_histogram2d_yproject(const gsl_histogram2d *h2, size_t istart,
                                size_t iend, gsl_histogram *h)
{
  size_t i, j;
  double sum;
  for (j = 0; j < h2->ny; j++) {
    sum = 0.0;
    for (i = istart; i <= iend; i++) {
      if (i >= h2->nx) break;
      sum += gsl_histogram2d_get(h2, i, j);
    }
    h->bin[j] = sum;
  }
}

gsl_histogram* mygsl_histogram2d_calloc_yproject(const gsl_histogram2d *h2,
                                                 size_t istart, size_t iend)
{
  gsl_histogram *h;
  h = gsl_histogram_calloc_range(h2->ny, h2->yrange);
  mygsl_histogram2d_yproject(h2, istart, iend, h);
  return h;
}

void mygsl_histogram2d_xproject(const gsl_histogram2d *h2, size_t jstart,
                                size_t jend, gsl_histogram *h)
{
  size_t i, j;
  double sum;
  for (i = 0; i < h2->nx; i++) {
    sum = 0.0;
    for (j = jstart; j <= jend; j++) {
      if (j >= h2->ny) break;
      sum += gsl_histogram2d_get(h2, i, j);
    }
    h->bin[i] = sum;
  }
}

gsl_histogram* mygsl_histogram2d_calloc_xproject(const gsl_histogram2d *h2,
                                                 size_t jstart, size_t jend)
{
  gsl_histogram *h;
  h = gsl_histogram_calloc_range(h2->nx, h2->xrange);
  mygsl_histogram2d_xproject(h2, jstart, jend, h);
  return h;
}

static VALUE rb_gsl_histogram2d_xproject(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h2 = NULL;
  gsl_histogram *h = NULL;
  size_t jstart = 0, jend;
  Data_Get_Struct(obj, gsl_histogram2d, h2);
  switch (argc) {
  case 2:
    jstart = (size_t) FIX2INT(argv[0]);
    jend = (size_t) FIX2INT(argv[1]);
    break;
  case 1:
    jstart = (size_t) FIX2INT(argv[0]);
    jend = h2->ny;
    break;
  case 0:
    jend = h2->ny;
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 0-2)", argc);
    break;
  }
  h = mygsl_histogram2d_calloc_xproject(h2, jstart, jend);
  return Data_Wrap_Struct(cgsl_histogram, 0, gsl_histogram_free, h);
}

static VALUE rb_gsl_histogram2d_yproject(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h2 = NULL;
  gsl_histogram *h = NULL;
  size_t istart = 0, iend;
  Data_Get_Struct(obj, gsl_histogram2d, h2);
  switch (argc) {
  case 2:
    istart = (size_t) FIX2INT(argv[0]);
    iend = (size_t) FIX2INT(argv[1]);
    break;
  case 1:
    istart = (size_t) FIX2INT(argv[0]);
    iend = h2->ny;
    break;
  case 0:
    iend = h2->ny;
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 0-2)", argc);
    break;
  }
  h = mygsl_histogram2d_calloc_yproject(h2, istart, iend);
  return Data_Wrap_Struct(cgsl_histogram, 0, gsl_histogram_free, h);
}

static int mygsl_histogram2d_fread2(FILE * stream, gsl_histogram2d * h)
{
  double xmin, xmax, ymin, ymax;
  int status;
  status = gsl_block_raw_fread(stream, &xmin, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fread(stream, &xmax, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fread(stream, &ymin, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fread(stream, &ymax, 1, 1);
  if (status)    return status;
  gsl_histogram2d_set_ranges_uniform(h, xmin, xmax, ymin, ymax);
  status = gsl_block_raw_fread (stream, h->bin, h->nx*h->ny, 1);
  if (status)    return status;
  return status;
}

static int mygsl_histogram2d_fwrite2(FILE * stream, const gsl_histogram2d * h)
{
  int status;
  status = gsl_block_raw_fwrite (stream, h->xrange, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fwrite (stream, h->xrange+h->nx, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fwrite (stream, h->yrange, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fwrite (stream, h->yrange+h->ny, 1, 1);
  if (status)    return status;
  status = gsl_block_raw_fwrite (stream, h->bin, h->nx*h->ny, 1);
  return status;
}

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

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

static gsl_histogram2d* mygsl_histogram2d_calloc_integrate(const gsl_histogram2d *h,
                                                           int flag)
{
  gsl_histogram2d *hi;
  size_t i, j, k;
  size_t nx = h->nx, ny = h->ny, n = h->nx*h->ny;
  hi = gsl_histogram2d_calloc(nx, ny);
  gsl_histogram2d_set_ranges(hi, h->xrange, nx+1, h->yrange, ny+1);
  if (flag == -1) {
    hi->bin[n-1] = h->bin[n-1];
    i = nx - 1;
    for (j = ny-2, k = 0;; j--, k++) {
      hi->bin[n-1-k] = gsl_histogram2d_get(hi, i, j+1) + gsl_histogram2d_get(h, i, j);
      if (j == 0) break;
    }
    j = ny - 1;
    for (i = nx-2;; i--) {
      hi->bin[i*ny + j] = gsl_histogram2d_get(hi, i+1, j) + gsl_histogram2d_get(h, i, j);
      if (i == 0) break;
    }
    for (i = nx-2;; i--) {
      for (j = ny-2;; j--) {
        hi->bin[i*ny+j] = ((gsl_histogram2d_get(hi, i+1, j)
                            + gsl_histogram2d_get(hi, i, j+1))
                           - gsl_histogram2d_get(hi, i+1, j+1))
                          + gsl_histogram2d_get(h, i, j);
        if (j == 0) break;
      }
      if (i == 0) break;
    }
  } else {
    hi->bin[0] = h->bin[0];
    for (j = 1; j < ny; j++) hi->bin[j] = gsl_histogram2d_get(hi, 0, j-1)
                                          + gsl_histogram2d_get(h, 0, j);
    for (i = 1; i < nx; i++) hi->bin[i*ny] = gsl_histogram2d_get(hi, i-1, 0)
                                             + gsl_histogram2d_get(h, i, 0);
    for (i = 1; i < nx; i++) {
      for (j = 1; j < ny; j++) {
        hi->bin[i*ny+j] = ((gsl_histogram2d_get(hi, i-1, j)
                            + gsl_histogram2d_get(hi, i, j-1))
                           - gsl_histogram2d_get(hi, i-1, j-1))
                          + gsl_histogram2d_get(h, i, j);
      }
    }
  }
  return hi;
}

static VALUE rb_gsl_histogram2d_integrate(int argc, VALUE *argv, VALUE obj)
{
  gsl_histogram2d *h = NULL, *hi = NULL;
  int flag;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  switch (argc) {
  case 0:
    flag = 1;
    break;
  case 1:
    flag = FIX2INT(argv[0]);
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (%d for 0 or 1)", argc);
    break;
  }
  hi = mygsl_histogram2d_calloc_integrate(h, flag);
  return Data_Wrap_Struct(cgsl_histogram2d_integ, 0, gsl_histogram2d_free, hi);
}

static VALUE rb_gsl_histogram2d_normalize_bang(VALUE obj)
{
  gsl_histogram2d *h = NULL;
  double scale;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  if (CLASS_OF(obj) == cgsl_histogram2d_integ)
    scale = 1.0/h->bin[h->nx*h->ny-1];
  else
    scale = 1.0/gsl_histogram2d_sum(h);
  gsl_histogram2d_scale(h, scale);
  return obj;
}

static VALUE rb_gsl_histogram2d_normalize(VALUE obj)
{
  gsl_histogram2d *h = NULL, *hnew = NULL;
  Data_Get_Struct(obj, gsl_histogram2d, h);
  hnew = gsl_histogram2d_clone(h);
  return rb_gsl_histogram2d_normalize_bang(Data_Wrap_Struct(CLASS_OF(obj), 0, gsl_histogram2d_free, hnew));
}

void Init_gsl_histogram2d(VALUE module)
{
  VALUE cgsl_histogram2d_pdf;

  cgsl_histogram2d = rb_define_class_under(module, "Histogram2d", cGSL_Object);
  cgsl_histogram2d_view = rb_define_class_under(cgsl_histogram2d, "View",
                                                cgsl_histogram);

  cgsl_histogram2d_integ = rb_define_class_under(cgsl_histogram2d, "Integral",
                                                 cgsl_histogram2d);

  /*  rb_define_singleton_method(cgsl_histogram2d, "new", rb_gsl_histogram2d_alloc, -1);*/
  rb_define_singleton_method(cgsl_histogram2d, "alloc", rb_gsl_histogram2d_alloc, -1);
  rb_define_singleton_method(cgsl_histogram2d, "new_uniform",
                             rb_gsl_histogram2d_alloc_uniform, -1);
  rb_define_singleton_method(cgsl_histogram2d, "alloc_uniform",
                             rb_gsl_histogram2d_alloc_uniform, -1);

  rb_define_singleton_method(cgsl_histogram2d, "equal_bins_p",
                             rb_gsl_histogram2d_equal_bins_p, 2);
  rb_define_singleton_method(cgsl_histogram2d, "equal_bins_p?",
                             rb_gsl_histogram2d_equal_bins_p2, 2);

  rb_define_method(cgsl_histogram2d, "set_ranges",
                   rb_gsl_histogram2d_set_ranges, -1);
  rb_define_method(cgsl_histogram2d, "set_ranges_uniform",
                   rb_gsl_histogram2d_set_ranges_uniform, -1);

  rb_define_singleton_method(cgsl_histogram2d, "memcpy",
                             rb_gsl_histogram2d_memcpy, 2);
  rb_define_method(cgsl_histogram2d, "clone",  rb_gsl_histogram2d_clone, 0);
  rb_define_alias(cgsl_histogram2d, "duplicate", "clone");
  rb_define_method(cgsl_histogram2d, "increment",
                   rb_gsl_histogram2d_accumulate, -1);
  rb_define_alias(cgsl_histogram2d, "fill", "increment");
  rb_define_alias(cgsl_histogram2d, "accumulate", "increment");

  rb_define_method(cgsl_histogram2d, "increment2",
                   rb_gsl_histogram2d_accumulate2, -1);
  rb_define_alias(cgsl_histogram2d, "accumulate2", "increment2");
  rb_define_alias(cgsl_histogram2d, "fill2", "increment2");

  rb_define_method(cgsl_histogram2d, "get",  rb_gsl_histogram2d_get, -1);
  rb_define_alias(cgsl_histogram2d, "[]", "get");

  rb_define_method(cgsl_histogram2d, "get_xrange", rb_gsl_histogram2d_get_xrange, 1);
  rb_define_method(cgsl_histogram2d, "get_yrange", rb_gsl_histogram2d_get_yrange, 1);
  rb_define_method(cgsl_histogram2d, "xmax",  rb_gsl_histogram2d_xmax, 0);
  rb_define_method(cgsl_histogram2d, "xmin",  rb_gsl_histogram2d_xmin, 0);
  rb_define_method(cgsl_histogram2d, "ymax",  rb_gsl_histogram2d_ymax, 0);
  rb_define_method(cgsl_histogram2d, "ymin",  rb_gsl_histogram2d_ymin, 0);
  rb_define_method(cgsl_histogram2d, "nx",  rb_gsl_histogram2d_nx, 0);
  rb_define_method(cgsl_histogram2d, "ny",  rb_gsl_histogram2d_ny, 0);

  rb_define_method(cgsl_histogram2d, "find",  rb_gsl_histogram2d_find, 2);

  rb_define_method(cgsl_histogram2d, "max_val",  rb_gsl_histogram2d_max_val, 0);
  rb_define_method(cgsl_histogram2d, "max_bin",  rb_gsl_histogram2d_max_bin, 0);
  rb_define_method(cgsl_histogram2d, "min_val",  rb_gsl_histogram2d_min_val, 0);
  rb_define_method(cgsl_histogram2d, "min_bin",  rb_gsl_histogram2d_min_bin, 0);

  rb_define_method(cgsl_histogram2d, "xmean",  rb_gsl_histogram2d_xmean, 0);
  rb_define_method(cgsl_histogram2d, "ymean",  rb_gsl_histogram2d_ymean, 0);
  rb_define_method(cgsl_histogram2d, "xsigma",  rb_gsl_histogram2d_xsigma, 0);
  rb_define_method(cgsl_histogram2d, "ysigma",  rb_gsl_histogram2d_ysigma, 0);
  rb_define_method(cgsl_histogram2d, "cov",  rb_gsl_histogram2d_cov, 0);
  rb_define_method(cgsl_histogram2d, "sum",  rb_gsl_histogram2d_sum, 0);
  rb_define_alias(cgsl_histogram2d, "integral", "sum");

  rb_define_method(cgsl_histogram2d, "add",  rb_gsl_histogram2d_add, 1);
  rb_define_alias(cgsl_histogram2d, "+", "add");
  rb_define_method(cgsl_histogram2d, "sub",  rb_gsl_histogram2d_sub, 1);
  rb_define_alias(cgsl_histogram2d, "-", "sub");
  rb_define_method(cgsl_histogram2d, "mul",  rb_gsl_histogram2d_mul, 1);
  rb_define_alias(cgsl_histogram2d, "*", "mul");
  rb_define_method(cgsl_histogram2d, "div",  rb_gsl_histogram2d_div, 1);
  rb_define_alias(cgsl_histogram2d, "/", "div");
  rb_define_method(cgsl_histogram2d, "scale",  rb_gsl_histogram2d_scale2, 1);
  rb_define_method(cgsl_histogram2d, "shift",  rb_gsl_histogram2d_shift2, 1);

  rb_define_method(cgsl_histogram2d, "scale!",  rb_gsl_histogram2d_scale, 1);
  rb_define_method(cgsl_histogram2d, "shift!",  rb_gsl_histogram2d_shift, 1);

  rb_define_method(cgsl_histogram2d, "fwrite",  rb_gsl_histogram2d_fwrite, 1);
  rb_define_method(cgsl_histogram2d, "fread",  rb_gsl_histogram2d_fread, 1);
  rb_define_method(cgsl_histogram2d, "fwrite2",  rb_gsl_histogram2d_fwrite2, 1);
  rb_define_method(cgsl_histogram2d, "fread2",  rb_gsl_histogram2d_fread2, 1);
  rb_define_method(cgsl_histogram2d, "fprintf",  rb_gsl_histogram2d_fprintf, -1);
  rb_define_method(cgsl_histogram2d, "fscanf",  rb_gsl_histogram2d_fscanf, 3);

  cgsl_histogram2d_pdf = rb_define_class_under(cgsl_histogram2d, "Pdf", cGSL_Object);

  /*  rb_define_singleton_method(cgsl_histogram2d_pdf, "new",
      rb_gsl_histogram2d_pdf_alloc, 2);*/
  rb_define_singleton_method(cgsl_histogram2d_pdf, "alloc",
                             rb_gsl_histogram2d_pdf_alloc, 2);
  rb_define_method(cgsl_histogram2d_pdf, "init",  rb_gsl_histogram2d_pdf_init, 1);

  rb_define_method(cgsl_histogram2d_pdf, "sample",  rb_gsl_histogram2d_pdf_sample, 2);

  rb_define_method(cgsl_histogram2d, "xrange",  rb_gsl_histogram2d_xrange, 0);
  rb_define_method(cgsl_histogram2d, "yrange",  rb_gsl_histogram2d_yrange, 0);
  rb_define_method(cgsl_histogram2d, "bin",  rb_gsl_histogram2d_bin, 0);

  rb_define_method(cgsl_histogram2d, "reset",  rb_gsl_histogram2d_reset, 0);
  rb_define_method(cgsl_histogram2d, "xproject",  rb_gsl_histogram2d_xproject, -1);
  rb_define_method(cgsl_histogram2d, "yproject",  rb_gsl_histogram2d_yproject, -1);

  rb_define_method(cgsl_histogram2d, "integrate",  rb_gsl_histogram2d_integrate, -1);
  rb_undef_method(cgsl_histogram2d_integ, "integrate");

  rb_define_method(cgsl_histogram2d, "normalize",  rb_gsl_histogram2d_normalize, 0);
  rb_define_method(cgsl_histogram2d, "normalize!",  rb_gsl_histogram2d_normalize_bang, 0);
}

#ifdef HISTOGRAM2D_P
#undef HISTOGRAM2D_P
#endif
#ifdef CHECK_HISTOGRAM2D
#undef CHECK_HISTOGRAM2D
#endif

