#include <stdlib.h>
#include <math.h>
#include <float.h>

#include "libnum/include/num.h"

/* neq:      number of equations
 * step:     initial time step
 * duration: total duration of the simulation
 * ic:       initial conditions vector
 * y:        solution vector
 * epsil:    precision on the data
 * data:     pointer to data structure
 */

/* Runge-Kutta-Fehlberg 4-5 order */
 
int NUM_rkf(int (*f)(int neq, double time, double *y, double *dy, void *data), 
            int neq, double step, double duration, float *ic, 
            double **y, double epsil, void *data)
{
  int i, n;
  int col;
  int status;
  
  double h;
  double t = 0.0;

  double *a;
  
  double *tmp;
  double *dy;
  double *K1, *K2, *K3, *K4, *K5, *K6;   

  double *E; /* Error vector of the error */
  
  double err; /* maximum error */
  double beta;
  
  tmp = (double *) malloc(sizeof(double) * neq);
  dy  = (double *) malloc(sizeof(double) * neq);

  E   = (double *) malloc(sizeof(double) * neq);
  
  K1  = (double *) malloc(sizeof(double) * neq);
  K2  = (double *) malloc(sizeof(double) * neq);
  K3  = (double *) malloc(sizeof(double) * neq);
  K4  = (double *) malloc(sizeof(double) * neq);
  K5  = (double *) malloc(sizeof(double) * neq);
  K6  = (double *) malloc(sizeof(double) * neq);

  
  h = step;
  n = 0;

  col = neq + 1;
  
  a = *y = (double *) malloc(sizeof(double) * col * (n + 1));
 
  
  for (i = 0; i < neq; i++)
  {
    tmp[i] = a[i + col*0] = (double) ic[i]; /* initial conditions */
  }
  a[neq] = t;
  
  while (t < duration)
  {  
    a = *y = (double *) realloc(*y, sizeof(double) * col * (n + 2));
    
    for (i = 0; i < neq; i++)
    {
      status = f(neq, t, tmp, dy, data);
      K1[i] = h * dy[i];
      
      tmp[i] = a[i + col*n] + K1[i]/4.0;  /* for the next step */          
    }
      
    for (i = 0; i < neq; i++)
    {
      status = f(neq, t + h/4.0, tmp, dy, data);
      K2[i] = h * dy[i];

      tmp[i] = a[i + col*n] + 3.0*K1[i]/32.0 + 9.0*K2[i]/32.0;
        
    }

    for (i = 0; i < neq; i++)
    {
      status = f(neq, t + 3.0*h/8.0, tmp, dy, data);
      K3[i] = h * dy[i];

      tmp[i] = a[i + col*n] + 1932.0*K1[i]/2197.0 - 7200.0*K2[i]/2197.0 +
        7296.0*K3[i]/2197.0;
    }

    
    for (i = 0; i < neq; i++)
    {
      status = f(neq, t + 12.0*h/13.0, tmp, dy, data);
      K4[i] = h * dy[i];
      
      tmp[i] = a[i + col*n] + 439.0*K1[i]/216.0 - 8.0*K2[i] +
        3680.0*K3[i]/513.0 - 845.0*K4[i]/4104.0;
    }

    for (i = 0; i < neq; i++)
    {
      status = f(neq, t + h, tmp, dy, data);
      K5[i] = h * dy[i];
      
      tmp[i] = a[i + col*n] - 8.0*K1[i]/27.0 + 2.0*K2[i] -
        3544.0*K3[i]/2565.0 + 1859.0*K4[i]/4104.0 - 11.0*K5[i]/40.0;
    }
    
    for (i = 0; i < neq; i++)
    {
      status = f(neq, t + h/2.0, tmp, dy, data);
      K6[i] = h * dy[i];
    }

    err = 0.0;
    for (i = 0; i <  neq; i++)
    {
      E[i] = fabs(K1[i]/360.0 - 128.0*K3[i]/4275.0 - 2197.0*K4[i]/75240.0 +
                   K5[i]/50.0 + 2.0*K6[i]/55.0); /* /h ?? */
      /*printf("E[%d] = %f\n", i, E[i]);*/
      err = ((E[i] > err) ? E[i] : err);      
    }

    /*printf("err = %e\n", err);*/

    if ((err < epsil) || (h <= step/1000) )
    {
      t += h;
      
      for (i = 0; i < neq; i++)
      {
        a[i + col*(n+1)] = a[i + col*n] + 25.0*K1[i]/216.0 +
          1408.0*K3[i]/2565.0 + 2197.0*K4[i]/4104.0 - 0.2*K5[i];
      }
      /* store the time */
      a[neq + col*(n+1)] = t;
      n++;
    }
    
    beta = pow(epsil/(2*err), 0.25);
    
    if (beta < 0.1)
    {
      h = 0.1* h;
    }
    else if (beta > 4)
    {
      h = 4.0*h;
    }
    else
    {
      h = beta * h;
    }

    /* we have to prevent too little h*/
    if (h < step/1000)
      h = step/1000;

    /* prevent too big */
    if (h > duration/16)
      h = duration/16;
    
    if (t + h > duration)
    {
      h =  duration - t;
    }
    
  }

  return n+1;
}

