/*      master.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 "systester.h"

unsigned long long xdigits;
int pflag, lflag, pass, errors, start, stop, first_error, chksum_err, mode;

/*
 * This is the control function
 */
void
master (unsigned long long iter, int threads, int algo)
{
  st_thread_t th[MAX_THREADS];
  st_threadret_t rth[MAX_THREADS];
  FILE *logfile, *fp;
  mp_exp_t expvar;
  unsigned long long prec = 0, bits, i = 0, n = 1;
  int p[MAX_THREADS], k = 0;
  char *pistr = NULL;
  char column[111] = "", pxdigits[13] = "", ptime[13] = "", failtime[13] =
    "No fail yet", xstr[256];

  if (algo == 0)		/* Borwein */
    prec = 4;
  else if (algo == 1)		/* Gauss */
    prec = 2;

  if (iter < 4)
    iter = 4;

/*
* Calculate precision xdigits
*/

  for (i = 0; i < iter - 1; i++)
    prec += prec;
  xdigits = prec;
  pth_stacksize = xdigits * 2;
  bits = (unsigned long) ((prec / 9) * 32) + 2;
  PRINT (" Calculating %ld digits of pi\n", (unsigned long) xdigits);

/*
* Initialize the mpf's
*/
  mpf_set_default_prec (bits);
  if (algo == 0)		/* Borwein */
    prec = 2;
  else if (algo == 1)		/* Gauss */
    prec = 1;


  stop = pitimer ();
  outtime (start, stop, ptime);

  for (k = 0; k < threads; k++)
    {
      p[k] = k;
      if (algo == 0)
	rth[k] =
	  st_thread_create (&th[p[k]], borwein_initialize,
			    (st_threadfuncarg_t) & p[k]);
      else if (algo == 1)
	rth[k] =
	  st_thread_create (&th[p[k]], gl_initialize,
			    (st_threadfuncarg_t) & p[k]);
    }

  PRINT ("%s", " -----------------------------------------\n");
  PRINT (" Turn:%4d  ", pass);
  PRINT ("Errors:%4d       ", errors);
  PRINT ("Threads:%4d\n", threads);
  PRINT (" Running For: %12s  ", ptime);
  PRINT ("ChkErr:%4d\n", chksum_err);
  PRINT (" Failed After: %12s\n", failtime);
  PRINT ("%s", " -----------------------------------------\n");
  PRINT ("%s", " Loop Digits State    Time\n");
  PRINT ("%s", " ---------------------------------\n");
  outxdigits (prec, pxdigits);

  PRINT (" %4d", 0);
  PRINT (" %6s ", pxdigits);
  PROCESS_EVENTS;

  st_thread_join (threads, th, rth);

  stop = pitimer ();
  if ((threads == 1) || (mode == 1))
    sprintf (xstr, " N/A ");
  if ((threads > 1) && (mode == 0))
    {
      sprintf (xstr, " OK ");
      for (k = 1; k < threads; k++)

	if (algo == 0)		/* Borwein */
	  {
	    if (mpf_cmp (bd[k - 1].p1, bd[k].p1) != 0)
	      {
		sprintf (xstr, " BAD ");
		errors++;
		if (first_error == 0)
		  {
		    first_error = pitimer ();
		    outtime (start, first_error, failtime);
		  }
		goto lbl100_exit;
	      }
	  }
	else if (algo == 1)	/*Gauss */
	  {
	    if (mpf_cmp (gd[k - 1].t, gd[k].t) != 0)
	      {
		sprintf (xstr, " BAD ");
		errors++;
		if (first_error == 0)
		  {
		    first_error = pitimer ();
		    outtime (start, first_error, failtime);
		  }
		goto lbl100_exit;
	      }
	  }
    }

  PRINT ("%s", xstr);

  outtime (start, stop, ptime);
  PRINT (" %s\n", ptime);
  PROCESS_EVENTS;

/*
* The main loop
*/
  if (algo == 0)		/* Borwein */
    prec = 4;
  else if (algo == 1)		/* Gauss */
    prec = 2;
  i = 0;
  while (i < (iter - 1))
    {
      if (stoppressed == TRUE)
	goto lbl100_exit;

      outxdigits (prec, pxdigits);
      PRINT (" %4d", (int) (i + 1));
      PRINT (" %6s ", pxdigits);
      PROCESS_EVENTS;

      for (k = 0; k < threads; k++)
	if (algo == 0)
	  rth[k] =
	    st_thread_create (&th[p[k]], borwein_calculate,
			      (st_threadfuncarg_t) & p[k]);
	else if (algo == 1)
	  rth[k] =
	    st_thread_create (&th[p[k]], gl_calculate,
			      (st_threadfuncarg_t) & p[k]);


      st_thread_join (threads, th, rth);
      stop = pitimer ();

      if ((threads == 1) || (mode == 1))
	sprintf (xstr, " N/A ");

      if ((threads > 1) && (mode == 0))
	{
	  sprintf (xstr, " OK ");
	  for (k = 1; k < threads; k++)

	    if (algo == 0)	/* Borwein */
	      {
		if (mpf_cmp (bd[k - 1].p1, bd[k].p1) != 0)
		  {
		    sprintf (xstr, " BAD ");
		    errors++;
		    if (first_error == 0)
		      {
			first_error = pitimer ();
			outtime (start, first_error, failtime);
		      }
		    goto lbl100_exit;
		  }
	      }
	    else if (algo == 1)	/*Gauss */
	      {
		if (mpf_cmp (gd[k - 1].t, gd[k].t) != 0)
		  {
		    sprintf (xstr, " BAD ");
		    errors++;
		    if (first_error == 0)
		      {
			first_error = pitimer ();
			outtime (start, first_error, failtime);
		      }
		    goto lbl100_exit;
		  }
	      }
	}
      PRINT ("%s", xstr);
      outtime (start, stop, ptime);
      PRINT (" %s\n", ptime);
      PROCESS_EVENTS;

/*
* Calculate the precision of the next pass
*/
      prec += prec;
      i++;
    }

/*
* The last pass is here. We don't need to prepare the next one
*/
  outxdigits (prec, pxdigits);
  PRINT (" %4d", (int) (i + 1));
  PRINT (" %6s ", pxdigits);
  PROCESS_EVENTS;

  for (k = 0; k < threads; k++)
    if (algo == 0)
      rth[k] =
	st_thread_create (&th[p[k]], borwein_finalize,
			  (st_threadfuncarg_t) & p[k]);
    else if (algo == 1)
      rth[k] =
	st_thread_create (&th[p[k]], gl_finalize,
			  (st_threadfuncarg_t) & p[k]);

  st_thread_join (threads, th, rth);
  stop = pitimer ();

  if ((threads == 1) || (mode == 1))
    sprintf (xstr, " N/A ");
  if ((threads > 1) && (mode == 0))
    {
      sprintf (xstr, " OK ");
      for (k = 1; k < threads; k++)
	{
	  if (algo == 0)	/* Borwein */
	    {
	      if (mpf_cmp (bd[k - 1].p1, bd[k].p1) != 0)
		{
		  sprintf (xstr, " BAD ");
		  errors++;
		  if (first_error == 0)
		    {
		      first_error = pitimer ();
		      outtime (start, first_error, failtime);
		    }
		  goto lbl100_exit;
		}
	    }
	  else if (algo == 1)	/*Gauss */
	    {
	      if (mpf_cmp (gd[k - 1].t, gd[k].t) != 0)
		{
		  sprintf (xstr, " BAD ");
		  errors++;
		  if (first_error == 0)
		    {
		      first_error = pitimer ();
		      outtime (start, first_error, failtime);
		    }
		  goto lbl100_exit;
		}
	    }
	}
    }
  PRINT ("%s", xstr);
  outtime (start, stop, ptime);
  PRINT (" %s\n", ptime);
  PROCESS_EVENTS;

  pistr = (char *) malloc (prec + 100);
  if (algo == 0)
    mpf_get_str (pistr, &expvar, 10, prec, bd[0].p2);
  else if (algo == 1)
    mpf_get_str (pistr, &expvar, 10, prec, gd[0].pi);

/*
* Checksum validation
*/
  PRINT ("%s", " Checksum validation...");

  if (algo == 0)
    PRINT ("%s", "Borwein");

  if (algo == 1)
    PRINT ("%s", "Gauss");

  if (validate_checksum (prec, pistr))
    sprintf (xstr, " PASSED!!!\n");
  else
    {
      sprintf (xstr, " FAILED!!!\n");
      chksum_err++;
    }

  PRINT ("%s", xstr);
  PROCESS_EVENTS;

/*
* Print the result
*/
  if (pflag == 1)
    {
      if (algo == 0)
	mpf_get_str (pistr, &expvar, 10, prec, bd[0].p2);
      else if (algo == 1)
	mpf_get_str (pistr, &expvar, 10, prec, gd[0].pi);

      PRINT ("%s", " Creating CPUPI.DAT...");
      PROCESS_EVENTS;
      //fp = fopen ("CPUPI.DAT", "w");
      fp = fopen (CPUPI_FILE_PATH, "w");

      fprintf (fp, " PI=3.%c%c", 10, 13);
      memset (column, ' ', 110);
      column[110] = 0;
      k = 0;
      n = 1;
      for (i = 1; i < prec; i += 10)
	{
	  memcpy (&column[n], &pistr[i], 10);
	  n += 11;
	  if (n > 110)
	    {
	      fprintf (fp, "%s%c%c", column, 10, 13);
	      n = 1;
	      k++;
	      if (k == 10)
		{
		  fprintf (fp, " %c%c", 10, 13);
		  k = 0;
		}
	    }
	}
      n = 1;
      i = prec - (prec % 100) + 1;
      memset (column, ' ', 110);
      for (; i < prec + 1; i++)
	{
	  column[n] = pistr[i];
	  n++;
	  if ((n % 11) == 0)
	    {
	      column[n] = ' ';
	      n++;
	    }
	}
      fprintf (fp, "%s%c%c", column, 10, 13);

      fclose (fp);
      PRINT ("%s", "done!!! \n\n");
      PROCESS_EVENTS;

    }

/*
* Now exiting
*/
lbl100_exit:
/*
* Log
*/

  if (lflag)
    {
      //logfile = fopen ("systester.log", "a+");
      logfile = fopen (LOG_FILE_PATH, "a+");
      fprintf (logfile, " Turn:%4d  Errors:%4d  Threads:%4d", pass, errors,
	       threads);
      fprintf (logfile,
	       " Running For: %12s Failed After: %12s - Checksum Errors: %4d%c%c",
	       ptime, failtime, chksum_err, 10, 13);
      fclose (logfile);
    }

  if (stoppressed == TRUE)
    {
      PRINT ("%s", "Interrupted by user \n\n");
      PROCESS_EVENTS;
    }

  if (algo == 0)		/* Borwein */
    {
      for (i = 0; i < threads; i++)
	{
	  mpf_clear (bd[i].x1);
	  mpf_clear (bd[i].x2);
	  mpf_clear (bd[i].p1);
	  mpf_clear (bd[i].p2);
	  mpf_clear (bd[i].w1);
	  mpf_clear (bd[i].w2);
	  mpf_clear (bd[i].sqrx);
	  mpf_clear (bd[i].sqr1x);
	  mpf_clear (bd[i].x2plus1);
	  mpf_clear (bd[i].w1plus1);
	  mpf_clear (bd[i].x2divw1);
	}
    }
  else if (algo == 1)		/* Gauss */
    {
      for (i = 0; i < threads; i++)
	{
	  mpf_clear (gd[i].a);
	  mpf_clear (gd[i].a2);
	  mpf_clear (gd[i].b);
	  mpf_clear (gd[i].b2);
	  mpf_clear (gd[i].t);
	  mpf_clear (gd[i].p);
	  mpf_clear (gd[i].p2);
	  mpf_clear (gd[i].aplus1);
	  mpf_clear (gd[i].bplus1);
	  mpf_clear (gd[i].tplus1);
	  mpf_clear (gd[i].pplus1);
	  mpf_clear (gd[i].temp1);
	  mpf_clear (gd[i].temp2);
	  mpf_clear (gd[i].pi);
	}
    }

/*
  for (k = 0; k < threads; k++)
    thread_cancel (th[p[k]]);
*/

  if (pistr != NULL)
    free (pistr);
}
