/* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * @file  MyroC-display-windows-interface-manager.c
 * @brief Parent Process for handling an image window on Windows 10
 *        This process provides the MyroC interface for handling image displays
 *
 *   OpenGL requires the window manager process to be a the primary thread
 *   for controlling images, so actual OpenGL processing is handled 
 *   by a child process,  MyroC-display-windows-opengl-img-handler.c
 *
 *   this version uses glutHideWindow when image windows time out 
 *   gluttHideWindow seems to work fine on Mac OS X and on some Linux machines
 *   unfortunately, when run on amd graphics cards, glutHideWindow causes
 *     window information to be lost, and subsequent calls to glutHowWindow fail
 *   thus, this version of MyroC-display.c works fine on Mac OS X and  
 *     but not on Linux machines with various graphics cards
 *
 * Revision History
 *
 * Version 1.0 based on a C++ package by April O'Neill, David Cowden,
 *     Dilan Ustek, Erik Opavsky, and Henry M. Walker
 *
 * Developers of the C package for Linux:
 *  Creators Version 2.0 (C functions for utilities,general,sensors,movement):
 *    Spencer Liberto
 *    Dilan Ustek
 *    Jordan Yuan
 *    Henry M. Walker
 *  Contributors Version 2.2-2.3: (C functions for image processing)
 *    Anita DeWitt
 *    Jason Liu
 *    Nick Knoebber
 *    Vasilisa Bashlovkina
 * Revision for Version 2.4:  (image row/column made to match matrix notation)
 *    Henry M. Walker
 *
 * Revisions for Version 3.0 
 *    Henry M. Walker
 *
 *  C ported to Macintosh
 *     Linux/Mac differences required for connections — otherwise same code
 *  OpenGL used to display images, replacing ImageMagick
 *     same [new] code used for both Linux and Macintosh
 *     1 process for robot control
 *     1 process needed for each titled window (not each image, as in 2.2-2.4)
 *  Blocking options (negative duration parameter)
 *     utilize separate thread timer
 *  MyroC implementation files organized by user function as follows:
 *
 * Revisions for Version 3.1 
 *    Henry M. Walker
 *
 *    Picture struct and image functions revised to allow 
 *       192 by  256 images from origial Fluke camera
 *       266 x 427 low-resolution images from Fluke 2
 *         (high-resolution (800 x 1280) too large for more than 2-4 
 *          variables on run-time stack)
 *       storage , retrieval, and display of any images up to 266 x 427 
 *
 * Revisions for Version 3.2
 *    Henry M. Walker
 *    
 *    Practical range of rBeep duration identified as <= 3.0014 seconds
 *    Image display and processing resolves several matters and adds functionality
 *        function rDisplayPicture completely rewritten
 *                over time OpenGL rountines had encountered troubles
 *                on Linux, specifically glutHideWindow() hid images, but could not be
 *                          restored, with difficulties depending on the graphics card
 *                on Mac, High Sierra generated compile warnings and 
 *                          restricted threads that could display images
 *    function rDisplayImageProcessingErrors added
 *    function rWaitTimedImageDisplay updated substantially
 *
 *  This program and all MyroC software is licensed under the Creative Commons
 *  Attribution-Noncommercial-Share Alike 3.0 United States License.
 *  Details available at http://creativecommons.org/licenses/by-nc-sa/3.0/us/
 *
 ********************** implementation overview *************************

> indicates functions implemented in this file

  0. LOW-LEVEL UTILITY (prototypes in MyroC-utilities.h
                       implemented in MyroC-utilities.c)
    rSend
    rReceive
    rSetOpeningExchange
    rSetBluetoothEcho
    rSetEchoMode
    rCheckHardwareVersionSetCameraSize

 1. GENERAL (mostly MyroC-general.c) 2. SENSOR  (MyroC-sensors.c)
    rConnect  (* MyroC-connect.c)     a.Scribbler Sensors
    rDisconnect  (* MyroC-connect.c)    rGetLightsAll
    rSetConnection                      rGetLightTxt
    rFinishProcessing                   rGetIRAll
    rSetVolume                          rGetIRTxt
    rBeep                               rGetLine
    Beep2                        
    rSetName                          b.Fluke Sensors
    rGetName                            rGetObstacleAll
    rSetForwardness                     rGetObstacleTxt
    rGetForwardness                     rGetBrightAll
    rSetLEDFront                        rGetBrightTxt
    rSetLEDBack                         rSetIRPower
    rGetBattery                          
    rGetStall           

      Note:
      *  MyroC-connect requires special
         knowledge of a Mac or Linux
         environment, yielding two versions:
         MyroC-connect-mac.c
         MyroC-connect-linux.c

 3. MOVEMENT (MyroC-movement.c)      4. PICTURES (files as indicated)
    rTurnLeft                           rGetCameraSize (MyroC-camera.c)
    rTurnRight                          rTakePicture (MyroC-camera.c)
    rTurnSpeed                        > rDisplayPicture (MyroC-display.c)
    rForward                          > rDisplayImageProcessingErrors (MyroC-display.c)
    rFastForward                      > rWaitTimedImageDisplay(MyroC-display.c)
    rBackward                           rSavePicture (MyroC-image-file.c)
    rMotors                             rLoadPicture (MyroC-image-file.c)   
    rStop
    rHardStop

 ************************************************************************
 */

 /* Overall processing constraints and plan
    Constraints
       Both OpenGL and ImageMagick on Linux and Mac OS X require
          a window to be controled as the primary thread of a process.
       Since a user may specify multiple windows (with different titles),
          the MyroC process must create a different process for 
          each image window.

       OpenGL allows an window to be updated with a new image
       Thus, 
        * the main MyroC process must be able to send a new message to 
          the processing controling a window.
        * a pipe is needed from the main MyroC proces to a window process
        * once a window process displays an image
          the process must await a 'D' message to display a new image

       The GLUT package within OpenGL contains procedure glutHideWindow
          This works fine on Mac OS X
          This hides a window without trouble on Linux, but
             the corresponding glutPostRedisplay does not make a new window visible
       Thus, 
        * MyroC will have to kill a process when a window is to be hidden,
          and a new window process created when a new image will be displayed
          at the same location with the same title.

       A window may disappear through either of two processes
        * If the window display is blocking, the window (and corresponding process)
          must go away when the duration interval is satisfied.
        * If the window display is non-blocking, 
           * a timer thread can keep track of when the window should disappear
           * another user request may cause a new picture to be displayed,
             based on a new timing interval.
 */

