/* $Id: visir_queue.c,v 1.4 2012-10-24 14:57:42 jtaylor Exp $
 *
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2012 European Southern Observatory
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */

/*
 * $Author: jtaylor $
 * $Date: 2012-10-24 14:57:42 $
 * $Revision: 1.4 $
 * $Name: not supported by cvs2svn $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if defined _OPENMP && _OPENMP >= 200805

/*-----------------------------------------------------------------------------
                                   Includes
 -----------------------------------------------------------------------------*/

#include <cxlist.h>
#include <cpl.h>
#include <time.h>
#include <omp.h>

/*-----------------------------------------------------------------------------
                                   Define
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_queue openmp queue
 *
 */
/*----------------------------------------------------------------------------*/


typedef struct {
    int max_size;
    int size;
    cpl_error_code error;
    cx_list * queue;
    omp_lock_t lock;
} visir_queue;


/*----------------------------------------------------------------------------*/
/**
  @brief    Initialize queue
  @param    max_size   maximum size of queue, if negative unlimited
  @return   queue
  */
/*----------------------------------------------------------------------------*/
visir_queue * visir_queue_init(const int max_size)
{
    visir_queue * q = cpl_malloc(sizeof(visir_queue));
    q->max_size = max_size <= 0 ? INT_MAX : max_size;
    q->size = 0;
    q->error = CPL_ERROR_NONE;
    q->queue = cx_list_new();
    omp_init_lock(&(q->lock));
    return q;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Delete queue
  @param    q     queue to delete
  */
/*----------------------------------------------------------------------------*/
void visir_queue_delete(visir_queue * q)
{
    cx_list_delete(q->queue);
    omp_destroy_lock(&(q->lock));
    cpl_free(q);
}


/*----------------------------------------------------------------------------*/
/**
  @brief    set errorstate of queue, will abort all blocking operations
  @param    q      queue
  @param    error  error_code
  */
/*----------------------------------------------------------------------------*/
void visir_queue_set_error(visir_queue * q, cpl_error_code error)
{
    omp_set_lock(&(q->lock));
    q->error = error;
    omp_unset_lock(&(q->lock));
}


/*----------------------------------------------------------------------------*/
/**
  @brief    put object on end of queue, blocks if queue is full
  @param    q      queue
  @param    data   object to put on queue
  @return   0 iff everything is ok

  if an error is set the function will not block, no data will be inserted
  and the error code returned

  */
/*----------------------------------------------------------------------------*/
cpl_error_code visir_queue_put(visir_queue * q, void * data)
{
    /* double checked lock failures not so bad here */
#pragma omp flush
    while (q->size >= q->max_size && q->error == CPL_ERROR_NONE) {
        /* no condition variables in omp :( */
        struct timespec ts = { 0, 50 * 1000000 };
        nanosleep(&ts, NULL);
#pragma omp taskyield
#pragma omp flush
    }

    omp_set_lock(&(q->lock));
    if (q->size >= q->max_size || q->error != CPL_ERROR_NONE) {
        omp_unset_lock(&(q->lock));
        return q->error;
    }
    cx_list_push_back(q->queue, data);
    q->size++;
    omp_unset_lock(&(q->lock));

    return CPL_ERROR_NONE;
}


/*----------------------------------------------------------------------------*/
/**
  @brief    pop object on front of queue, blocks if queue is empty
  @param    q      queue
  @return   object

  if an error is set the function will not block, not modify the queue and
  return NULL

  */
/*----------------------------------------------------------------------------*/
void * visir_queue_pop(visir_queue * q)
{
    void * data = NULL;

    while (1) {
        omp_set_lock(&(q->lock));
        if (q->size > 0 && q->error == CPL_ERROR_NONE) {
            data = cx_list_pop_front(q->queue);
            q->size--;
            omp_unset_lock(&(q->lock));
            break;
        }
        else {
            omp_unset_lock(&(q->lock));
            /* no condition variables in omp :( */
            struct timespec ts;
            ts.tv_sec = 0;
            ts.tv_nsec = 50 * 1000000;
            if (q->error != CPL_ERROR_NONE)
                return NULL;
            nanosleep(&ts, NULL);
#pragma omp taskyield
        }
    }
    return data;
}

#endif
