/*------------------------------------------------------------------------
 *  Copyright 2007-2009 (c) Jeff Brown <spadix@users.sourceforge.net>
 *
 *  This file is part of the ZBar Bar Code Reader.
 *
 *  The ZBar Bar Code Reader is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Lesser Public License as
 *  published by the Free Software Foundation; either version 2.1 of
 *  the License, or (at your option) any later version.
 *
 *  The ZBar Bar Code Reader 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 Lesser Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser Public License
 *  along with the ZBar Bar Code Reader; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 *  Boston, MA  02110-1301  USA
 *
 *  http://sourceforge.net/projects/zbar
 *------------------------------------------------------------------------*/

#include <assert.h>
#include "processor.h"

/* the processor api lock is a recursive mutex with added capabilities
 * to completely drop all lock levels before blocking and atomically
 * unblock a waiting set.  the lock is implemented using a variation
 * of the "specific notification pattern" [cargill], which makes it
 * easy to provide these features across platforms with consistent,
 * predictable semantics.  probably overkill, but additional overhead
 * associated with this mechanism should fall in the noise, as locks
 * are only exchanged O(frame/image)
 *
 * [cargill]
 * http://www.profcon.com/profcon/cargill/jgf/9809/SpecificNotification.html
 */

static inline proc_waiter_t *proc_waiter_queue(zbar_processor_t *proc)
{
    proc_waiter_t *waiter = proc->free_waiter;
    if (waiter) {
	proc->free_waiter = waiter->next;
	waiter->events	  = 0;
    } else {
	waiter = calloc(1, sizeof(proc_waiter_t));
	_zbar_event_init(&waiter->notify);
    }

    waiter->next      = NULL;
    waiter->requester = _zbar_thread_self();

    if (proc->wait_head)
	proc->wait_tail->next = waiter;
    else
	proc->wait_head = waiter;
    proc->wait_tail = waiter;
    return (waiter);
}

static inline proc_waiter_t *proc_waiter_dequeue(zbar_processor_t *proc)
{
    proc_waiter_t *prev = proc->wait_next, *waiter;
    if (prev)
	waiter = prev->next;
    else
	waiter = proc->wait_head;
    while (waiter && (waiter->events & EVENTS_PENDING)) {
	prev		= waiter;
	proc->wait_next = waiter;
	waiter		= waiter->next;
    }

    if (waiter) {
	if (prev)
	    prev->next = waiter->next;
	else
	    proc->wait_head = waiter->next;
	if (!waiter->next)
	    proc->wait_tail = prev;
	waiter->next = NULL;

	proc->lock_level = 1;
	proc->lock_owner = waiter->requester;
    }
    return (waiter);
}

static inline void proc_waiter_release(zbar_processor_t *proc,
				       proc_waiter_t *waiter)
{
    if (waiter) {
	waiter->next	  = proc->free_waiter;
	proc->free_waiter = waiter;
    }
}

int _zbar_processor_lock(zbar_processor_t *proc)
{
    proc_waiter_t *waiter;
    if (!proc->lock_level) {
	proc->lock_owner = _zbar_thread_self();
	proc->lock_level = 1;
	return (0);
    }

    if (_zbar_thread_is_self(proc->lock_owner)) {
	proc->lock_level++;
	return (0);
    }

    waiter = proc_waiter_queue(proc);
    _zbar_event_wait(&waiter->notify, &proc->mutex, NULL);

    assert(proc->lock_level == 1);
    assert(_zbar_thread_is_self(proc->lock_owner));

    proc_waiter_release(proc, waiter);
    return (0);
}

int _zbar_processor_unlock(zbar_processor_t *proc, int all)
{
    assert(proc->lock_level > 0);
    assert(_zbar_thread_is_self(proc->lock_owner));

    if (all)
	proc->lock_level = 0;
    else
	proc->lock_level--;

    if (!proc->lock_level) {
	proc_waiter_t *waiter = proc_waiter_dequeue(proc);
	if (waiter)
	    _zbar_event_trigger(&waiter->notify);
    }
    return (0);
}

void _zbar_processor_notify(zbar_processor_t *proc, unsigned events)
{
    proc_waiter_t *waiter;
    proc->wait_next = NULL;
    for (waiter = proc->wait_head; waiter; waiter = waiter->next)
	waiter->events =
	    ((waiter->events & ~events) | (events & EVENT_CANCELED));

    if (!proc->lock_level) {
	waiter = proc_waiter_dequeue(proc);
	if (waiter)
	    _zbar_event_trigger(&waiter->notify);
    }
}

static inline int proc_wait_unthreaded(zbar_processor_t *proc,
				       proc_waiter_t *waiter,
				       zbar_timer_t *timeout)
{
    int rc;
    int blocking = proc->streaming && zbar_video_get_fd(proc->video) < 0;
    _zbar_mutex_unlock(&proc->mutex);

    rc = 1;
    while (rc > 0 && (waiter->events & EVENTS_PENDING)) {
	int reltime;
	/* FIXME lax w/the locking (though shouldn't matter...) */
	if (blocking) {
	    zbar_image_t *img = zbar_video_next_image(proc->video);
	    if (!img) {
		rc = -1;
		break;
	    }

	    /* FIXME reacquire API lock! (refactor w/video thread?) */
	    _zbar_mutex_lock(&proc->mutex);
	    _zbar_process_image(proc, img);
	    zbar_image_destroy(img);
	    _zbar_mutex_unlock(&proc->mutex);
	}
	reltime = _zbar_timer_check(timeout);
	if (blocking && (reltime < 0 || reltime > MAX_INPUT_BLOCK))
	    reltime = MAX_INPUT_BLOCK;
	rc = _zbar_processor_input_wait(proc, NULL, reltime);
    }
    _zbar_mutex_lock(&proc->mutex);
    return (rc);
}

int _zbar_processor_wait(zbar_processor_t *proc, unsigned events,
			 zbar_timer_t *timeout)
{
    int save_level;
    proc_waiter_t *waiter;
    int rc;

    _zbar_mutex_lock(&proc->mutex);
    save_level	   = proc->lock_level;
    waiter	   = proc_waiter_queue(proc);
    waiter->events = events & EVENTS_PENDING;

    _zbar_processor_unlock(proc, 1);
    if (proc->threaded)
	rc = _zbar_event_wait(&waiter->notify, &proc->mutex, timeout);
    else
	rc = proc_wait_unthreaded(proc, waiter, timeout);

    if (rc <= 0 || !proc->threaded) {
	/* reacquire api lock */
	waiter->events &= EVENT_CANCELED;
	proc->wait_next = NULL;
	if (!proc->lock_level) {
	    proc_waiter_t *w = proc_waiter_dequeue(proc);
	    assert(w == waiter);
	} else
	    _zbar_event_wait(&waiter->notify, &proc->mutex, NULL);
    }
    if (rc > 0 && (waiter->events & EVENT_CANCELED))
	rc = -1;

    assert(proc->lock_level == 1);
    assert(_zbar_thread_is_self(proc->lock_owner));

    proc->lock_level = save_level;
    proc_waiter_release(proc, waiter);
    _zbar_mutex_unlock(&proc->mutex);
    return (rc);
}