/* Approach to the display of camera images
 *
 * approach outline based on two processes:
 * this [user] [main] program runs in one program
 *    user/main program provides overall control of the robot, images, etc.
 * a child process provides OpenGL control for the display of images
 *
 * overall algorithm
 *   utilize shared Picture struct variable 
 *   main process 
 *      provides general user interface
 *      call to rDisplayPicture
 *         if second/child process not running, 
 *             create pipe to send Picture images from main to child process
 *             create child process
 *             initialize graphics environment
 *             establish shared Picture image as texture for image
 *             establish new_image procedure as glutIdleFunc
 *         if second process already running
 *             first thread sends image through pipe to child process
 *             redisplay process in child process
 *                reads image
 *                uses glTexSubImage2D to establish shared Picture image
 *                uses glutPostRedisplay to force display of new image
 *   image process (one process for each window)
 *      create/initialize new image:  procedure init_opengl_window
 *      refresh image (required by openGL):  procdure display
 *      update image:  procedure update_display
 *
 *  synchronization protocol:  all record keeping performed in main process
 *             for new or updated image:
 *               main process sends data to image process through socket
 *               image process sends confirmation when done through socket
 */

/* Primary reference for 2-way communication between parent and child process,
   used here to communicate between MyroC procedures (e.g., rDisplayPicture)
   and OpenGL image handler process:

     https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
*/

/* OpenGL frameworks
 */

/* headers for any environment */
#include "MyroC.h"
#include "MyroC-utilities.h"
#include <stdlib.h>
#include <stdio.h>      /* added to example for experimentation */
#include <string.h>     /* for strerror function */
#include <unistd.h>     /* for byte-level read/write */
#include <sys/types.h>

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
   /* OpenGL headers for Windows 10 environment */
   #include <windows.h>
   #include <winsock2.h>
   #include <ws2bth.h>
   #include <GL/glew.h>
   #include <GL/glut.h>

#else
   /* libraries for Linux and Mac */
   #include <sys/socket.h> /* mechanism for inter-thread communication */
   #include <sys/stat.h>   /* needed to check if window process still exists */
   #include <errno.h>      /* for checking runtime error codes */
   #include <signal.h>     /* for killing display window, if necessary */
   #include <sys/wait.h>   /* for determining if window process is running */
   #include <pthread.h>    /* for threads, used in timing non-blocking images */

   #ifdef __APPLE__
      /* OpenGL headers for Mac OS X environment */
      #include <GLUT/glut.h>

   #else
      /* OpenGL headers for Linux environment */
      #include <GL/glut.h>

   #endif
#endif


/* *********** Pipe structure for image handler communicaitons ************

              This                                  Remote
             MyroC                 Pipe               Image
            Handler                Name              Handler

            toImgHandWr ->->->-> toImgPipe ->->->-> toImgHandRd

           fromImgHandRd <-<-<- fromImgPipe <-<-<- fromImgHandWr
*/
/* ********************** Declarations for MyroC internals ****************
 */

/**  Struct for recording details of openGL process
 */
typedef struct {
  char * window_title;       // pointer to title string (unique for window)
  pid_t display_pid;         // ID of openGL process for window
  HANDLE toImgHandWr;        // input end of pipe toImgPipe to image handler
  HANDLE fromImgHandRd;      // output end of pipe fromImgPipe from image handler
  PROCESS_INFORMATION procInfo;   // process information for image handler process
  pthread_mutex_t lock;      // mutex to prevent concurrent socket communication
  GLuint  texture_objs [1];  // array for texture 1 object for openGL process
  int height;                // height of image object (192 for original fluke)
  int width;                 // width  of image object (256 for original fluke)
  int process_status;        // current status of a display window process
                             // values
                             //   0:  image process inactive, likely killed
                             //   1:  process active to display current image
  int image_num;             // sequence number of current/most recent image
} GLimageset;

