//
// Test the rsvg library in a threaded environment.
//
// Author: Wolfgang Glas <wolfgang.glas@ev-i.at>
//
// This code may be used without any restrictions.
//
// compile with:
//
// gcc -g -Wall `pkg-config --cflags librsvg-2.0` rsvg-thread-test.c `pkg-config --libs librsvg-2.0` `pkg-config --libs gthread-2.0` -o rsvg-thread-test
//

#include <alloca.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <librsvg/rsvg.h>

typedef struct thread_info_st thread_info_t;
typedef struct startup_info_st startup_info_t;

// This structire holds all informations about a specific thread.
struct thread_info_st {
  int id;
  pthread_t handle;
  const char *infile;
  char *outfile;
  startup_info_t *startup_info;
};

// This structure holds all global informations, which have to be known by all thread.
struct startup_info_st {
  pthread_mutex_t mutex;
  pthread_cond_t cond;
  int nthreads;
  int nregistered;
  int have_written;
};

static void init_thread_info(thread_info_t *info, int id,
                             char* argv[], pthread_t main_thread,
                             startup_info_t *startup_info)
{
  info->id = id;
  info->handle=main_thread;
  info->startup_info=startup_info;
  info->infile = argv[1];

  size_t len = strlen(argv[2]) + 12;

  info->outfile = malloc(len);

  char *dot = strrchr(argv[2],'.');

  if (dot) {
    snprintf(info->outfile,len,"%.*s-%d%s",(int)(dot-argv[2]),argv[2],id,dot);
  } else {
    snprintf(info->outfile,len,"%s-%d.png",argv[2],id);
  }
}

static void cleanup_thread_info(thread_info_t *info, pthread_t main_thread)
{
  if (info->handle != main_thread) {
    void *ret_value;
    pthread_join(info->handle,&ret_value);
    if (ret_value != NULL)
      fprintf(stderr,"Thread %d failed.\n",info->id);
  }

  if (info->outfile) free(info->outfile);
}

static void *do_render(thread_info_t *info)
{
  void *ret=(void*)-1;

  GError *error = 0;
  RsvgHandle* handle = rsvg_handle_new_from_file(info->infile,&error);
  GdkPixbuf *pixbuf = NULL;

  if (!handle)
    {
      fprintf(stderr,"Thread %d: Error creating svg handle.\n",info->id);
      goto cleanup;
    }
   
  pixbuf = rsvg_handle_get_pixbuf(handle);

  if (!pixbuf)
    {
      fprintf(stderr,"Thread %d: Error fetching pixel buffer from svg handle.\n",info->id);
      goto cleanup;
    }
  
  int r = gdk_pixbuf_save(pixbuf,info->outfile,"png",&error,NULL);

  if (!r)
    {
      fprintf(stderr,"Thread %d: Error saving pixel buffer: %s.\n",info->id,error->message);
      goto cleanup;
    }
  
  ret = NULL;
  
 cleanup:

  if (error)
    g_error_free (error);
  if (pixbuf)
    g_object_unref(pixbuf);
  if (handle)
    rsvg_handle_free(handle);

  return ret;
}

// Ths is the main function of the renderer thread.
static void *render_thread_run(void *ctxt)
{
  thread_info_t *info = (thread_info_t *)ctxt;

  printf("Starting thread %d.\n",info->id);

  // All this code is here in order to wait for all threads initialized.
  // If all threads have been initialized, we broadcast, so all threads start to
  // work instantaneously.
  void *ret=(void*)-1;

  pthread_mutex_lock(&info->startup_info->mutex);

  if (++info->startup_info->nregistered != info->startup_info->nthreads) {

    // Wait until all threads are ready to go...
    pthread_cond_wait(&info->startup_info->cond,
                      &info->startup_info->mutex);

    // If this cindition is not true, some pthread_create has failed.
    if (info->startup_info->nregistered == info->startup_info->nthreads)
      ret = NULL;

  } else {
    // I'm the last one being initialized, so trigger the rest of us...
    pthread_cond_broadcast(&info->startup_info->cond);
    ret = NULL;
  }

  pthread_mutex_unlock(&info->startup_info->mutex);

  // pthread_create has failed,  go out prematurely.
  if (ret) return ret;

  ret = do_render(info);

  if (ret == NULL)
    printf("Finished thread %d.\n",info->id);

  return ret;
}

int main( int argc, char **argv )
{
  int ret = 1;
  int i;
  thread_info_t *thread_infos = NULL;
  pthread_t main_thread = pthread_self();
  int do_prerender=0;

  startup_info_t startup_info = {
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER,
    0,0
  };

  g_thread_init(NULL);
  rsvg_init();

  if (argc > 4 && strcmp(argv[1],"-prerender") == 0)
    {
      ++argv;
      --argc;
      do_prerender=1;
    }

  if (argc!=4) {
    fprintf(stderr,"usage: %s [-prerender] <infile> <outfile> <nthreads>.\n",argv[0]);
    goto leave;
  }


  if (1!=sscanf(argv[3],"%d",&startup_info.nthreads) ||
      startup_info.nthreads < 1) {
    fprintf(stderr,"Argument <nthreads> (value:%s) is not a positive integer.\n",
            argv[3]);
    goto leave;
  }

  thread_infos = alloca(sizeof(thread_info_t)*startup_info.nthreads);

  for (i=0;i<startup_info.nthreads;++i)
    init_thread_info(&thread_infos[i],i,argv,main_thread,&startup_info);

  // render one image before entering parallelism, which
  // causes all inherently non-threadsafe initializations in the
  // toolchain to be undertaken.
  if (do_prerender)
    {
      printf("Pre-rendering one image.\n");
      if (do_render(&thread_infos[0]))
        {
          fprintf(stderr,"Pre-rendering failed.\n");
          goto cleanup;
        }
      printf("Pre-rendering finished.\n");
    }

  for (i=0;i<startup_info.nthreads;++i)
    if (pthread_create(&thread_infos[i].handle,NULL,
                       render_thread_run,&thread_infos[i])) {
      perror("pthread_create failed");
      // wake all threads currently waiting for all threads being initialized.
      pthread_cond_broadcast(&startup_info.cond);
      goto cleanup;
    }

cleanup:

  if (thread_infos)
    for (i=0;i<startup_info.nthreads;++i)
      cleanup_thread_info(&thread_infos[i],main_thread);

leave:
  rsvg_term();
  return ret;
}

