/*
  min.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.h"
#include "rb_gsl_function.h"
#include <gsl/gsl_min.h>

double rb_gsl_function_f(double x, void *p); 

enum {
  GSL_MIN_FMINIMIZER_GOLDENSECTION,
  GSL_MIN_FMINIMIZER_BRENT,
};
static const gsl_min_fminimizer_type* rb_gsl_min_fminimizer_type_get(VALUE t);

static const gsl_min_fminimizer_type* rb_gsl_min_fminimizer_type_get(VALUE t) 
{
  char name[32];
  switch (TYPE(t)) {
  case T_STRING:
    strcpy(name, STR2CSTR(t));
    if (str_tail_grep(name, "goldensection") == 0) 
      return gsl_min_fminimizer_goldensection;
    else if (str_tail_grep(name, "brent") == 0) 
      return gsl_min_fminimizer_brent;
    else 
      rb_raise(rb_eTypeError, "unknown type %s (goldensection or brent expected)",
	       name);
    break;
  case T_FIXNUM:
    switch (FIX2INT(t)) {
    case GSL_MIN_FMINIMIZER_GOLDENSECTION: 
      return gsl_min_fminimizer_goldensection; 
      break;
    case GSL_MIN_FMINIMIZER_BRENT: 
      return gsl_min_fminimizer_brent; 
      break;
    default: 
      rb_raise(rb_eTypeError, "unknown type (GOLDENSECION or BRENT expected)"); 
      break;
    }
    break;
  default:
    rb_raise(rb_eTypeError, "wrong argument type %s (String of Fixnum)",
	     rb_class2name(CLASS_OF(t)));
  }
}

static VALUE rb_gsl_min_fminimizer_new(VALUE klass, VALUE t)
{
  gsl_min_fminimizer *gmf = NULL;
  const gsl_min_fminimizer_type *T;
  T = rb_gsl_min_fminimizer_type_get(t);
  gmf = gsl_min_fminimizer_alloc(T);
  return Data_Wrap_Struct(klass, 0, gsl_min_fminimizer_free, gmf);
}

static VALUE rb_gsl_min_fminimizer_name(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_str_new2(gsl_min_fminimizer_name(gmf));
}

static VALUE rb_gsl_min_fminimizer_set(VALUE obj, VALUE ff, VALUE xmin, 
				       VALUE xl, VALUE xu)
{
  gsl_min_fminimizer *gmf = NULL;
  gsl_function *f = NULL;
  Need_Float(xmin); Need_Float(xl); Need_Float(xu); 
  CHECK_FUNCTION(ff);
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  Data_Get_Struct(ff, gsl_function, f);
  return INT2FIX(gsl_min_fminimizer_set(gmf, f, NUM2DBL(xmin), 
					NUM2DBL(xl), NUM2DBL(xu)));
}

static VALUE rb_gsl_min_fminimizer_set_with_values(VALUE obj, VALUE ff, 
						   VALUE xmin, VALUE fmin,
						   VALUE xl, VALUE fl, 
						   VALUE xu, VALUE fu)
{
  gsl_min_fminimizer *gmf = NULL;
  gsl_function *f = NULL;
  Need_Float(xmin); Need_Float(xl); Need_Float(xu); 
  Need_Float(fl); Need_Float(fu); 
  CHECK_FUNCTION(ff);
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  Data_Get_Struct(ff, gsl_function, f);
  return INT2FIX(gsl_min_fminimizer_set_with_values(gmf, f, NUM2DBL(xmin), 
						    NUM2DBL(fmin),
						    NUM2DBL(xl), NUM2DBL(fl),
						    NUM2DBL(xu), NUM2DBL(fu)));
}

static VALUE rb_gsl_min_fminimizer_iterate(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return INT2FIX(gsl_min_fminimizer_iterate(gmf));
}


static VALUE rb_gsl_min_fminimizer_x_lower(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_float_new(gsl_min_fminimizer_x_lower(gmf));
}

static VALUE rb_gsl_min_fminimizer_x_upper(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_float_new(gsl_min_fminimizer_x_upper(gmf));
}

#ifndef GSL_1_2_LATER
static double
gsl_min_fminimizer_x_minimum(const gsl_min_fminimizer * s)
{  
  /*  return s->x_minimum;*/
  return s->minimum;
}

static double
gsl_min_fminimizer_f_minimum(const gsl_min_fminimizer * s)
{  
  return s->f_minimum;
}
static double
gsl_min_fminimizer_f_lower(const gsl_min_fminimizer * s)
{ 
  return s->f_lower;
}
static double
gsl_min_fminimizer_f_upper(const gsl_min_fminimizer * s)
{ 
  return s->f_upper;
}
#endif