/**
 * Struct for timing display of image
 */
typedef struct
{
  double delay_time;
  int GLimageset_index;
  int image_seq_number;
  int thread_seq_number;
} image_timing_data;

/**
 * Struct for acting timing-thread information
 */
typedef struct
{
  int thread_seq_number;
  pthread_t pthread;
} timing_thread_info;


/* ************* MyroC helper procedures *********************************
 */

/**
 * take action to display image for the proper length of time
 */
void process_image_duration (double duration, int window_index);

/**
 * timer to complete given openGL image after specified time
 */
void * display_timer_thread (void * delay_data);

/*
 * @brief Create a new window to display a picture
 * @param pic          RGB picture struct from Scribbler 2 camera
 * @param duration     if duration > 0, operation is blocking
 *                     if duration <= 0, operation is non-blocking
 *                     for duration != 0, picture displayed for abs(duration) 
 *                            seconds or until picture closed manually
 *                     if duration == 0, picture displayed until closed manually
 * @param window_index Index of existing window in image_array
 *                     white spaces will be replaced with underscores. 
 * @param attempt_num  count of how many attempts allowed before
 *                            giving up in creating new window
 *                            ( start >= 2, in case old window is being
 *                              killed when processing starts )
 * @pre                image_array[window_index] does not correspond 
 *                            to an active window process
 */
void rDisplayPictureNewWindow (Picture * pic, double duration,
                               const char * windowTitle, int window_index);

/*
 * @brief Update a picture in an existing window
 * @param pic          RGB picture struct from Scribbler 2 camera
 * @param duration     if duration > 0, operation is blocking
 *                     if duration <= 0, operation is non-blocking
 *                     for duration != 0, picture displayed for abs(duration) 
 *                            seconds or until picture closed manually
 *                     if duration == 0, picture displayed until closed manually
 * @param window_index Index of existing window in image_array
 *                     white spaces will be replaced with underscores. 
 * @pre                0 <= window_index < num_windows
 *                     image_array[window_index] corresponds to an active
 *                            window process
 */
void rDisplayPictureUpdate (Picture * pic, double duration, int window_index);

/**
 * add thread sequence number, id, etc. to thread_timer array;
 */
void recordTimerThread (int thread_seq_num, pthread_t thread);

/** 
 * remove  thread sequence number, id, etc. to thread_timer array;
 */
void deleteTimerThread (int thread_seq_num);

/* ************* MyroC procedures ****************************************
*/

/**
 * @brief Display a picture in a new window
 * @param pic         pointer to an RGB picture struct from Scribbler 2 camera
 * @param duration    if duration > 0, operation is blocking                 \n
 *                    if duration <= 0, operation is non-blocking            \n
 *                    for duration != 0, picture displayed for abs(duration) \n
 *                            seconds or until picture closed manually or 
 *                            until the program terminates                   \n
 *                    if duration == 0, picture displayed until closed manually
 * @param windowTitle The title of the window that appears.
 *                    white spaces will be replaced with underscores. 
 * @pre               windowTitle is less than 100 characters. 
 * @post              image is displayed for the duration specified,         \n
 *                    EXCEPT all display windows are closed 
 *                           when the main program terminates.
 * @warning           If images are displayed with a non-blocking option, and
 *                    if the user wants images to appear for a full duration,
 *                    use rWaitTimedImageDisplay or rFinishProcessing to block
 *                    processing until all image timers are finished.
 *                    Otherwise, program termination may close windows prematurely.
 */
void rDisplayPicture(Picture * pic, double duration, const char * windowTitle);

/** @brief Turn on and off the printing of error messasge for image processing
 *  @note  Some operating systems (e.g., Mac OX S High Sierra) display 
 *         an error message whenevern a new process for image display starts
 *  @post  This function toggles this printing on and off
 *         Initially, printing is off
 */
void rDisplayImageProcessingErrors ();

 /**
 * @brief Wait until all timed, non-blocking image window timers are complete
 * @post              wait until all timed [non-blocking] images have closed \n
 *                    robot motion is unaffected by this function
 *
 * @warning           images opened with duration 0.0 do not close until 
 *                       images are updated or until the program terminates  \n
 *                    all other image windows are closed by this function
 */
void rWaitTimedImageDisplay ();

/* ********************** global variables *****************************
 */

/* maximum number of openGL windows allowed 
 */
#define max_windows 9

/* number of openGL windows currently defined
 */
int num_windows = 0;

/* index of current window being processed*/
/*     global variable needed, so value can be accessed by all window procedures */
int window_index;

/* more recent image sequence number
 */
int image_seq_number = 0;

/* array of data for openGL processes
 */

GLimageset image_array[max_windows] = {{NULL}};

/*
 * elements for keeping track of active threads
 */
int max_size_thread_array = 0;     // maximum size of the current thread array
int current_size_thread_array = 0; // number of elements actually containing thread info
int current_thread_seq_num = 0;    // a sequence number for identifying each thread
int initial_thread_array_size = 10;// size of thread array when first initialized

