/*
  root.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_array.h"
#include "include/rb_gsl_common.h"
#include "include/rb_gsl_function.h"
#include "include/rb_gsl_root.h"

EXTERN VALUE cgsl_function_fdf;

enum {
  GSL_ROOT_FSOLVER_BISECTION,
  GSL_ROOT_FSOLVER_FALSEPOS,
  GSL_ROOT_FSOLVER_BRENT,
  GSL_ROOT_FDFSOLVER_NEWTON,
  GSL_ROOT_FDFSOLVER_SECANT,
  GSL_ROOT_FDFSOLVER_STEFFENSON,
};

/*****/

static VALUE rb_gsl_fsolver_new(VALUE klass, VALUE t)
{
  gsl_root_fsolver *s = NULL;
  const gsl_root_fsolver_type *T;
  char name[32];
  switch (TYPE(t)) {
  case T_STRING:
    strcpy(name, STR2CSTR(t));
    if (!str_tail_grep(name, "bisection")) {
      T = gsl_root_fsolver_bisection;
    } else if (!str_tail_grep(name, "falsepos")) {
      T = gsl_root_fsolver_falsepos;
    } else if (!str_tail_grep(name, "brent")) {
      T = gsl_root_fsolver_brent;
    } else {
      rb_raise(rb_eTypeError,
               "type must be \"bisection\" or \"falsepos\", or \"brent\".");
    }
    break;
  case T_FIXNUM:
    switch (FIX2INT(t)) {
    case GSL_ROOT_FSOLVER_BISECTION:
      T = gsl_root_fsolver_bisection;
      break;
    case GSL_ROOT_FSOLVER_FALSEPOS:
      T = gsl_root_fsolver_falsepos;
      break;
    case GSL_ROOT_FSOLVER_BRENT:
      T = gsl_root_fsolver_brent;
      break;
    default:
      rb_raise(rb_eTypeError, "type must be BISECTION or FALSEPOS, or BRENT.");
      break;
    }
    break;
  default:
    rb_raise(rb_eTypeError, "wrong argument type %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(t)));
    break;
  }
  s = gsl_root_fsolver_alloc(T);
  return Data_Wrap_Struct(klass, 0, gsl_root_fsolver_free, s);
}

static VALUE rb_gsl_fsolver_set(VALUE obj, VALUE func, VALUE xl, VALUE xh)
{
  gsl_root_fsolver *s = NULL;
  gsl_function *fff = NULL;
  double xlow, xup;
  Need_Float(xl); Need_Float(xh);
  CHECK_FUNCTION(func);
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  Data_Get_Struct(func, gsl_function, fff);
  xlow = NUM2DBL(xl);
  xup = NUM2DBL(xh);
  gsl_root_fsolver_set(s, fff, xlow, xup);
  return obj;
}

static VALUE rb_gsl_fsolver_iterate(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return INT2FIX(gsl_root_fsolver_iterate(s));
}

static VALUE rb_gsl_fsolver_root(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_float_new(gsl_root_fsolver_root(s));
}

static VALUE rb_gsl_fsolver_x_lower(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_float_new(gsl_root_fsolver_x_lower(s));
}

static VALUE rb_gsl_fsolver_x_upper(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_float_new(gsl_root_fsolver_x_upper(s));
}

static VALUE rb_gsl_fsolver_name(VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return rb_str_new2(gsl_root_fsolver_name(s));
}

static VALUE rb_gsl_fsolver_test_interval(VALUE obj, VALUE eabs, VALUE erel)
{
  gsl_root_fsolver *s = NULL;
  Need_Float(eabs); Need_Float(erel);
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  return INT2FIX(gsl_root_test_interval(s->x_lower, s->x_upper,
                                        NUM2DBL(eabs), NUM2DBL(erel)));
}

static VALUE rb_gsl_root_test_interval(VALUE obj, VALUE xl, VALUE xu, VALUE eabs,
                                       VALUE erel)
{
  Need_Float(xl); Need_Float(xu);
  Need_Float(eabs); Need_Float(erel);
  return INT2FIX(gsl_root_test_interval(NUM2DBL(xl), NUM2DBL(xu),
                                        NUM2DBL(eabs), NUM2DBL(erel)));
}

static VALUE rb_gsl_root_test_delta(VALUE obj, VALUE xl, VALUE xu, VALUE eabs,
                                    VALUE erel)
{
  Need_Float(xl); Need_Float(xu);
  Need_Float(eabs); Need_Float(erel);
  return INT2FIX(gsl_root_test_delta(NUM2DBL(xl), NUM2DBL(xu),
                                     NUM2DBL(eabs), NUM2DBL(erel)));
}

static VALUE rb_gsl_root_test_residual(VALUE obj, VALUE xl,VALUE eabs)
{
  Need_Float(xl); Need_Float(eabs);
  return INT2FIX(gsl_root_test_residual(NUM2DBL(xl),  NUM2DBL(eabs)));
}

static VALUE rb_gsl_fsolver_solve(int argc, VALUE *argv, VALUE obj)
{
  gsl_root_fsolver *s = NULL;
  gsl_function *F = NULL;
  double x, xl, xh, epsabs = 0.0, epsrel = 1e-6;
  int status, iter = 0, max_iter = 100;
  switch (argc) {
  case 3:
    Check_Type(argv[2], T_ARRAY);
    epsabs = NUM2DBL(rb_ary_entry(argv[2], 0));
    epsrel = NUM2DBL(rb_ary_entry(argv[2], 1));
  /* no break */
  case 2:
    Check_Type(argv[1], T_ARRAY);
    xl = NUM2DBL(rb_ary_entry(argv[1], 0));
    xh = NUM2DBL(rb_ary_entry(argv[1], 1));
    break;
  default:
    rb_raise(rb_eArgError,
             "Usage: solve(f = Function, range = Array, eps = Array)");
    break;
  }
  CHECK_FUNCTION(argv[0]);
  Data_Get_Struct(argv[0], gsl_function, F);
  Data_Get_Struct(obj, gsl_root_fsolver, s);
  gsl_root_fsolver_set(s, F, xl, xh);
  do {
    iter++;
    status = gsl_root_fsolver_iterate (s);
    x = gsl_root_fsolver_root (s);
    xl = gsl_root_fsolver_x_lower (s);
    xh = gsl_root_fsolver_x_upper (s);
    status = gsl_root_test_interval (xl, xh, epsabs, epsrel);
    if (status == GSL_SUCCESS) break;
  } while (status == GSL_CONTINUE && iter < max_iter);
  return rb_ary_new3(3, rb_float_new(x), INT2FIX(iter), INT2FIX(status));
}

static VALUE rb_gsl_fdfsolver_new(VALUE klass, VALUE t)
{
  gsl_root_fdfsolver *s = NULL;
  const gsl_root_fdfsolver_type *T;
  char name[32];
  switch (TYPE(t)) {
  case T_STRING:
    strcpy(name, STR2CSTR(t));
    if (!str_tail_grep(name, "newton")) {
      T = gsl_root_fdfsolver_newton;
    } else if (!str_tail_grep(name, "secant")) {
      T = gsl_root_fdfsolver_secant;
    } else if (!str_tail_grep(name, "steffenson")) {
      T = gsl_root_fdfsolver_steffenson;
    } else {
      rb_raise(rb_eTypeError, "type must be NEWTON or SECANT, or STEFFENSON.");
    }
    break;
  case T_FIXNUM:
    switch (FIX2INT(t)) {
    case GSL_ROOT_FDFSOLVER_NEWTON:
      T = gsl_root_fdfsolver_newton;
      break;
    case GSL_ROOT_FDFSOLVER_SECANT:
      T = gsl_root_fdfsolver_secant;
      break;
    case GSL_ROOT_FDFSOLVER_STEFFENSON:
      T = gsl_root_fdfsolver_steffenson;
      break;
    default:
      rb_raise(rb_eTypeError, "type must be NEWTON or SECANT, or STEFFENSON.");
      break;
    }
    break;
  default:
    rb_raise(rb_eTypeError, "wrong argument type %s (String or Fixnum expected)",
             rb_class2name(CLASS_OF(t)));
    break;
  }
  s = gsl_root_fdfsolver_alloc(T);
  return Data_Wrap_Struct(klass, 0, gsl_root_fdfsolver_free, s);
}

static VALUE rb_gsl_fdfsolver_set(VALUE obj, VALUE func, VALUE r)
{
  gsl_root_fdfsolver *s = NULL;
  gsl_function_fdf *fff = NULL;
  double root;
  CHECK_FUNCTION_FDF(func);
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  Data_Get_Struct(func, gsl_function_fdf, fff);
  root = NUM2DBL(r);
  gsl_root_fdfsolver_set(s, fff, root);
  return obj;
}

static VALUE rb_gsl_fdfsolver_iterate(VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  return INT2FIX(gsl_root_fdfsolver_iterate(s));
}

static VALUE rb_gsl_fdfsolver_root(VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  return rb_float_new(gsl_root_fdfsolver_root(s));
}

static VALUE rb_gsl_fdfsolver_name(VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  return rb_str_new2(gsl_root_fdfsolver_name(s));
}

static VALUE rb_gsl_fdfsolver_solve(int argc, VALUE *argv, VALUE obj)
{
  gsl_root_fdfsolver *s = NULL;
  double x = 0.0, x0, epsabs = 0.0, epsrel = 1e-6;
  gsl_function_fdf *F = NULL;
  int status, iter = 0, max_iter = 100;
  switch (argc) {
  case 3:
    Check_Type(argv[2], T_ARRAY);
    epsabs = NUM2DBL(rb_ary_entry(argv[2], 0));
    epsrel = NUM2DBL(rb_ary_entry(argv[2], 1));
  /* no break */
  case 2:
    Need_Float(argv[1]);
    x0 = NUM2DBL(argv[1]);
    break;
  default:
    rb_raise(rb_eArgError, "Usage: solve(f = Function, range = Array, eps = Array)");
    break;
  }
  CHECK_FUNCTION_FDF(argv[0]);
  Data_Get_Struct(argv[0], gsl_function_fdf, F);
  Data_Get_Struct(obj, gsl_root_fdfsolver, s);
  gsl_root_fdfsolver_set(s, F, x0);
  do {
    iter++;
    status = gsl_root_fdfsolver_iterate (s);
    x0 = x;
    x = gsl_root_fdfsolver_root (s);
    status = gsl_root_test_delta(x, x0, epsabs, epsrel);
    if (status == GSL_SUCCESS) break;
  } while (status == GSL_CONTINUE && iter < max_iter);
  return rb_ary_new3(3, rb_float_new(x), INT2FIX(iter), INT2FIX(status));
}

static VALUE rb_gsl_function_rootfinder(int argc, VALUE *argv, VALUE obj)
{
  int status, iter = 0, max_iter = 1000;
  const gsl_root_fsolver_type *T;
  gsl_root_fsolver *s = NULL;
  gsl_function *F = NULL;
  double r, a, b, epsabs = 0.0, epsrel = 1e-6;
  Data_Get_Struct(obj, gsl_function, F);
  switch (argc) {
  case 2:
    a = NUM2DBL(argv[0]);
    b = NUM2DBL(argv[1]);
    break;
  case 1:
    switch (TYPE(argv[0])) {
    case T_ARRAY:
      a = NUM2DBL(rb_ary_entry(argv[0], 0));
      b = NUM2DBL(rb_ary_entry(argv[0], 1));
      break;
    default:
      rb_raise(rb_eTypeError, "interval must be given by an array [a, b]");
    }
    break;
  default:
    rb_raise(rb_eArgError, "interval must be given");
    break;
  }
  T = gsl_root_fsolver_brent;
  s = gsl_root_fsolver_alloc (T);
  gsl_root_fsolver_set (s, F, a, b);
  do {
    iter++;
    status = gsl_root_fsolver_iterate (s);
    r = gsl_root_fsolver_root (s);
    a = gsl_root_fsolver_x_lower (s);
    b = gsl_root_fsolver_x_upper (s);
    status = gsl_root_test_interval (a, b, epsabs, epsrel);
    if (status == GSL_SUCCESS) break;
  } while (status == GSL_CONTINUE && iter < max_iter);
  gsl_root_fsolver_free(s);
  if (status == GSL_SUCCESS) {
    return rb_ary_new3(3, rb_float_new(r), INT2FIX(iter), INT2FIX(status));
  } else {
    printf("not converged\n");
    return Qfalse;
  }
}

void Init_gsl_root(VALUE module)
{
  VALUE mgsl_root;
  VALUE cgsl_fsolver;
  VALUE cgsl_fdfsolver;

  mgsl_root = rb_define_module_under(module, "Root");

  cgsl_fsolver = rb_define_class_under(mgsl_root, "FSolver", cGSL_Object);
  rb_define_singleton_method(cgsl_fsolver, "alloc", rb_gsl_fsolver_new, 1);

  rb_define_method(cgsl_fsolver, "set", rb_gsl_fsolver_set, 3);
  rb_define_method(cgsl_fsolver, "iterate", rb_gsl_fsolver_iterate, 0);
  rb_define_method(cgsl_fsolver, "root", rb_gsl_fsolver_root, 0);
  rb_define_method(cgsl_fsolver, "name", rb_gsl_fsolver_name, 0);
  rb_define_method(cgsl_fsolver, "x_lower", rb_gsl_fsolver_x_lower, 0);
  rb_define_method(cgsl_fsolver, "x_upper", rb_gsl_fsolver_x_upper, 0);
  rb_define_method(cgsl_fsolver, "test_interval", rb_gsl_fsolver_test_interval, 2);
  rb_define_method(cgsl_fsolver, "solve", rb_gsl_fsolver_solve, -1);

  rb_define_singleton_method(mgsl_root, "test_interval",
                             rb_gsl_root_test_interval, 4);
  rb_define_singleton_method(mgsl_root, "test_delta",
                             rb_gsl_root_test_delta, 4);
  rb_define_singleton_method(mgsl_root, "test_residual",
                             rb_gsl_root_test_residual, 2);

  cgsl_fdfsolver = rb_define_class_under(mgsl_root, "FdfSolver", cGSL_Object);
  rb_define_singleton_method(cgsl_fdfsolver, "alloc", rb_gsl_fdfsolver_new, 1);

  rb_define_method(cgsl_fdfsolver, "set", rb_gsl_fdfsolver_set, 2);
  rb_define_method(cgsl_fdfsolver, "iterate", rb_gsl_fdfsolver_iterate, 0);
  rb_define_method(cgsl_fdfsolver, "root", rb_gsl_fdfsolver_root, 0);
  rb_define_method(cgsl_fdfsolver, "name", rb_gsl_fdfsolver_name, 0);
  rb_define_method(cgsl_fdfsolver, "solve", rb_gsl_fdfsolver_solve, -1);

  rb_define_method(cgsl_function, "fsolve", rb_gsl_function_rootfinder, -1);
  rb_define_alias(cgsl_function, "solve", "fsolve");

  rb_define_const(cgsl_fsolver, "BISECTION", INT2FIX(GSL_ROOT_FSOLVER_BISECTION));
  rb_define_const(cgsl_fsolver, "FALSEPOS", INT2FIX(GSL_ROOT_FSOLVER_FALSEPOS));
  rb_define_const(cgsl_fsolver, "BRENT", INT2FIX(GSL_ROOT_FSOLVER_BRENT));
  rb_define_const(cgsl_fsolver, "Bisection", INT2FIX(GSL_ROOT_FSOLVER_BISECTION));
  rb_define_const(cgsl_fsolver, "Falsepos", INT2FIX(GSL_ROOT_FSOLVER_FALSEPOS));
  rb_define_const(cgsl_fsolver, "Brent", INT2FIX(GSL_ROOT_FSOLVER_BRENT));

  rb_define_const(cgsl_fdfsolver, "NEWTON", INT2FIX(GSL_ROOT_FDFSOLVER_NEWTON));
  rb_define_const(cgsl_fdfsolver, "SECANT", INT2FIX(GSL_ROOT_FDFSOLVER_SECANT));
  rb_define_const(cgsl_fdfsolver, "STEFFENSON", INT2FIX(GSL_ROOT_FDFSOLVER_STEFFENSON));
  rb_define_const(cgsl_fdfsolver, "Newton", INT2FIX(GSL_ROOT_FDFSOLVER_NEWTON));
  rb_define_const(cgsl_fdfsolver, "Secant", INT2FIX(GSL_ROOT_FDFSOLVER_SECANT));
  rb_define_const(cgsl_fdfsolver, "Steffenson", INT2FIX(GSL_ROOT_FDFSOLVER_STEFFENSON));
}
