/******************************************************************************/
/*                                                                            */
/*  QUAT_THR - Threaded version of tracquat(), called from execute()          */
/*                                                                            */
/*  This will be called only if MAX_THREADS > 1, so you need not bother       */
/*  compiling it if you do not wish to use multi-threading.                   */
/*                                                                            */
/******************************************************************************/

#define STRICT
#include <windows.h>
#include <commctrl.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <process.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <new.h>
#include <float.h>

#include "const.h"
#include "classes.h"
#include "extern.h"
#include "funcdefs.h"
#include "julia.rh"

#define DEBUG_THREADS 0

// These parameters are constant for the entire object, so I make them static to avoid
// having to pass them as parameters for every thread

static double Rcent ;        // Object position at display center
static double Icent ;
static double Jcent ;
static double Kcent ;
static double magnif ;       // AMY magnification: 1 / radius.
static int collapse ;        // Dimension to collapse, 1=real thru 4=K
static QUATERNION jparam ;   // additive constant 'c' for Julia sets
static double elev ;         // Elevation and azimuth (degrees CCW from +X) of view
static double az ;
static int nrows ;           // Number of top-to-bottom rows to do
static int ncols ;           // Number of left-to-right columns to do


/*
--------------------------------------------------------------------------------

   do_row - Local routine computes a single row in a thread

   We could just as well eliminate this routine and put tracquat()
   in quat_threaded below.  I think this way is a little more clear.

--------------------------------------------------------------------------------
*/

void do_row (
   int irow ,            // Row number
   double *out ,         // Ncols of t parameter (-10=miss, else -2 (far) to 2)
   QUATERNION *orbit     // Work area maximum iterations long
   )
{
   tracquat ( Rcent , Icent , Jcent , Kcent , magnif , collapse ,
              &jparam , elev , az ,
              ((double) nrows/2.0 - (double) irow - 0.5 ) / (double) (nrows-1) ,
              ncols , out , orbit ) ;
}


/*
--------------------------------------------------------------------------------

   Thread stuff...  Structure for passing information to/from threaded code
                    called by the main subroutine

--------------------------------------------------------------------------------
*/

typedef struct {
   int irow ;          // Row number for this thread
   double *rowptr ;    // Computed row placed here
   QUATERNION* orbit;  // Work area DwellLimit long (See EXECUTE.CPP for allocation)
} PARAMS ;

static unsigned int __stdcall quat_threaded ( LPVOID dp )
{
   do_row ( ((PARAMS *) dp)->irow ,      // Row number
            ((PARAMS *) dp)->rowptr ,    // Output computed here
            ((PARAMS *) dp)->orbit ) ;   // Work area
   return 0 ;
}


/*
--------------------------------------------------------------------------------

   Use threading to compute all rows

   NOTE ON THREADS... If the thread workload is low, overhead will keep CPU time
                      low.  If debug printing, one may see that only the first
                      thread keeps ending and getting replaced.  Threading behaves
                      as expected only when each thread uses a lot of CPU time.

--------------------------------------------------------------------------------
*/