timing_thread_info * thread_info = NULL; // can't use malloc to initialize, so
                 // actual initialization in first call to rDisplayPictureNewWindow

/* option for tracing program execution
 */
int enableTracing = 0; //  1 = print program execution to stdout; 0 - turn off tracing prints

/* *************************** Implementations ****************************
 */

/* **** Implementations:  MyroC helper functions **************************
 */
/**
 * take action to display image for the proper length of time
 */
void process_image_duration (double duration, int window_index)
{
   BOOL bSuccess;
  /* if duration > 0, 
   *      block further execution for given time 
   */

  // printf ("      main process:  duration processing:  %10.8lf\n", duration);

  if (duration > 0)
    {
      // printf ("duration > 0\n");

      int utime = (int)(duration * 1000000);
      usleep (utime);

      char ch = 'H';  // 'H' signifies Hide; use char variable as write requires address

      /* use lock, so transmission of image data not interrupted by separate timer
       */
       pthread_mutex_lock (&image_array[window_index].lock);

       long unsigned int dwWritten, dwRead;
       bSuccess = WriteFile(image_array[window_index].toImgHandWr, &ch, 1, &dwWritten, NULL);
       if (! bSuccess || dwWritten == 0) {
          fprintf (stderr, "    error in transmitting image-hiding command\n");
          exit(1);
       }

       // printf ("      main process:  continues: window: %\n", window_index);

       /* block until image window is largely set up */
      bSuccess = ReadFile (image_array[window_index].fromImgHandRd, (char *) &ch, 1, &dwRead, NULL);

      pthread_mutex_unlock (&image_array[window_index].lock);

       if (! bSuccess || dwRead == 0 || ch != 'h') { // 'h' for hidden (lower case)
         fprintf (stderr, "    error in hiding image:\n");
         exit(1);
       }

     if (enableTracing)
        printf ("hidng image window: %s\n", image_array[window_index].window_title);

      /* update status of window process --- no longer running */
      image_array[window_index].process_status  = 0;

   } // end of duration == 0
  
  /* if duration < 0, 
   * set timer in separate thread to complete window after designated time
   */
  else if (duration < 0)
    {  
      // printf ("duration < 0\n");
      // create thread, packaging needed data into struct for timer
      image_timing_data * delay_data = malloc (sizeof (image_timing_data));
      delay_data->GLimageset_index = window_index;
      delay_data->delay_time = -duration;
      delay_data->image_seq_number = image_array[window_index].image_num;
      current_thread_seq_num++;
      delay_data->thread_seq_number = current_thread_seq_num;
      
      pthread_t thread;
      int err = pthread_create (&thread, NULL, &display_timer_thread, 
				delay_data);
      if (err != 0)
	{
	  printf ("\ncannot create timer for non-blocking image;");
	  printf ("error code:  %s\n", strerror(err));
	}

      if (enableTracing) {
        printf("thread created:  %lld\n", thread);
      }
       /* record thread id for the window display timer thread */
      recordTimerThread (current_thread_seq_num, thread);
    }

  /* duration == 0:  no processing needed */
  else 
    {
      /* image to remain displayed indefinitely; no window display timer needed */
      // printf ("duration == 0\n");
    }
} //  process_image_duration

/**
 * timer to complete given openGL image after specified time
 */
void * display_timer_thread (void * delay_data)
{
  //printf ("      main process:  timer thread started\n");
  /* make local copy of the delay data, so that subsequent processing does not
   * impact this timer
   */
  image_timing_data local_delay_data = *((image_timing_data *) delay_data);
  int window_index = local_delay_data.GLimageset_index;
  int image_seq_number = local_delay_data.image_seq_number;
  int thread_seq_number = local_delay_data.thread_seq_number;
  BOOL bSuccess;

  /* with relevant data stored, delete copy in dynamic memory */
  free (delay_data);  

  /* time specified interval of delay */
  int utime = (int)(1000000 * local_delay_data.delay_time);
  usleep (utime);
  
  /* check if timer corresponds to current image being displayed*/
  if (image_array[window_index].image_num == image_seq_number) {
      /* timer corresponds to current image being displayed, so hide window */

      /* update status of window process --- no longer running */
      image_array[window_index].process_status  = 0;

      char ch = 'H';  // 'H' signifies Hide; use char variable as write requires address

      /* use lock, so transmission of image data not interrupted by separate timer
       */
       pthread_mutex_lock (&image_array[window_index].lock);

       long unsigned int dwWritten, dwRead;
       bSuccess = WriteFile(image_array[window_index].toImgHandWr, &ch, 1, &dwWritten, NULL);
       if (! bSuccess || dwWritten == 0) {
          fprintf (stderr, "    error in transmitting image-hiding command\n");
          exit(1);
       }

       // printf ("      main process:  continues: window: %\n", window_index);

       /* block until image window is largely set up */
      bSuccess = ReadFile (image_array[window_index].fromImgHandRd, (char *) &ch, 1, &dwRead, NULL);

      pthread_mutex_unlock (&image_array[window_index].lock);

       if (! bSuccess || dwRead == 0 || ch != 'h') { // 'h' for hidden (lower case)
         fprintf (stderr, "    error in hiding image:\n");
         exit(1);
       }
     if (enableTracing)
       printf ("hidng image window: %s\n", image_array[window_index].window_title);

      /* update status of window process --- no longer running */
      image_array[window_index].process_status  = 0;

      // printf ("      main process:  image for window_index %d hidden\n\n", window_index); 
    }

  // printf ("      main process:  timer completion done for sequence number: %d\n",
  //	  image_seq_number);

  deleteTimerThread (thread_seq_number);
  
  /* pthread procedure must return void * data */
  return (void *) 0;
}

