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

static gsl_vector* gsl_poly_conv_vector(gsl_vector *v1, gsl_vector *v2);
int gsl_poly_conv(const double *a, size_t na, const double *b, size_t nb,
		  double *c, size_t *nc);
gsl_vector* gsl_poly_deconv_vector(gsl_vector *c, gsl_vector *a, gsl_vector **r);
gsl_vector* gsl_poly_deriv(gsl_vector *v);
gsl_vector* gsl_poly_integ(gsl_vector *v);

int gsl_poly_conv(const double *a, size_t na, const double *b, size_t nb,
		  double *c, size_t *nc)
{
  double x;
  int i, j;
  *nc = na + nb - 1;
  for (i = 0; i < *nc; i++) c[i] = 0.0;
  for (i = 0; i < *nc; i++) {
    if (i >= na) break;
    x = a[i];
    for (j = 0; j < *nc; j++) {
      if (j >= nb) break;
      else {
	c[i+j] += x*b[j];
      }
    }
  }
  return 0;
}

static gsl_vector* gsl_poly_conv_vector(gsl_vector *v1, gsl_vector *v2)
{
  gsl_vector *vnew = NULL;
  int n;
  size_t tmp;
  n = v1->size + v2->size - 1;
  vnew = gsl_vector_alloc(n);
  gsl_vector_set_zero(vnew);
  gsl_poly_conv(v1->data, v1->size, v2->data, v2->size, vnew->data, &tmp);
  return vnew;
}

gsl_vector* gsl_poly_reduce(gsl_vector *v)
{
  int i, nn = v->size;
  gsl_vector *vnew = NULL;


  for (i = v->size-1; i >= 0; i--) {
    if (!gsl_fcmp(gsl_vector_get(v, i), 0.0, 1e-10)) {
      nn = i;
      break;
    }
  }
  vnew = gsl_vector_alloc(nn);
  for (i = 0; i < nn; i++) {
    gsl_vector_set(vnew, i, gsl_vector_get(v, i));
  }
  return vnew;
}

gsl_vector* gsl_poly_deriv(gsl_vector *v)
{
  gsl_vector *vnew = NULL;
  int i;
  vnew = gsl_vector_alloc(v->size - 1);
  for (i = 0; i < v->size - 1; i++) {
    gsl_vector_set(vnew, i, gsl_vector_get(v, i+1)*(i+1));
  }
  return vnew;
}

gsl_vector* gsl_poly_integ(gsl_vector *v)
{
  gsl_vector *vnew = NULL;
  int i;
  vnew = gsl_vector_alloc(v->size + 1);
  gsl_vector_set(vnew, 0, 0.0);
  for (i = 1; i < v->size + 1; i++) {
    gsl_vector_set(vnew, i, gsl_vector_get(v, i-1)/i);
  }
  return vnew;
}

gsl_vector* gsl_poly_deconv_vector(gsl_vector *c, gsl_vector *a, gsl_vector **r)
{
  gsl_vector *vnew = NULL, *a2 = NULL, *c2 = NULL, *vtmp = NULL;
  double x, y, z, aa;
  int n, i, j, k, jj;

  c2 = gsl_poly_reduce(c);
  a2 = gsl_poly_reduce(a);
  n = c2->size - a2->size + 1;
  vnew = gsl_vector_alloc(n);
  *r = gsl_vector_alloc(c2->size - 1);
  gsl_vector_set_zero(vnew);
  aa = gsl_vector_get(a2, a2->size - 1);
  gsl_vector_set(vnew, n-1, gsl_vector_get(c2, c2->size-1)/aa);
  for (i = n - 2, k = 1; k < n; i--, k++) {
    x = gsl_vector_get(c2, c2->size-1-k);
    for (j = n-1; j >= 0; j--) {
      z = gsl_vector_get(vnew, j);
      jj = c2->size-1-k-j;
      if (jj > k || jj < 0) continue;
      y = gsl_vector_get(a2, jj);
      x -= y*z;
    }
    gsl_vector_set(vnew, i, x/aa);
  }
  vtmp = gsl_poly_conv_vector(vnew, a2);
  for (i = 0; i < (*r)->size; i++) {
    x = gsl_vector_get(c2, i);
    y = gsl_vector_get(vtmp, i);
    gsl_vector_set(*r, i, x - y);
  }
  gsl_vector_free(vtmp);
  gsl_vector_free(c2);
  gsl_vector_free(a2);
  return vnew;
}

static int get_polys(int argc, VALUE *argv, VALUE obj,
		     gsl_vector **v, gsl_vector **v2, int *flag1, int *flag2);
static int get_poly(int argc, VALUE *argv, VALUE obj,
		    gsl_vector **v, int *flag1);

