/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pixel_processor.c: Copyright (C) 1999 Jay Cox <jaycox@earthlink.net>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include "pixel_processor.h"
#include "pixel_region.h"
#include "pixel_regionP.h"
#include "gimprc.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#ifdef ENABLE_MP

#include <pthread.h>

#define IF_THREAD(statement) statement

#else /* !ENABLE_MP */

#define IF_THREAD(statement)

#endif /* ENABLE_MP */



typedef void (*p1_func)(void *, PixelRegion *);
typedef void (*p2_func)(void *, PixelRegion * ,PixelRegion *);
typedef void (*p3_func)(void *, PixelRegion * ,PixelRegion *, PixelRegion *);
typedef void (*p4_func)(void *, PixelRegion * ,PixelRegion *, PixelRegion *,
			PixelRegion *);

struct _PixelProcessor
{
  void *data;
  p_func f;
  PixelRegionIterator *PRI;
  IF_THREAD(pthread_mutex_t mutex;)
  int nthreads;
  int n_regions;
  PixelRegion *r[4];

  void *progress_report_data;
  ProgressReportFunc progress_report_func;
};

static void *
do_parallel_regions(PixelProcessor  *p_s)
{
  PixelRegion tr[4];
  int ntiles = 0;
  int i;
  int cont = 1;

  IF_THREAD(pthread_mutex_lock(&p_s->mutex);)
  if (p_s->nthreads != 0 && p_s->PRI)
    p_s->PRI =  (PixelRegionIterator*)pixel_regions_process(p_s->PRI);
  if (p_s->PRI == NULL)
  {
    IF_THREAD(pthread_mutex_unlock(&p_s->mutex);)
    return NULL;
  }
  p_s->nthreads++;

  do
  {
    for (i = 0; i < p_s->n_regions; i++)
      if (p_s->r[i])
      {
	memcpy(&tr[i], p_s->r[i], sizeof(PixelRegion));
	IF_THREAD(
	  if (tr[i].tiles)
	    tile_lock(tr[i].curtile);
	  )
      }
    IF_THREAD(pthread_mutex_unlock(&p_s->mutex);)
    ntiles++;
    switch(p_s->n_regions)
    {
     case 1:
       ((p1_func)p_s->f)(p_s->data,
			 p_s->r[0] ? &tr[0] : NULL);
       break;
     case 2:
       ((p2_func)p_s->f)(p_s->data,
			 p_s->r[0] ? &tr[0] : NULL,
			 p_s->r[1] ? &tr[1] : NULL);
       break;
     case 3:
       ((p3_func)p_s->f)(p_s->data,
			 p_s->r[0] ? &tr[0] : NULL,
			 p_s->r[1] ? &tr[1] : NULL,
			 p_s->r[2] ? &tr[2] : NULL);
       break;
     case 4:
       ((p4_func)p_s->f)(p_s->data,
			 p_s->r[0] ? &tr[0] : NULL,
			 p_s->r[1] ? &tr[1] : NULL,
			 p_s->r[2] ? &tr[2] : NULL,
			 p_s->r[3] ? &tr[3] : NULL);
       break;
     default:
       g_message("do_parallel_regions: Bad number of regions %d\n",
		 p_s->n_regions);
    }
    IF_THREAD(pthread_mutex_lock(&p_s->mutex);)
    IF_THREAD(
      {
	for (i = 0; i < p_s->n_regions; i++)
	  if (p_s->r[i])
	  {
	    if (tr[i].tiles)
	      tile_release(tr[i].curtile, tr[i].dirty);
	  }
      })
    if (p_s->progress_report_func)
      if (!p_s->progress_report_func(p_s->progress_report_data,
				     p_s->r[0]->x, p_s->r[0]->y, 
				     p_s->r[0]->w, p_s->r[0]->h))
	cont = 0;
  } while (cont && p_s->PRI &&
	   (p_s->PRI = (PixelRegionIterator*)pixel_regions_process(p_s->PRI)));
  p_s->nthreads--;
  /*  fprintf(stderr, "processed %d tiles\n", ntiles); */
  IF_THREAD(pthread_mutex_unlock(&p_s->mutex);)
  return NULL;
}

#define MAX_THREADS 30