/*
 * @brief Create a new window to display a picture
 * @param pic          RGB picture struct from Scribbler 2 camera or smaller image
 * @param duration     if duration > 0, operation is blocking
 *                     if duration <= 0, operation is non-blocking
 *                     for duration != 0, picture displayed for abs(duration) 
 *                            seconds or until picture closed manually
 *                     if duration == 0, picture displayed until closed manually
 * @param window_index Index of existing window in image_array
 *                     white spaces will be replaced with underscores. 
 * @pre                image_array[window_index] does not correspond 
 *                            to an active window process
 */
void rDisplayPictureNewWindow (Picture * pic, double duration,
                               const char * windowTitle,
                               int window_index)
{
  /* if all attempts have been exhausted to create window process,
     report failure and quit */
  // printf ("   New Window--num_window:  %d, window_index:  %d\n",
  //	  num_windows, window_index);

  /* create thread_array, when function first called */
  if (thread_info == NULL)
    {
      thread_info =
	(timing_thread_info*) malloc(initial_thread_array_size*sizeof (timing_thread_info));
      max_size_thread_array = initial_thread_array_size;
    }
  
  /* create pipes for communication with image processing handler process
           Pipe structure for image handler communicaitons

              This                                  Remote
             MyroC                 Pipe               Image
            Handler                Name              Handler

            toImgHandWr ->->->-> toImgPipe ->->->-> toImgHandRd

           fromImgHandRd <-<-<- fromImgPipe <-<-<- fromImgHandWr
   */

   ///////// code from tmain in server code at above URL
   image_array[window_index].toImgHandWr = NULL;
   image_array[window_index].fromImgHandRd = NULL;
   HANDLE toImgHandRd = NULL;
   HANDLE fromImgHandWr = NULL;

   // set security attributes, including the bInheritHandle, so pipe handles are inherited
   SECURITY_ATTRIBUTES saAttr; 
   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
   saAttr.bInheritHandle = TRUE; 
   saAttr.lpSecurityDescriptor = NULL; 

    if (enableTracing)
      printf ("creating image window: %s\n", image_array[window_index].window_title);

   // create a pipe for the image handler process's STDOUT
   if ( ! CreatePipe(&image_array[window_index].fromImgHandRd, &fromImgHandWr, &saAttr, 0) ) {
      fprintf (stderr, "  error in creating pipe  for image handler process\n");
      exit (1);
   }
   // read handle for STDOUT is not inherited
   if ( ! SetHandleInformation(image_array[window_index].fromImgHandRd, HANDLE_FLAG_INHERIT, 0) ) {
      fprintf (stderr, "  err in setting stdout for image handler prlcess\n");
      exit (1);
   }
   // create a pipe for the image handler process's STDIN
   if ( ! CreatePipe(&toImgHandRd, &image_array[window_index].toImgHandWr, &saAttr, 0) ) {
      fprintf (stderr, "  error in creating pipe to image handler process\n");
      exit (1);
   }
   // write handle for STDIN is not inherited
   if ( ! SetHandleInformation(image_array[window_index].toImgHandWr, HANDLE_FLAG_INHERIT, 0) ) {
     fprintf (stderr, "  error in setting stdin for image handler process\n");
     exit (1);
   }

  if (enableTracing)
      printf ("pipes created to image handling process\n");

   ///////// code from CreateChildProcess from above URL
   // find full path for image-handling program (.exe)
   //   allow consider space for directory name and image-handler.exe file name
   //   since a path name might contain a space, path name is placed in double quotes
   //   DIRECTORY_FOR_IMAGE_HANDLER is set to directory of image handler,
   //      as defined in MyroC-utility.h   (e.g., /usr/local/bin)
   int imgHandFullPathLen = 500;  
   char imgHandFullPath [imgHandFullPathLen];
   strcpy (imgHandFullPath, "");
   strcat (imgHandFullPath, DIRECTORY_FOR_IMAGE_HANDLER);
   strcat (imgHandFullPath, "/MyroC-display-windows-opengl-img-handler.exe  \"");
   strcat (imgHandFullPath, windowTitle);
   strcat (imgHandFullPath, "\"");
   //  if (enableTracing)
      printf ("full path name and window title for image handler's program:  %s\n", imgHandFullPath);

  // Create a child process that uses the previously created pipes for STDIN and STDOUT.
   STARTUPINFO siStartInfo;
   BOOL bSuccess = FALSE; 
 
   // Set up members of the PROCESS_INFORMATION structure. 
 
   ZeroMemory( &image_array[window_index].procInfo, sizeof(PROCESS_INFORMATION) );
 
   // Set up members of the STARTUPINFO structure. 
   // This structure specifies the STDIN and STDOUT handles for redirection.
 
   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO); 
   siStartInfo.hStdError = stderr;
   siStartInfo.hStdOutput = fromImgHandWr;
   siStartInfo.hStdInput = toImgHandRd;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
 
   // Create the child process. 
    
   bSuccess = CreateProcess(NULL, 
                 imgHandFullPath,     // command line 
                 NULL,          // process security attributes 
                 NULL,          // primary thread security attributes 
                 TRUE,          // handles are inherited 
                 0,             // creation flags 
                 NULL,          // use parent's environment 
                 NULL,          // use parent's current directory 
                 &siStartInfo,  // STARTUPINFO pointer 
                 &image_array[window_index].procInfo);  // receives PROCESS_INFORMATION 
   
   // If an error occurs, exit the application. 
   if ( ! bSuccess ) {
      fprintf (stderr, "   error in creating image handler process\n");fflush(stderr);
      fprintf (stderr, "   error number:  %ld\n", GetLastError()); fflush(stderr);
      exit (1);
   }
   // Close handles to the child process and its primary thread.

   // Some applications might keep these handles to monitor the status
   // of the child process, for example. 

   //CloseHandle(image_array[window_index].procInfo.hProcess);
   CloseHandle(image_array[window_index].procInfo.hThread);
     
   // Close handles to the stdin and stdout pipes no longer needed by the child process.
   // If they are not explicitly closed, there is no way to recognize that the child process has ended.
      
   CloseHandle(fromImgHandWr);
   CloseHandle(toImgHandRd);

   /* when preparation done, image will be visible */
   image_array[window_index].process_status  = 1;
   if (enableTracing) {
      printf ("image handler process created\n"); fflush(stdout);
   }

   ////// send image data through pipe to image handler   

   /* the first arguments received through the pipe are (in this order)
         the window index for this window
         the width and height of the image
         the pixel values for the initial image (RGB values in row-major order)
   */

   /* write window parameters */
   long unsigned int dwWritten, dwRead;
   bSuccess = WriteFile(image_array[window_index].toImgHandWr, (char *) &window_index, 4, &dwWritten, NULL);
   if (enableTracing) {
      printf ("printing window_index:  %lu bytes written\n", dwWritten); fflush(stdout);
   }
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in setting up image-handling process:  window index not communicated\n");
      exit(1);
   }
   bSuccess = WriteFile(image_array[window_index].toImgHandWr, (char *) &(pic->width), 4, &dwWritten, NULL);
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in setting up image-handling process:  image width not communicated\n");
      exit(1);
   }
   bSuccess = WriteFile(image_array[window_index].toImgHandWr, (char *) &(pic->height), 4, &dwWritten, NULL);
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in setting up image-handling process:  image width not communicated\n");
      exit(1);
   }

   /* read pixels for initial image */
   for (int row = 0; row < pic->height; row++) {
      for (int col = 0; col < pic->width; col++) {
         bSuccess = WriteFile (image_array[window_index].toImgHandWr, (char *) &(pic->pix_array[row][col]), 3, &dwWritten, NULL);
         if (! bSuccess || dwWritten == 0) {
            fprintf (stderr, "    error in setting up image-handling process:  problem with Pixel data\n");
            exit(1);
         }
      }
   }

   // printf ("      main process:  continues: window: %\n", window_index);

   /* block until image window is largely set up */
   char ch;
   bSuccess = ReadFile (image_array[window_index].fromImgHandRd, (char *) &ch, 1, &dwRead, NULL);

   if (! bSuccess || dwRead == 0 || ch != 'I') {
     fprintf (stderr, "    error handling first image:\n");
     exit(1);
   }

  /* mark status of display window as having a display and visible image
     (subject to functioning of glutMainLoop) */
  image_array[window_index].process_status = 1;

  /* record new sequence number for image */
  image_seq_number++;
  image_array[window_index].image_num = image_seq_number;
      
   /* handle duration parameter for this image */
   process_image_duration (duration, window_index);
             
   /* image processing completed; continue with main program */
   // printf ("      main process:  New Window thread completed\n");
} // rDisplayPictureNewWindow