static int get_polys(int argc, VALUE *argv, VALUE obj,
		     gsl_vector **v, gsl_vector **v2, int *flag1, int *flag2)
{
  *flag1 = 0;
  *flag2 = 0;
  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc != 2) rb_raise(rb_eArgError, "wrong number of arguments (%d for 2)",
			    argc);

    switch (TYPE(argv[0])) {
    case T_ARRAY:
      *v = get_vector(argv[0]);
      *flag1 = 1;
      break;
    default:
      CHECK_VECTOR(argv[0]);
      Data_Get_Struct(argv[0], gsl_poly, *v);
    }

    switch (TYPE(argv[1])) {
    case T_ARRAY:
      *v2 = get_vector(argv[1]);
      *flag2 = 1;
      break;
    default:
      CHECK_VECTOR(argv[1]);
      Data_Get_Struct(argv[1], gsl_poly, *v2);
    }
    break;
  default:
    if (argc != 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)",
			    argc);
    switch (TYPE(obj)) {
    case T_ARRAY:
      *v = get_vector(obj);
      *flag1 = 1;
      break;
    default:
      CHECK_VECTOR(obj);
      Data_Get_Struct(obj, gsl_poly, *v);
    }

    switch (TYPE(argv[0])) {
    case T_ARRAY:
      *v2 = get_vector(argv[0]);
      *flag2 = 1;
      break;
    default:
      CHECK_VECTOR(argv[0]);
      Data_Get_Struct(argv[0], gsl_poly, *v2);
    }
  }
  return 0;
}

static int get_poly(int argc, VALUE *argv, VALUE obj,
		    gsl_vector **v, int *flag1)
{
  *flag1 = 0;

  switch (TYPE(obj)) {
  case T_MODULE:
  case T_CLASS:
  case T_OBJECT:
    if (argc != 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)",
			    argc);
    
    switch (TYPE(argv[0])) {
    case T_ARRAY:
      *v = get_vector(argv[0]);
      *flag1 = 1;
      break;
    default:
      CHECK_VECTOR(argv[0]);
      Data_Get_Struct(argv[0], gsl_vector, *v);
    }
    break;
  default:
    if (argc != 0) rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)",
			    argc);
    switch (TYPE(obj)) {
    case T_ARRAY:
      *v = get_vector(obj);
      *flag1 = 1;
      break;
    default:
      CHECK_VECTOR(obj);
      Data_Get_Struct(obj, gsl_vector, *v);
    }
  }
  return 0;
}
static VALUE rb_gsl_poly_conv(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL, *v2 = NULL, *vnew = NULL;
  int flag1 = 0, flag2 = 0;
  get_polys(argc, argv, obj, &v, &v2, &flag1, &flag2);
  vnew = gsl_poly_conv_vector(v, v2);
  if (flag1 == 1) gsl_vector_free(v);
  if (flag2 == 1) gsl_vector_free(v2);
  return Data_Wrap_Struct(cgsl_poly, 0, gsl_vector_free, vnew);
}

static VALUE rb_gsl_poly_deconv(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL, *v2 = NULL, *vnew = NULL, *r = NULL;
  int flag1 = 0, flag2 = 0;
  get_polys(argc, argv, obj, &v, &v2, &flag1, &flag2);
  vnew = gsl_poly_deconv_vector(v, v2, &r);
  if (flag1 == 1) gsl_vector_free(v);
  if (flag2 == 1) gsl_vector_free(v2);
  return rb_ary_new3(2, Data_Wrap_Struct(cgsl_poly, 0, gsl_vector_free, vnew),
		     Data_Wrap_Struct(cgsl_poly, 0, gsl_vector_free, r));
}

static VALUE rb_gsl_poly_reduce(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL, *vnew = NULL;
  int flag = 0;
  get_poly(argc, argv, obj, &v, &flag);
  vnew = gsl_poly_reduce(v);
  return Data_Wrap_Struct(cgsl_poly, 0, gsl_vector_free, vnew);
}

static VALUE rb_gsl_poly_deriv(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL, *vnew = NULL;
  int flag = 0;
  get_poly(argc, argv, obj, &v, &flag);
  vnew = gsl_poly_deriv(v);
  if (flag == 1) gsl_vector_free(v);
  return Data_Wrap_Struct(cgsl_poly, 0, gsl_vector_free, vnew);
}

static VALUE rb_gsl_poly_integ(int argc, VALUE *argv, VALUE obj)
{
  gsl_vector *v = NULL, *vnew = NULL;
  int flag = 0;
  get_poly(argc, argv, obj, &v, &flag);
  vnew = gsl_poly_integ(v);
  if (flag == 1) gsl_vector_free(v);
  return Data_Wrap_Struct(cgsl_poly, 0, gsl_vector_free, vnew);
}


void Init_gsl_poly2()
{
  rb_define_method(cgsl_poly, "conv", rb_gsl_poly_conv, -1);
  rb_define_singleton_method(cgsl_poly, "conv", rb_gsl_poly_conv, -1);

  rb_define_method(cgsl_poly, "deconv", rb_gsl_poly_deconv, -1);
  rb_define_singleton_method(cgsl_poly, "deconv", rb_gsl_poly_deconv, -1);

  rb_define_method(cgsl_poly, "reduce", rb_gsl_poly_reduce, -1);
  rb_define_singleton_method(cgsl_poly, "reduce", rb_gsl_poly_reduce, -1);
  rb_define_method(cgsl_poly, "deriv", rb_gsl_poly_deriv, -1);
  rb_define_singleton_method(cgsl_poly, "deriv", rb_gsl_poly_deriv, -1);
  rb_define_method(cgsl_poly, "integ", rb_gsl_poly_integ, -1);
  rb_define_singleton_method(cgsl_poly, "integ", rb_gsl_poly_integ, -1);
}
