/*      gausslegendre.cpp
//      
//      Copyright 2005-2011 Lucas Tsatiris <systester.project@gmail.com>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//      
*/      


#include <string.h>
#include <gmp.h>

#include "compthreads.h"
#include "pi.h"
#include "gausslegendre.h"

gldata gd[MAX_THREADS];

st_threadfunc_t
gl_initialize (st_threadfuncarg_t idx)
{

  int i;

  memcpy (&i, idx, sizeof (int));
  mpf_init (gd[i].a);
  mpf_init (gd[i].a2);
  mpf_init (gd[i].b);
  mpf_init (gd[i].b2);
  mpf_init (gd[i].t);
  mpf_init (gd[i].p);
  mpf_init (gd[i].p2);
  mpf_init (gd[i].aplus1);
  mpf_init (gd[i].bplus1);
  mpf_init (gd[i].tplus1);
  mpf_init (gd[i].pplus1);
  mpf_init (gd[i].temp1);
  mpf_init (gd[i].temp2);
  mpf_init (gd[i].pi);

  /*
   * Calculate the initial values 
   */
  mpf_set_ui (gd[i].a, 1);
  mpf_sqrt_ui (gd[i].b, 2);
  mpf_div (gd[i].b, gd[i].a, gd[i].b);
  mpf_div_ui (gd[i].t, gd[i].a, 4);
  mpf_set_ui (gd[i].p, 1);

  /*
   * Set a2 = a, b2 = b, p2 = p
   */

  mpf_set (gd[i].a2, gd[i].a);
  mpf_set (gd[i].b2, gd[i].b);
  mpf_set (gd[i].p2, gd[i].p);
  return (st_threadfunc_t) NULL;
}

/*
 *  Step 1 of the calculation: a(i+1)
 */
static st_threadfunc_t
gl_step_1 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));
  /*
   * Calculate (a+b) 
   */
  mpf_add (gd[i].temp1, gd[i].a, gd[i].b);

  /*
   * Calculate (a+b) / 2                ->      a(i+1)
   */
  mpf_div_ui (gd[i].aplus1, gd[i].temp1, 2);

  return (st_threadfunc_t) NULL;
}


/*
 *  Step 2 of the calculation: b(i+1)
 */
static st_threadfunc_t
gl_step_2 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));
  /*
   * Calculate a * b
   */
  mpf_mul (gd[i].temp2, gd[i].a2, gd[i].b2);

  /*
   * Calculate square of (a * b)        ->      b(i+1);
   */
  mpf_sqrt (gd[i].bplus1, gd[i].temp2);

  return (st_threadfunc_t) NULL;
}

/*
 *  Step 3 of the calculation: t(i+1)
 */
static st_threadfunc_t
gl_step_3 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));
  /*
   * Calculate a - a(i+1)
   */
  mpf_sub (gd[i].temp1, gd[i].a, gd[i].aplus1);

  /*
   * Calculate [a - a(i+1)] ^ 2
   */
  mpf_pow_ui (gd[i].temp1, gd[i].temp1, 2);

  /*
   * Calculate p * { [a - a(i+1)] ^ 2 }
   */
  mpf_mul (gd[i].temp1, gd[i].p, gd[i].temp1);

  /*
   * Calculate t(i+1)
   */
  mpf_sub (gd[i].tplus1, gd[i].t, gd[i].temp1);

  return (st_threadfunc_t) NULL;
}


/*
 *  Step 4 of the calculation: p(i+1)
 */
static st_threadfunc_t
gl_step_4 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));

  /*
   * Calculate p(i+1)
   */
  mpf_mul_ui (gd[i].pplus1, gd[i].p2, 2);


  return (st_threadfunc_t) NULL;
}

/*
 * Here we calculate pi. This is the body of the main loop 
 */
st_threadfunc_t
gl_calculate (st_threadfuncarg_t idx)
{

  int i;

  memcpy (&i, idx, sizeof (int));
  gl_step_1 (idx);
  gl_step_2 (idx);
  gl_step_3 (idx);
  gl_step_4 (idx);

  /*
   * Set the results to the initial variables for recursion 
   */
  mpf_set (gd[i].a, gd[i].aplus1);
  mpf_set (gd[i].b, gd[i].bplus1);
  mpf_set (gd[i].t, gd[i].tplus1);
  mpf_set (gd[i].p, gd[i].pplus1);

  mpf_set (gd[i].a2, gd[i].a);
  mpf_set (gd[i].b2, gd[i].b);
  mpf_set (gd[i].p2, gd[i].p);

  return (st_threadfunc_t) NULL;

}

/*
 * This is the final pass 
 */
st_threadfunc_t
gl_finalize (st_threadfuncarg_t idx)
{

  int i;

  memcpy (&i, idx, sizeof (int));

  /*
   * Calculate a+b
   */
  mpf_add (gd[i].temp1, gd[i].a, gd[i].b);

  /*
   * Calculate (a+b)^2
   */
  mpf_pow_ui (gd[i].temp1, gd[i].temp1, 2);

  /*
   * Multiply t * 4
   */
  mpf_mul_ui (gd[i].temp2, gd[i].t, 4);

  /*
   * Calculate pi
   */
  mpf_div (gd[i].pi, gd[i].temp1, gd[i].temp2);

  return (st_threadfunc_t) NULL;

}