/*
 * @brief Update a picture in an existing window
 * @param pic          RGB picture struct from Scribbler 2 camera
 * @param duration     if duration > 0, operation is blocking
 *                     if duration <= 0, operation is non-blocking
 *                     for duration != 0, picture displayed for abs(duration) 
 *                            seconds or until picture closed manually
 *                     if duration == 0, picture displayed until closed manually
 * @param window_index Index of existing window in image_array
 *                     white spaces will be replaced with underscores. 
 * @pre                0 <= window_index < num_windows
 *                     image_array[window_index] corresponds to an active
 *                            window process
 */
void rDisplayPictureUpdate (Picture * pic, double duration, int window_index)
{
  /* send new picture to existing openGL process */

  /* write display option, 
   *       height and width, 
   *       image sequence number
   *       pixels
   */

   if (enableTracing)
     printf ("updating image window: %s\n", image_array[window_index].window_title);

   char ch = 'D';  // 'D' signifies display; use char variable as write requires address 

   /* use lock, so transmission of image data not interrupted by
      separate timer
   */
   pthread_mutex_lock (&image_array[window_index].lock);

   long unsigned int dwWritten, dwRead;  // int type required for WriteFile, ReadFile
   BOOL bSuccess;
   bSuccess = WriteFile(image_array[window_index].toImgHandWr, 
                           &ch, 1, &dwWritten, NULL);
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in setting up image-handling process:  image width not communicated\n");
      exit(1);
   }
   bSuccess = WriteFile(image_array[window_index].toImgHandWr, 
                           (char *) &(pic->width), 4, &dwWritten, NULL);
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in setting up image-handling process:  image width not communicated\n");
      exit(1);
   }
   bSuccess = WriteFile(image_array[window_index].toImgHandWr, 
                           (char *) &(pic->height), 4, &dwWritten, NULL);
   if (! bSuccess || dwWritten == 0) {
      fprintf (stderr, "    error in setting up image-handling process:  image width not communicated\n");
      exit(1);
   }

   /* read pixels for initial image */
   for (int row = 0; row < pic->height; row++) 
      for (int col = 0; col < pic->width; col++) {
         bSuccess = WriteFile (image_array[window_index].toImgHandWr, 
                                (char *) &(pic->pix_array[row][col]), 3, &dwWritten, NULL);
         if (! bSuccess || dwWritten == 0) {
            fprintf (stderr, "    error in setting up image-handling update process:  problem with Pixel data\n");
            fprintf (stderr, "    row: %d , col: %d, error number:  %ld\n",
                             row, col, GetLastError());
            exit (1);
         }
      }

   pthread_mutex_unlock (&image_array[window_index].lock);

   // printf ("      main process:  continues: window: %\n", window_index);

   /* block until image window is largely set up */
   bSuccess = ReadFile (image_array[window_index].fromImgHandRd, &ch, 1, &dwRead, NULL);

   if (! bSuccess || dwRead == 0 || ch != 'U') { // 'U' for Updated
     fprintf (stderr, "    error handling first image:\n");
     exit(1);
   }

  /* mark status of display window as having a display and visible image
     (subject to functioning of glutMainLoop) */
  image_array[window_index].process_status = 1;

  /* record new sequence number for image */
  image_seq_number++;
  image_array[window_index].image_num = image_seq_number;
      
   /* handle duration parameter for this image */
   process_image_duration (duration, window_index);
          
   if (enableTracing)
      printf ("      main process:  rDisplayPictureUpdate completed\n");
} // rDisplayPictureUpdate