static VALUE rb_gsl_min_fminimizer_x_minimum(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_float_new(gsl_min_fminimizer_x_minimum(gmf));
}

static VALUE rb_gsl_min_fminimizer_f_minimum(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_float_new(gsl_min_fminimizer_f_minimum(gmf));
}

static VALUE rb_gsl_min_fminimizer_f_lower(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_float_new(gsl_min_fminimizer_f_lower(gmf));
}

static VALUE rb_gsl_min_fminimizer_f_upper(VALUE obj)
{
  gsl_min_fminimizer *gmf = NULL;
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  return rb_float_new(gsl_min_fminimizer_f_upper(gmf));
}

static VALUE rb_gsl_min_fminimizer_test_interval(VALUE obj, VALUE ea, VALUE er)
{
  gsl_min_fminimizer *gmf = NULL;
  double xl, xu;
  Need_Float(ea); Need_Float(er);
  Data_Get_Struct(obj, gsl_min_fminimizer, gmf);
  xl = gsl_min_fminimizer_x_lower(gmf);
  xu = gsl_min_fminimizer_x_upper(gmf);
  return INT2FIX(gsl_min_test_interval(xl, xu, NUM2DBL(ea), NUM2DBL(er)));
}

static VALUE rb_gsl_fminimizer_test_interval(VALUE obj, VALUE xl, VALUE xu,
					     VALUE ea, VALUE er)
{
  Need_Float(xl); Need_Float(xu);
  Need_Float(ea); Need_Float(er);
  return INT2FIX(gsl_min_test_interval(NUM2DBL(xl), NUM2DBL(xu),
				       NUM2DBL(ea), NUM2DBL(er)));
}

void Init_gsl_min(VALUE module)
{
  VALUE mgsl_min, cgsl_fminimizer;

  mgsl_min = rb_define_module_under(module, "Min");
  
  cgsl_fminimizer = rb_define_class_under(mgsl_min, "FMinimizer", rb_cObject);

  rb_define_const(cgsl_fminimizer, "GOLDENSECTION", 
		  INT2FIX(GSL_MIN_FMINIMIZER_GOLDENSECTION));
  rb_define_const(cgsl_fminimizer, "Goldensection", 
		  INT2FIX(GSL_MIN_FMINIMIZER_GOLDENSECTION));
  rb_define_const(cgsl_fminimizer, "BRENT",
		  INT2FIX(GSL_MIN_FMINIMIZER_BRENT));
  rb_define_const(cgsl_fminimizer, "Brent",
		  INT2FIX(GSL_MIN_FMINIMIZER_BRENT));

  rb_define_singleton_method(cgsl_fminimizer, "new", rb_gsl_min_fminimizer_new, 1);
  rb_define_singleton_method(cgsl_fminimizer, "alloc", rb_gsl_min_fminimizer_new, 1);

  rb_define_method(cgsl_fminimizer, "name", rb_gsl_min_fminimizer_name, 0);
  rb_define_method(cgsl_fminimizer, "set", rb_gsl_min_fminimizer_set, 4);
  rb_define_method(cgsl_fminimizer, "set_with_values", rb_gsl_min_fminimizer_set_with_values, 7);
  rb_define_method(cgsl_fminimizer, "iterate", rb_gsl_min_fminimizer_iterate, 0);

  rb_define_method(cgsl_fminimizer, "x_lower", rb_gsl_min_fminimizer_x_lower, 0);
  rb_define_method(cgsl_fminimizer, "x_upper", rb_gsl_min_fminimizer_x_upper, 0);
  rb_define_method(cgsl_fminimizer, "test_interval", rb_gsl_min_fminimizer_test_interval, 2);

  rb_define_singleton_method(mgsl_min, "test_interval", 
			     rb_gsl_fminimizer_test_interval, 4);

  rb_define_method(cgsl_fminimizer, "x_minimum", rb_gsl_min_fminimizer_x_minimum, 0);
  rb_define_method(cgsl_fminimizer, "f_minimum", rb_gsl_min_fminimizer_f_minimum, 0);
  rb_define_method(cgsl_fminimizer, "f_lower", rb_gsl_min_fminimizer_f_lower, 0);
  rb_define_method(cgsl_fminimizer, "f_upper", rb_gsl_min_fminimizer_f_upper, 0);

}