static void
pixel_regions_do_parallel(PixelProcessor *p_s)
{
  IF_THREAD(int i;)
  IF_THREAD(int nthreads;)
  IF_THREAD(pthread_t threads[MAX_THREADS];)
  IF_THREAD(pthread_attr_t pthread_attr;)

  /*		 (p_s->PRI->region_width * p_s->PRI->region_height) /(64*64)); */
  IF_THREAD(
    nthreads = MIN(num_processors, MAX_THREADS);
    nthreads = MIN(nthreads, 1 + 
		   (p_s->PRI->region_width * p_s->PRI->region_height)
		   /(TILE_WIDTH*TILE_HEIGHT));
    if (nthreads > 1)
    {
      pthread_attr_init (&pthread_attr);
      for (i = 0; i < nthreads; i++)
      {
	pthread_create (&threads[i], &pthread_attr,
			(void *(*)(void *)) do_parallel_regions,
			p_s);
      }
      for (i = 0; i < nthreads; i++)
      {
	pthread_join(threads[i], NULL);
      }
    }
    else
    )
    do_parallel_regions(p_s);
  if (p_s->nthreads != 0)
    fprintf(stderr, "Ack, we lost a thread\n");
}

static PixelProcessor *
pixel_regions_real_process_parallel(p_func f, void *data,
				    ProgressReportFunc report_func,
				    void *report_data,
				    int num_regions, va_list ap)
{
  int i;
  PixelProcessor *p_s;


  p_s = g_new(PixelProcessor, 200);


  for (i = 0; i < num_regions; i++)
    p_s->r[i] = va_arg (ap, PixelRegion *);

  switch(num_regions)
  {
   case 1:
    p_s->PRI = (PixelRegionIterator *) pixel_regions_register(num_regions,
							      p_s->r[0]);
    break;
   case 2:
     p_s->PRI = (PixelRegionIterator *) pixel_regions_register(num_regions,
							       p_s->r[0],
							       p_s->r[1]);
     break;
   case 3:
     p_s->PRI = (PixelRegionIterator *) pixel_regions_register(num_regions,
							       p_s->r[0],
							       p_s->r[1],
							       p_s->r[2]);
     break;
   case 4:
     p_s->PRI = (PixelRegionIterator *) pixel_regions_register(num_regions,
							       p_s->r[0],
							       p_s->r[1],
							       p_s->r[2],
							       p_s->r[3]);
    break;
   default:
     g_message("pixel_regions_real_process_parallel: Bad number of regions %d\n",
	       p_s->n_regions);
  }
  if (!p_s->PRI)
  {
    pixel_processor_free(p_s);
    return NULL;
  }
  IF_THREAD(p_s->PRI->dirty_tiles = 0;)
  p_s->f = f;
  p_s->data = data;
  p_s->n_regions = num_regions;
 IF_THREAD(pthread_mutex_init(&(p_s->mutex), NULL);)
  p_s->nthreads = 0;

  p_s->progress_report_data = report_data;
  p_s->progress_report_func = report_func;


  pixel_regions_do_parallel(p_s);

  if (p_s->PRI)
    return p_s;
  pixel_processor_free (p_s);
  return NULL;
}

void
pixel_regions_process_parallel(p_func f, void *data, int num_regions, ...)
{
  va_list va;
  va_start (va, num_regions);

  pixel_regions_real_process_parallel(f, data, NULL, NULL, num_regions, va);

  va_end (va);
}

PixelProcessor *
pixel_regions_process_parallel_progress(p_func f, void *data,
					ProgressReportFunc progress_func,
					void *progress_data, int num_regions,
					...)
{
  PixelProcessor *ret;
  va_list va;
  va_start (va, num_regions);

  ret = pixel_regions_real_process_parallel(f, data,
					    progress_func, progress_data,
					    num_regions, va);

  va_end (va);
  return ret;
}

void
pixel_processor_stop(PixelProcessor *pp)
{
  if (!pp)
    return;
  if (pp->PRI)
  {
    pixel_regions_process_stop (pp->PRI);
    pp->PRI = NULL;
  }
  pixel_processor_free(pp);
}

PixelProcessor *
pixel_processor_cont(PixelProcessor *pp)
{
  pixel_regions_do_parallel(pp);
  if (pp->PRI)
    return pp;
  pixel_processor_free (pp);
  return NULL;
}

void 
pixel_processor_free (PixelProcessor *pp)
{
  if (pp->PRI)
    pixel_processor_stop(pp);
  else
    g_free(pp);
}