/**
 * add thread sequence number, id, etc. to thread_timer array;
 */
void recordTimerThread (int thread_seq_num, pthread_t thread)
{
  // printf ("      main process:  registering thread with sequence number:  %d\n",
  //	  thread_seq_num);
  int i;
  for (i = 0; i < current_size_thread_array; i++)
    {
      /* find unused array element, if present */
      if (thread_info[i].thread_seq_number == 0)
	{
	  /* insert thread record into array */
	  thread_info[i].thread_seq_number = thread_seq_num;
	  thread_info[i].pthread  = thread;
	  return;
	}
    }
      /* no space found in previously-used part of thread array */
  if (current_size_thread_array <  max_size_thread_array)
    {
      /* insert thread record into array */
      thread_info[current_size_thread_array].thread_seq_number = thread_seq_num;
      thread_info[current_size_thread_array].pthread  = thread;
      current_size_thread_array++;
      return;
    }
      
  /* must extend thread array to accommodate more thread elements */
  timing_thread_info * temp_thread_info
    = malloc (2 * max_size_thread_array * sizeof (image_timing_data));
  for (i = 0; i < current_size_thread_array; i++)
    {
      temp_thread_info[i].thread_seq_number = thread_info[i].thread_seq_number;
      temp_thread_info[i].pthread = thread_info[i].pthread;
    }
  free (thread_info);
  max_size_thread_array *= 2;

  /* insert new thread info into array */
  thread_info = temp_thread_info;
  thread_info[current_size_thread_array].thread_seq_number = thread_seq_num;
  thread_info[current_size_thread_array].pthread  = thread;
  current_size_thread_array++;
}

/** 
 * remove  thread sequence number, id, etc. to thread_timer array;
 */
void deleteTimerThread (int thread_seq_num)
{
  // printf ("      main process:  removing thread with sequence number:  %d\n",
  //	  thread_seq_num);
  int i;
  for (i = 0; i < current_size_thread_array; i++)
    {
      /* if sequence number found */
      if (thread_info[i].thread_seq_number == thread_seq_num)
	{
	  /* delete record of thread */
	  thread_info[i].thread_seq_number = 0;

          /* time for this timer superseded by later window */ 
          /* set thread, so that it will exit cleanly when completed */
          /* if this call omitted, this process will become a "zombie thread",
             until main explicitly calls pthread_join */
          pthread_detach (thread_info[i].pthread);
   
	  return;
	}
    }
}

