/******************************************************************************/
/*                                                                            */
/*  THREADING - Oversee multithreading of computations                        */
/*                                                                            */
/******************************************************************************/

#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 "const.h"
#include "classes.h"
#include "funcdefs.h"
#include "vortex.rh"
#include "extern.h"

#define DEBUG_THREADS 0

#if MAX_THREADS == 0
int compute_threaded ( unsigned char * )
{
   return 0 ;
}

#else


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

   do_row - Local routine computes a single row in a thread

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

void do_row (
   int irow ,            // Row number for this thread
   unsigned char *rowptr // Computed row placed here
   )
{
   int icol ;
   double window_row, window_col, value ;

   window_row = vortex_params.max_row - (vortex_params.max_row - vortex_params.min_row) * irow / (vortex_params.nrows - 1.0) ;
   for (icol=0 ; icol< vortex_params.ncols ; icol++) {
      window_col = vortex_params.max_col - (vortex_params.max_col - vortex_params.min_col) * icol / (vortex_params.ncols - 1.0) ;
      if (vortex_params.pattern == 1)
          value = compute_checker( window_row, window_col ) ;
      else if (vortex_params.pattern == 2)
          value = compute_spiral( window_row, window_col ) ;
      else
          value = 0.0;  // Never happens; here as a placeholder for more patterns
      assert ( value >= 0.0  &&  value <= 1.0 ) ;
      rowptr[icol] = (unsigned char) (255.99 * value) ;
      }
}


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

   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 char *rowptr ; // Computed row placed here
} PARAMS ;

static unsigned int __stdcall vortex_threaded ( LPVOID dp )
{
   do_row ( ((PARAMS *) dp)->irow ,
            ((PARAMS *) dp)->rowptr ) ;
   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 compute_threaded ( unsigned char *image )
{
   int i, k, this_row, ret_val, max_threads, n_threads, empty_slot ;
   char msg[4096] ;
   PARAMS params[MAX_THREADS] ;
   HANDLE threads[MAX_THREADS] ;

   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, "THREADING.CPP: User abort; n_threads=%d  k=%d  Wait retval=%d", n_threads, k, ret_val ) ;
         audit ( 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 < vortex_params.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 = image + this_row * vortex_params.ncols ;
         threads[k] = (HANDLE) _beginthreadex ( NULL , 0 , vortex_threaded , &params[k] , 0 , NULL ) ;
         if (threads[k] == NULL) {
            audit ( "Internal ERROR: bad thread creation in VORTEX" ) ;
            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 ) ;
         audit ( 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 < vortex_params.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 THREADING", 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 ) ;
         audit ( msg ) ;
#endif

         sprintf_s ( msg , "row %d of %d", this_row+1+1, vortex_params.nrows ) ;
         setpos_wait_message ( (double) (this_row+1) / (double) vortex_params.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 == vortex_params.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 THREADING.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 ) ;
            audit ( msg ) ;
#endif
            CloseHandle ( threads[i] ) ;
            }
         break ;
         }
      } // Endless loop which threads computation of all rows

   return 0 ;
}
#endif