int quat_thr (
   double Rcent_p ,        // Object position at display center
   double Icent_p ,
   double Jcent_p ,
   double Kcent_p ,
   double magnif_p ,       // AMY magnification: 1 / radius.
   int collapse_p ,        // Dimension to collapse, 1=real thru 4=K
   QUATERNION *jparam_p ,  // additive constant 'c' for Julia sets
   double elev_p ,         // Elevation and azimuth (degrees CCW from +X) of view
   double az_p ,
   int nrows_p ,           // Number of top-to-bottom rows to do
   int ncols_p ,           // Number of left-to-right columns to do
   double *out_mat ,       // nrows by ncols matrix of t parameters
   QUATERNION *orbit       // Work area MAX_THREADS * maximum iterations long
   )
{
   int i, k, this_row, ret_val, max_threads, n_threads, empty_slot ;
   char msg[4096] ;
   PARAMS params[MAX_THREADS] ;
   HANDLE threads[MAX_THREADS] ;

   // Copy these constant parameters to statics for common use by all threads

   Rcent = Rcent_p ;
   Icent = Icent_p ;
   Jcent = Jcent_p ;
   Kcent = Kcent_p ;
   magnif = magnif_p ;
   collapse = collapse_p ;
   jparam.r = jparam_p->r ;
   jparam.i = jparam_p->i ;
   jparam.j = jparam_p->j ;
   jparam.k = jparam_p->k ;
   elev = elev_p ;
   az = az_p ;
   nrows = nrows_p ;
   ncols = ncols_p ;

   max_threads = MAX_THREADS ;

   n_threads = 0 ;                    // Counts threads that are active
   for (i=0 ; i<max_threads ; i++)
      threads[i] = NULL ;

   empty_slot = -1 ;  // After full, will identify the thread that just completed

   this_row = 0 ;
   
   for (;;) {         // Main thread loop processes all competitors
      
/*
   Handle user ESCape
*/

      if (escape_key_pressed  ||  user_pressed_escape ()) {
         audit ( "User pressed ESCape" ) ;
         for (i=0, k=0 ; i<max_threads ; i++) {  // Compress threads to be contiguous
            if (threads[i] != NULL)
               threads[k++] = threads[i] ;
            }
         ret_val = WaitForMultipleObjects ( k , threads , TRUE , INFINITE ) ;
         sprintf_s ( msg, "QUAT_THR.CPP: User abort; n_threads=%d  k=%d  Wait retval=%d", n_threads, k, ret_val ) ;
         MEMTEXT ( msg ) ;
         for (i=0 ; i<k ; i++) {
            CloseHandle ( threads[i] ) ;
            threads[i] = NULL ;
            }
         return ERROR_ESCAPE ;
         }


/*
   Start a new thread if we still have work to do
*/

      if (this_row < nrows) {  // If there are still some to do
         if (empty_slot < 0)   // Negative while we are initially filling the queue
            k = n_threads ;
         else
            k = empty_slot ;
         params[k].irow = this_row  ;
         params[k].rowptr = out_mat + this_row * ncols ;
         params[k].orbit = orbit + k * DwellLimit ;  // DwellLimit is a global parameter; see EXECUTE.CPP
         threads[k] = (HANDLE) _beginthreadex ( NULL , 0 , quat_threaded , &params[k] , 0 , NULL ) ;
         if (threads[k] == NULL) {
            audit ( "Internal ERROR: bad thread creation in QUAT_THR.CPP" ) ;
            for (i=0 ; i<n_threads ; i++) {
               if (threads[i] != NULL) {
                  CloseHandle ( threads[i] ) ;
                  threads[i] = NULL ;
                  }
               }
            return ERROR_INSUFFICIENT_MEMORY ;
            }
#if DEBUG_THREADS
         sprintf_s ( msg, "Starting this_row=%3d  Thread %8d (k=%2d n_threads=%2d)",
                   this_row, (int) threads[k], k, n_threads+1 ) ;
         MEMTEXT ( msg ) ;
#endif
         ++n_threads ;
         ++this_row ;
         } // if (this_row < nrows)

      if (n_threads == 0)  // Are we done?
         break ;

/*
   Handle full suite of threads running and more threads to add as soon as some are done.
   Wait for just one thread to finish.
*/

      if (n_threads == max_threads  &&  this_row < nrows) {
         ret_val = WaitForMultipleObjects ( n_threads , threads , FALSE , INFINITE ) ;
         if (ret_val == WAIT_TIMEOUT  ||  ret_val == WAIT_FAILED  ||  ret_val < 0  ||  ret_val >= n_threads) {
            for (i=0 ; i<n_threads ; i++) {
               if (threads[i] != NULL) {
                  CloseHandle ( threads[i] ) ;
                  threads[i] = NULL ;
                  }
               }
            sprintf_s ( msg, "INTERNAL ERROR!!!  Thread wait failed (%d) in QUAT_THR.CPP", ret_val ) ;
            audit ( msg ) ;
            return ERROR_INSUFFICIENT_MEMORY ;
            }

#if DEBUG_THREADS
         sprintf_s ( msg, "End Thread %8d (%2d)  this_row=%d",
         (int) threads[ret_val], ret_val, params[ret_val].irow ) ;
         MEMTEXT ( msg ) ;
#endif

         sprintf_s ( msg , "row %d of %d", this_row+1+1, nrows ) ;
         setpos_progress_message ( (double) (this_row+1) / (double) nrows ) ;

         empty_slot = ret_val ;
         CloseHandle ( threads[empty_slot] ) ;
         threads[empty_slot] = NULL ;
         --n_threads ;
         }

/*
   Handle all work has been started and now we are just waiting for threads to finish
*/

      else if (this_row == nrows) {
         ret_val = WaitForMultipleObjects ( n_threads , threads , TRUE , INFINITE ) ;
         if (ret_val == WAIT_TIMEOUT  ||  ret_val == WAIT_FAILED  ||  ret_val < 0  ||  ret_val >= n_threads) {
            for (i=0 ; i<n_threads ; i++) {
               if (threads[i] != NULL) {
                  CloseHandle ( threads[i] ) ;
                  threads[i] = NULL ;
                  }
               }
            sprintf_s ( msg, "INTERNAL ERROR!!!  Thread wait failed (%d) in QUAT_THR.CPP", ret_val ) ;
            audit ( msg ) ;
            return ERROR_INSUFFICIENT_MEMORY ;
            }

         for (i=0 ; i<n_threads ; i++) {
#if DEBUG_THREADS
            sprintf_s ( msg, "Final Thread %8d  this_row=%d",
            (int) threads[i], params[i].irow ) ;
            MEMTEXT ( msg ) ;
#endif
            CloseHandle ( threads[i] ) ;
            }
         break ;
         }
      } // Endless loop which threads computation of all rows

   return 0 ;
}