/* **** Implementations:  MyroC functions *********************************
 */
/**
 * @brief Display a picture
 * @param pic         pointer to an RGB picture struct from Scribbler 2 camera
 * @param duration    if duration > 0, operation is blocking                 \n
 *                    if duration <= 0, operation is non-blocking            \n
 *                    for duration != 0, picture displayed for abs(duration) \n
 *                            seconds or until picture closed manually or 
 *                            until the program terminates                   \n
 *                    if duration == 0, picture displayed until closed manually
 * @param windowTitle The title of the window that appears.
 *                    white spaces will be replaced with underscores. 
 * @pre               windowTitle is less than 100 characters. 
 * @post              image is displayed for the duration specified,         \n
 *                    EXCEPT all display windows are closed 
 *                           when the main program terminates.
 * @warning           If images are displayed with a non-blocking option, and
 *                    if the user wants images to appear for a full duration,
 *                    use rWaitTimedImageDisplay or rFinishProcessing to block
 *                    processing until all image timers are finished.
 *                    Otherwise, program termination may close windows prematurely.
 */
void rDisplayPicture(Picture * pic, double duration, const char * windowTitle)
{
  /* determine if window with given title exists */
  for (window_index = 0; window_index < num_windows; window_index++)
    { 
      if (strcmp (windowTitle, image_array[window_index].window_title) == 0)
	break;
    }

  /* 3 cases/subcases must be considered 
     1.  window title was not encountered previously
         a.  a full quota of named windows have already been created
         b.  a new window can be created
     2.  window title was encountered previously
         a.  a process currently is handling a window with that title
  */

  // printf ("rDisplayPicture:  window_index: %d, num_windows: %d\n",
  //	  window_index, num_windows);
  if (window_index == num_windows)
    {   /* case 1:  check if window name previously used */
      if (window_index >= max_windows)
	{ /* case 1a:  a full quota of named windows already created */

	  // printf ("--->case 1a:  ");
	  printf ("the number of requested image windows exceeds ");
	  printf ("the maximum allowed (%d)\n", max_windows);
	  printf ("   image not displayed --- other processing continues\n");
	  return;
	}
      else
	{ /*case 1b: a new named window can be created */

	   /* image title not previously recorded,
              so record image data in image_array
	   */
	  // printf ("--->case 1b\n");
	  num_windows++;

	  /* put window title in newly-allocated character array */
	  int title_len = strlen(windowTitle);
	  image_array[window_index].window_title 
	    = (char *)malloc ((title_len + 1) * sizeof (char));
	  strncpy (image_array[window_index].window_title,
		   windowTitle, title_len+1);
      
	  /* record image size */
	  image_array[window_index].height = pic->height;
	  image_array[window_index].width = pic-> width;
	  image_array[window_index].image_num = 1;

	  /* set up mutex lock, so communication for one image
	     does not interfere with communication for a second image
	  */
	  // printf ("set up mutex lock for window %d\n", window_index);
	  if (pthread_mutex_init (&(image_array[window_index].lock), NULL) != 0)
	    {
	      printf ("\nmutex initialization failed\n");
	      exit (1);
	    }

	  /* create new openGL process to display picture */	  
	  rDisplayPictureNewWindow(pic, duration, windowTitle, window_index);
	  return;
	}
    }
  else
    {  /* 2.  window title was encountered previously */
	      rDisplayPictureUpdate (pic, duration, window_index);
	      return;
    }
}

/** @brief Turn on and off the printing of error messasge for image processing
 *  @note  Some operating systems (e.g., Mac OX S High Sierra) display 
 *         an error message whenevern a new process for image display starts
 *  @note  Windows 10 does not generate these error messages, so this
 *         function has no useful purpose for Windows systems
 *  @post  This function turns this printing on
 *         Initially, printing is off
 */
void rDisplayImageProcessingErrors ()
{
  printf ("Procedure rDisplayProcessingErrors has no purpose and thus is not implement for Windows 10\n");
}
 
 /**
 * @brief Wait until all timed, non-blocking image window timers are complete
 * @post              wait until all timed [non-blocking] images have closed \n
 *                    robot motion is unaffected by this function
 *
 * @warning           images opened with duration 0.0 do not close until 
 *                       images are updated or until the program terminates  \n
 *                    all other image windows are closed by this function
 */
void rWaitTimedImageDisplay ()
{
   int i;
   for (i = 0; i < current_size_thread_array; i ++)
      {
	if (thread_info [i].thread_seq_number != 0)
            {  // wait for display image timer to complete
               if (enableTracing) {
               printf("waiting to join thread:  %lld\n", thread_info [i].pthread);
               }

               int err = pthread_join (thread_info [i].pthread, NULL);
               printf ("  thread number:  %lld\n", thread_info [i].pthread);
               printf ("  pthread join error code:  %d\n", err);
               printf ("  error codes:  EDEADLK: %d, EINVAL:sss %d. ESRCH: %d\n",
                                EDEADLK, EINVAL, ESRCH);

               // timer is no longer active
               thread_info [i].thread_seq_number = 0;
            }
      }
}


