/*      qcborwein.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 <gmp.h>
#include <string.h>

#include "compthreads.h"
#include "pi.h"
#include "qcborwein.h"

bdata bd[MAX_THREADS];

/* struct borwein_data bd[MAX_THREADS]; */

/*
 * Initialize the pi calculation 
 */
st_threadfunc_t
borwein_initialize (st_threadfuncarg_t idx)
{

  int i;
  memcpy (&i, idx, sizeof (int));
  mpf_init (bd[i].x1);
  mpf_init (bd[i].x2);
  mpf_init (bd[i].p1);
  mpf_init (bd[i].p2);
  mpf_init (bd[i].w1);
  mpf_init (bd[i].w2);
  mpf_init (bd[i].sqrx);
  mpf_init (bd[i].sqr1x);
  mpf_init (bd[i].x2plus1);
  mpf_init (bd[i].w1plus1);
  mpf_init (bd[i].x2divw1);

  /*
   * Calculate the initial values 
   */
  mpf_set_d (bd[i].x1, 2.0);
  mpf_sqrt (bd[i].x1, bd[i].x1);
  mpf_add_ui (bd[i].p1, bd[i].x1, 2);
  mpf_sqrt (bd[i].w1, bd[i].x1);

  /*
   * Borwein Algorithm 
   */
  mpf_sqrt (bd[i].sqrx, bd[i].x1);
  mpf_ui_div (bd[i].sqr1x, 1, bd[i].sqrx);
  return (st_threadfunc_t) NULL;
}

/*
 *  Step 1 of the calculation: X(i+1), [X(i+1) + 1]
 */
static st_threadfunc_t
borwein_step_1 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));
  /*
   * Calculate X(i+1) 
   */
  mpf_add (bd[i].x2, bd[i].sqrx, bd[i].sqr1x);
  mpf_div_ui (bd[i].x2, bd[i].x2, 2);

  /*
   * Calculate [X(i+1) + 1] 
   */
  mpf_add_ui (bd[i].x2plus1, bd[i].x2, 1);
  return (st_threadfunc_t) NULL;
}


/*
 *  Step 2 of the calculation: [Y(i+1) + 1] 
 */
static st_threadfunc_t
borwein_step_2 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));
  /*
   * Calculate [Y(i+1) + 1] 
   */
  mpf_add_ui (bd[i].w1plus1, bd[i].w1, 1);
  return (st_threadfunc_t) NULL;
}

/*
 *  Step 3 of the calculation: [X(i+1) + 1] / [Y(i+1) + 1], P(i+1) 
 */
static st_threadfunc_t
borwein_step_3 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));
  /*
   * Calculate [X(i+1) + 1] / [Y(i+1) + 1] 
   */
  mpf_div (bd[i].x2divw1, bd[i].x2plus1, bd[i].w1plus1);

  /*
   * Calculate P(i+1) 
   */
  mpf_mul (bd[i].p2, bd[i].p1, bd[i].x2divw1);
  return (st_threadfunc_t) NULL;
}


/*
 *  Step 4 of the calculation: SQR X(i+1), 
 *                             1/SQR X(i+1), 
 *                             Y(i) * SQR X(i+1),
 *                             [Y(i) * SQR X(i+1)] + [1 / SQR X(i+1)],
 *                             Y(i) + 1, 
 *                             Y(i+1) 
 */
static st_threadfunc_t
borwein_step_4 (st_threadfuncarg_t idx)
{
  int i;
  memcpy (&i, idx, sizeof (int));


  /*
   * Calculate SQR X(i+1) 
   */
  mpf_sqrt (bd[i].sqrx, bd[i].x2);

  /*
   * Calculate 1/SQR X(i+1) 
   */
  mpf_ui_div (bd[i].sqr1x, 1, bd[i].sqrx);

  /*
   * Calculate Y(i) * SQR X(i+1) 
   */
  mpf_mul (bd[i].w2, bd[i].w1, bd[i].sqrx);

  /*
   * Calculate [Y(i) * SQR X(i+1)] + [1 / SQR X(i+1)] 
   */
  mpf_add (bd[i].w2, bd[i].w2, bd[i].sqr1x);

  /*
   * Calculate Y(i) + 1 
   */
  mpf_add_ui (bd[i].w1, bd[i].w1, 1);

  /*
   * Calculate Y(i+1) 
   */
  mpf_div (bd[i].w2, bd[i].w2, bd[i].w1);
  return (st_threadfunc_t) NULL;

}

/*
 * Here we calculate pi. This is the body of the main loop 
 */
st_threadfunc_t
borwein_calculate (st_threadfuncarg_t idx)
{
  st_thread_t th[2];
  st_threadret_t rth[2];

  int i;

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

  rth[0] = st_thread_create (&th[0], borwein_step_1, idx);
  rth[1] = st_thread_create (&th[1], borwein_step_2, idx);
  st_thread_join (2, th, rth);

#if !defined (_TEST)
  if (stoppressed == TRUE)
    return (st_threadfunc_t) NULL;
#endif

  rth[0] = st_thread_create (&th[0], borwein_step_3, idx);
  rth[1] = st_thread_create (&th[1], borwein_step_4, idx);
  st_thread_join (2, th, rth);


#if !defined (_TEST)
  if (stoppressed == TRUE)
    return (st_threadfunc_t) NULL;
#endif

  /*
   * Set the results to the initial variables for recursion 
   */
  mpf_set (bd[i].x1, bd[i].x2);
  mpf_set (bd[i].p1, bd[i].p2);
  mpf_set (bd[i].w1, bd[i].w2);

  return (st_threadfunc_t) NULL;

}

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

  int i;

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

  /*
   * Calculate X(i+1) 
   */
  mpf_add (bd[i].x2, bd[i].sqrx, bd[i].sqr1x);
  mpf_div_ui (bd[i].x2, bd[i].x2, 2);

  /*
   * Calculate [X(i+1) + 1] 
   */
  mpf_add_ui (bd[i].x2plus1, bd[i].x2, 1);

  /*
   * Calculate [Y(i+1) + 1] 
   */
  mpf_add_ui (bd[i].w1plus1, bd[i].w1, 1);

  /*
   * Calculate [X(i+1) + 1] / [Y(i+1) + 1] 
   */
  mpf_div (bd[i].x2divw1, bd[i].x2plus1, bd[i].w1plus1);

  /*
   * Calculate P(i+1) 
   */
  mpf_mul (bd[i].p2, bd[i].p1, bd[i].x2divw1);
  return (st_threadfunc_t) NULL;

}
