/******************************************************************************/
/*                                                                            */
/*  MAND_THR - Threaded Mandelbrot/Julia, 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 image, so I make them static to avoid
// having to pass them as parameters for every thread

static double min_r ;
static double max_r ;
static double min_i ;
static double max_i ;
static unsigned int dwell_limit ;
static int binary_decomp ;
static int object_type ;
static int dwell_vs_binary ;
static double binary_threshold ; // MSETDIST binary threshold (used when ! DwellVsBinary)
static COMPLEX c_const ;          // Additive constant 'c' for Julia sets
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

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

void do_row (
   int irow ,            // Row number
   unsigned int *out ,   // Ncols of computed dwells
   double *work_real ,   // Ncols work area
   double *work_imag     // Ditto
   )
{
   int icol ;
   unsigned int* dwell_ptr;
   double real_inc, imag_inc, delta ;
   COMPLEX z_start, end ;

   real_inc = (MaxR - MinR) / (ncols - 1) ;
   imag_inc = (MinI - MaxI) / (nrows - 1) ;
   dwell_ptr = out ;
   z_start.imag = MaxI + imag_inc * (double) irow ;

   for (icol=0 ; icol<ncols ; icol++) {
      z_start.real = MinR + real_inc * (double) icol ;
      if (dwell_vs_binary) {
         if (object_type == 2)  // Julia
            *dwell_ptr++ = countit ( &c_const , &z_start , &end , work_real, work_imag ) ;
         else                   // Mandelbrot
            *dwell_ptr++ = countit ( &z_start , &z_start , &end, work_real, work_imag ) ;
         if (binary_decomp  &&  (*(dwell_ptr-1) < dwell_limit)) {
            // Low-order bit indicates sign of imaginary part
            *(dwell_ptr-1) &= 0xFFFFFFFE ;  // This assumes dwell is four bytes
            if (end.imag  >  0.0)
               ++*(dwell_ptr-1) ;
            }
         }
      else {
         delta = binary_threshold * 0.5 * (real_inc - imag_inc) ;
         if (object_type == 2) // Julia
            *dwell_ptr++ = msetdist ( &c_const , &z_start , DwellLimit , 1 , work_real , work_imag ) < delta ;
         else
            *dwell_ptr++ = msetdist ( &z_start , &z_start , DwellLimit , 0 , work_real , work_imag ) < delta ;
         }
      } // For all cols
}


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

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

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

typedef struct {
   int irow ;              // Row number for this thread
   unsigned int *rowptr ;  // Computed row placed here
   double *work_real ;     // Ncols work area
   double *work_imag ;     // Ditto
} PARAMS ;

static unsigned int __stdcall mand_threaded ( LPVOID dp )
{
   do_row ( ((PARAMS *) dp)->irow ,        // Row number
            ((PARAMS *) dp)->rowptr ,      // Output computed here
            ((PARAMS *) dp)->work_real ,   // Work area
            ((PARAMS *) dp)->work_imag ) ; // Ditto
   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 mand_thr (
   double min_r_p ,
   double max_r_p ,
   double min_i_p ,
   double max_i_p ,
   unsigned int dwell_limit_p ,
   int binary_decomp_p ,
   int object_type_p ,
   int dwell_vs_binary_p ,
   double binary_threshold_p ,
   COMPLEX c_const_p ,
   int nrows_p ,
   int ncols_p ,
   unsigned int *dwell_mat ,
   double *loop_real ,
   double *loop_imag
   )
{
   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 the constant parameters to statics for common use by all threads

   min_r = min_r_p ;
   max_r = max_r_p ;
   min_i = min_i_p ;
   max_i = max_i_p ;
   dwell_limit = dwell_limit_p ;
   binary_decomp = binary_decomp_p ;
   object_type = object_type_p ;
   dwell_vs_binary = dwell_vs_binary_p ;
   binary_threshold = binary_threshold_p ;
   c_const = c_const_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, "MAND_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 = dwell_mat + this_row * ncols ;
         params[k].work_real = loop_real + k * dwell_limit ;
         params[k].work_imag = loop_imag + k * dwell_limit ;
         threads[k] = (HANDLE) _beginthreadex ( NULL , 0 , mand_threaded , &params[k] , 0 , NULL ) ;
         if (threads[k] == NULL) {
            audit ( "Internal ERROR: bad thread creation in MAND_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 MAND_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 MAND_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 ;
}