/*------------------------------------------------------------------------
 *  Copyright 2007-2010 (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 "processor.h"
#include "image.h"
#include "img_scanner.h"
#include "window.h"

static inline int proc_enter(zbar_processor_t *proc)
{
    _zbar_mutex_lock(&proc->mutex);
    return (_zbar_processor_lock(proc));
}

static inline int proc_leave(zbar_processor_t *proc)
{
    int rc = _zbar_processor_unlock(proc, 0);
    _zbar_mutex_unlock(&proc->mutex);
    return (rc);
}

static inline int proc_open(zbar_processor_t *proc)
{
    /* arbitrary default */
    int width = 640, height = 480;
    if (proc->video) {
	width  = zbar_video_get_width(proc->video);
	height = zbar_video_get_height(proc->video);
    }
    return (_zbar_processor_open(proc, "zbar barcode reader", width, height));
}

/* API lock is already held */
int _zbar_process_image(zbar_processor_t *proc, zbar_image_t *img)
{
    int rc;
    uint32_t force_fmt = proc->force_output;
    if (img) {
	uint32_t format;
	zbar_image_t *tmp;
	int nsyms;
	if (proc->dumping) {
	    zbar_image_write(proc->window->image, "zbar");
	    proc->dumping = 0;
	}

	format = zbar_image_get_format(img);
	zprintf(16, "processing: %.4s(%08" PRIx32 ") %dx%d @%p\n",
		(char *)&format, format, zbar_image_get_width(img),
		zbar_image_get_height(img), zbar_image_get_data(img));

	/* FIXME locking all other interfaces while processing is conservative
         * but easier for now and we don't expect this to take long...
         */
	tmp = zbar_image_convert(img, fourcc('Y', '8', '0', '0'));
	if (!tmp)
	    goto error;

	if (proc->syms) {
	    zbar_symbol_set_ref(proc->syms, -1);
	    proc->syms = NULL;
	}
	zbar_image_scanner_recycle_image(proc->scanner, img);
	nsyms = zbar_scan_image(proc->scanner, tmp);
	_zbar_image_swap_symbols(img, tmp);

	zbar_image_destroy(tmp);
	tmp = NULL;
	if (nsyms < 0)
	    goto error;

	proc->syms = zbar_image_scanner_get_results(proc->scanner);
	if (proc->syms)
	    zbar_symbol_set_ref(proc->syms, 1);

	if (_zbar_verbosity >= 8) {
	    const zbar_symbol_t *sym = zbar_image_first_symbol(img);
	    while (sym) {
		zbar_symbol_type_t type = zbar_symbol_get_type(sym);
		int count		= zbar_symbol_get_count(sym);
		zprintf(8, "%s: %s (%d pts) (dir=%d) (q=%d) (%s)\n",
			zbar_get_symbol_name(type), zbar_symbol_get_data(sym),
			zbar_symbol_get_loc_size(sym),
			zbar_symbol_get_orientation(sym),
			zbar_symbol_get_quality(sym),
			(count < 0) ? "uncertain" :
			(count > 0) ? "duplicate" :
					    "new");
		sym = zbar_symbol_next(sym);
	    }
	}

	if (nsyms) {
	    /* FIXME only call after filtering */
	    _zbar_mutex_lock(&proc->mutex);
	    _zbar_processor_notify(proc, EVENT_OUTPUT);
	    _zbar_mutex_unlock(&proc->mutex);
	    if (proc->handler)
		proc->handler(img, proc->userdata);
	}

	if (force_fmt) {
	    zbar_symbol_set_t *syms = img->syms;
	    img			    = zbar_image_convert(img, force_fmt);
	    if (!img)
		goto error;
	    img->syms = syms;
	    zbar_symbol_set_ref(syms, 1);
	}
    }

    /* display to window if enabled */
    rc = 0;
    if (proc->window) {
	if ((rc = zbar_window_draw(proc->window, img)))
	    err_copy(proc, proc->window);
	_zbar_processor_invalidate(proc);
    }

    if (force_fmt && img)
	zbar_image_destroy(img);
    return (rc);

error:
    return (err_capture(proc, SEV_ERROR, ZBAR_ERR_UNSUPPORTED, __func__,
			"unknown image format"));
}

int _zbar_processor_handle_input(zbar_processor_t *proc, int input)
{
    int event = EVENT_INPUT;
    switch (input) {
    case -1:
	event |= EVENT_CANCELED;
	_zbar_processor_set_visible(proc, 0);
	err_capture(proc, SEV_WARNING, ZBAR_ERR_CLOSED, __func__,
		    "user closed display window");
	break;

    case 'd':
	proc->dumping = 1;
	return (0);

    case '+':
    case '=':
	if (proc->window) {
	    int ovl = zbar_window_get_overlay(proc->window);
	    zbar_window_set_overlay(proc->window, ovl + 1);
	}
	break;

    case '-':
	if (proc->window) {
	    int ovl = zbar_window_get_overlay(proc->window);
	    zbar_window_set_overlay(proc->window, ovl - 1);
	}
	break;
    }

    _zbar_mutex_lock(&proc->mutex);
    proc->input = input;
    if (input == -1 && proc->visible && proc->streaming)
	/* also cancel outstanding output waiters */
	event |= EVENT_OUTPUT;
    _zbar_processor_notify(proc, event);
    _zbar_mutex_unlock(&proc->mutex);
    return (input);
}

#ifdef ZTHREAD

static ZTHREAD proc_video_thread(void *arg)
{
    zbar_processor_t *proc = arg;
    zbar_thread_t *thread  = &proc->video_thread;

    _zbar_mutex_lock(&proc->mutex);
    _zbar_thread_init(thread);
    zprintf(4, "spawned video thread\n");

    while (thread->started) {
	zbar_image_t *img;
	/* wait for video stream to be active */
	while (thread->started && !proc->streaming)
	    _zbar_event_wait(&thread->notify, &proc->mutex, NULL);
	if (!thread->started)
	    break;

	/* blocking capture image from video */
	_zbar_mutex_unlock(&proc->mutex);
	img = zbar_video_next_image(proc->video);
	_zbar_mutex_lock(&proc->mutex);

	if (!img && !proc->streaming)
	    continue;
	else if (!img)
	    /* FIXME could abort streaming and keep running? */
	    break;

	/* acquire API lock */
	_zbar_processor_lock(proc);
	_zbar_mutex_unlock(&proc->mutex);

	if (thread->started && proc->streaming)
	    _zbar_process_image(proc, img);

	zbar_image_destroy(img);

	_zbar_mutex_lock(&proc->mutex);
	/* release API lock */
	_zbar_processor_unlock(proc, 0);
    }

    thread->running = 0;
    _zbar_event_trigger(&thread->activity);
    _zbar_mutex_unlock(&proc->mutex);
    return (0);
}

static ZTHREAD proc_input_thread(void *arg)
{
    int rc		   = 0;
    zbar_processor_t *proc = arg;
    zbar_thread_t *thread  = &proc->input_thread;
    if (proc->window && proc_open(proc))
	goto done;

    _zbar_mutex_lock(&proc->mutex);
    thread->running = 1;
    _zbar_event_trigger(&thread->activity);
    zprintf(4, "spawned input thread\n");

    rc = 0;
    while (thread->started && rc >= 0) {
	_zbar_mutex_unlock(&proc->mutex);
	rc = _zbar_processor_input_wait(proc, &thread->notify, -1);
	_zbar_mutex_lock(&proc->mutex);
    }

    _zbar_mutex_unlock(&proc->mutex);
    _zbar_processor_close(proc);
    _zbar_mutex_lock(&proc->mutex);

done:
    thread->running = 0;
    _zbar_event_trigger(&thread->activity);
    _zbar_mutex_unlock(&proc->mutex);
    return (0);
}

#endif

zbar_processor_t *zbar_processor_create(int threaded)
{
    zbar_processor_t *proc = calloc(1, sizeof(zbar_processor_t));
    if (!proc)
	return (NULL);
    err_init(&proc->err, ZBAR_MOD_PROCESSOR);

    proc->scanner = zbar_image_scanner_create();
    if (!proc->scanner) {
	free(proc);
	return (NULL);
    }

    proc->threaded = !_zbar_mutex_init(&proc->mutex) && threaded;
    _zbar_processor_init(proc);
    return (proc);
}

void zbar_processor_destroy(zbar_processor_t *proc)
{
    proc_waiter_t *w, *next;

    zbar_processor_init(proc, NULL, 0);

    if (proc->syms) {
	zbar_symbol_set_ref(proc->syms, -1);
	proc->syms = NULL;
    }
    if (proc->scanner) {
	zbar_image_scanner_destroy(proc->scanner);
	proc->scanner = NULL;
    }

    _zbar_mutex_destroy(&proc->mutex);
    _zbar_processor_cleanup(proc);

    assert(!proc->wait_head);
    assert(!proc->wait_tail);
    assert(!proc->wait_next);

    for (w = proc->free_waiter; w; w = next) {
	next = w->next;
	_zbar_event_destroy(&w->notify);
	free(w);
    }

    err_cleanup(&proc->err);
    free(proc);
}

int zbar_processor_init(zbar_processor_t *proc, const char *dev,
			int enable_display)
{
    int rc = 0;
    int video_threaded, input_threaded;

    if (proc->video)
	zbar_processor_set_active(proc, 0);

    if (proc->window && !proc->input_thread.started)
	_zbar_processor_close(proc);

    _zbar_mutex_lock(&proc->mutex);
    _zbar_thread_stop(&proc->input_thread, &proc->mutex);
    _zbar_thread_stop(&proc->video_thread, &proc->mutex);

    _zbar_processor_lock(proc);
    _zbar_mutex_unlock(&proc->mutex);

    if (proc->window) {
	zbar_window_destroy(proc->window);
	proc->window = NULL;
    }

    rc = 0;
    if (proc->video) {
	zbar_video_destroy(proc->video);
	proc->video = NULL;
    }

    if (!dev && !enable_display)
	/* nothing to do */
	goto done;

    if (enable_display) {
	proc->window = zbar_window_create();
	if (!proc->window) {
	    rc = err_capture(proc, SEV_FATAL, ZBAR_ERR_NOMEM, __func__,
			     "allocating window resources");
	    goto done;
	}
    }

    if (dev) {
	proc->video = zbar_video_create();
	if (!proc->video) {
	    rc = err_capture(proc, SEV_FATAL, ZBAR_ERR_NOMEM, __func__,
			     "allocating video resources");
	    goto done;
	}
	if (proc->req_width || proc->req_height)
	    zbar_video_request_size(proc->video, proc->req_width,
				    proc->req_height);
	if (proc->req_intf)
	    zbar_video_request_interface(proc->video, proc->req_intf);
	if ((proc->req_iomode &&
	     zbar_video_request_iomode(proc->video, proc->req_iomode)) ||
	    zbar_video_open(proc->video, dev)) {
	    rc = err_copy(proc, proc->video);
	    goto done;
	}
    }

    /* spawn blocking video thread */
    video_threaded =
	(proc->threaded && proc->video && zbar_video_get_fd(proc->video) < 0);
    if (video_threaded &&
	_zbar_thread_start(&proc->video_thread, proc_video_thread, proc,
			   &proc->mutex)) {
	rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
			 "spawning video thread");
	goto done;
    }

    /* spawn input monitor thread */
    input_threaded =
	(proc->threaded && (proc->window || (proc->video && !video_threaded)));
    if (input_threaded &&
	_zbar_thread_start(&proc->input_thread, proc_input_thread, proc,
			   &proc->mutex)) {
	rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_SYSTEM, __func__,
			 "spawning input thread");
	goto done;
    }

    if (proc->window && !input_threaded && (rc = proc_open(proc)))
	goto done;

    if (proc->video && proc->force_input) {
	if (zbar_video_init(proc->video, proc->force_input))
	    rc = err_copy(proc, proc->video);
    } else if (proc->video) {
	int retry = -1;
	if (proc->window) {
	    retry = zbar_negotiate_format(proc->video, proc->window);
	    if (retry)
		fprintf(stderr,
			"WARNING: no compatible input to output format\n"
			"...trying again with output disabled\n");
	}
	if (retry)
	    retry = zbar_negotiate_format(proc->video, NULL);

	if (retry) {
	    zprintf(1, "ERROR: no compatible %s format\n",
		    (proc->video) ? "video input" : "window output");
	    rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_UNSUPPORTED, __func__,
			     "no compatible image format");
	}
    }

done:
    _zbar_mutex_lock(&proc->mutex);
    proc_leave(proc);
    return (rc);
}

zbar_image_data_handler_t *
zbar_processor_set_data_handler(zbar_processor_t *proc,
				zbar_image_data_handler_t *handler,
				const void *userdata)
{
    zbar_image_data_handler_t *result = NULL;
    proc_enter(proc);

    result	   = proc->handler;
    proc->handler  = handler;
    proc->userdata = userdata;

    proc_leave(proc);
    return (result);
}

void zbar_processor_set_userdata(zbar_processor_t *proc, void *userdata)
{
    _zbar_mutex_lock(&proc->mutex);
    proc->userdata = userdata;
    _zbar_mutex_unlock(&proc->mutex);
}

void *zbar_processor_get_userdata(const zbar_processor_t *proc)
{
    void *userdata;
    zbar_processor_t *ncproc = (zbar_processor_t *)proc;
    _zbar_mutex_lock(&ncproc->mutex);
    userdata = (void *)ncproc->userdata;
    _zbar_mutex_unlock(&ncproc->mutex);
    return (userdata);
}

int zbar_processor_set_config(zbar_processor_t *proc, zbar_symbol_type_t sym,
			      zbar_config_t cfg, int val)
{
    int rc;
    proc_enter(proc);
    rc = zbar_image_scanner_set_config(proc->scanner, sym, cfg, val);
    proc_leave(proc);
    return (rc);
}

int zbar_processor_set_control(zbar_processor_t *proc, const char *control_name,
			       int value)
{
    int rc;
    int value_before, value_after;
    proc_enter(proc);
    if (_zbar_verbosity >= 4)
	if (zbar_video_get_control(proc->video, control_name, &value_before) ==
	    0)
	    zprintf(0, "value of %s before a set: %d\n", control_name,
		    value_before);
    rc = zbar_video_set_control(proc->video, control_name, value);
    if (_zbar_verbosity >= 4)
	if (zbar_video_get_control(proc->video, control_name, &value_after) ==
	    0)
	    zprintf(0, "value of %s after a set: %d\n", control_name,
		    value_after);
    proc_leave(proc);
    return (rc);
}

int zbar_processor_get_control(zbar_processor_t *proc, const char *control_name,
			       int *value)
{
    int rc;
    proc_enter(proc);
    rc = zbar_video_get_control(proc->video, control_name, value);
    proc_leave(proc);
    return (rc);
}

int zbar_processor_request_size(zbar_processor_t *proc, unsigned width,
				unsigned height)
{
    proc_enter(proc);
    proc->req_width  = width;
    proc->req_height = height;
    proc_leave(proc);
    return (0);
}

int zbar_processor_request_interface(zbar_processor_t *proc, int ver)
{
    proc_enter(proc);
    proc->req_intf = ver;
    proc_leave(proc);
    return (0);
}

int zbar_processor_request_iomode(zbar_processor_t *proc, int iomode)
{
    proc_enter(proc);
    proc->req_iomode = iomode;
    proc_leave(proc);
    return (0);
}

int zbar_processor_force_format(zbar_processor_t *proc, unsigned long input,
				unsigned long output)
{
    proc_enter(proc);
    proc->force_input  = input;
    proc->force_output = output;
    proc_leave(proc);
    return (0);
}

int zbar_processor_is_visible(zbar_processor_t *proc)
{
    int visible;
    proc_enter(proc);
    visible = proc->window && proc->visible;
    proc_leave(proc);
    return (visible);
}

int zbar_processor_set_visible(zbar_processor_t *proc, int visible)
{
    int rc = 0;
    proc_enter(proc);
    _zbar_mutex_unlock(&proc->mutex);

    if (proc->window) {
	if (proc->video)
	    rc = _zbar_processor_set_size(proc,
					  zbar_video_get_width(proc->video),
					  zbar_video_get_height(proc->video));
	if (!rc)
	    rc = _zbar_processor_set_visible(proc, visible);

	if (!rc)
	    proc->visible = (visible != 0);
    } else if (visible)
	rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
			 "processor display window not initialized");

    _zbar_mutex_lock(&proc->mutex);
    proc_leave(proc);
    return (rc);
}

const zbar_symbol_set_t *
zbar_processor_get_results(const zbar_processor_t *proc)
{
    const zbar_symbol_set_t *syms;
    zbar_processor_t *ncproc = (zbar_processor_t *)proc;
    proc_enter(ncproc);
    syms = proc->syms;
    if (syms)
	zbar_symbol_set_ref(syms, 1);
    proc_leave(ncproc);
    return (syms);
}

int zbar_processor_user_wait(zbar_processor_t *proc, int timeout)
{
    int rc = -1;

    proc_enter(proc);
    _zbar_mutex_unlock(&proc->mutex);

    if (proc->visible || proc->streaming || timeout >= 0) {
	zbar_timer_t timer;
	rc = _zbar_processor_wait(proc, EVENT_INPUT,
				  _zbar_timer_init(&timer, timeout));
    }

    if (!proc->visible)
	rc = err_capture(proc, SEV_WARNING, ZBAR_ERR_CLOSED, __func__,
			 "display window not available for input");

    if (rc > 0)
	rc = proc->input;

    _zbar_mutex_lock(&proc->mutex);
    proc_leave(proc);
    return (rc);
}

int zbar_processor_set_active(zbar_processor_t *proc, int active)
{
    int rc;
    proc_enter(proc);

    if (!proc->video) {
	rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
			 "video input not initialized");
	goto done;
    }
    _zbar_mutex_unlock(&proc->mutex);

    zbar_image_scanner_enable_cache(proc->scanner, active);

    rc = zbar_video_enable(proc->video, active);
    if (!rc) {
	_zbar_mutex_lock(&proc->mutex);
	proc->streaming = active;
	_zbar_mutex_unlock(&proc->mutex);
	rc = _zbar_processor_enable(proc);
    } else
	err_copy(proc, proc->video);

    if (!proc->streaming && proc->window) {
	if (zbar_window_draw(proc->window, NULL) && !rc)
	    rc = err_copy(proc, proc->window);
	_zbar_processor_invalidate(proc);
    }

    _zbar_mutex_lock(&proc->mutex);
    if (proc->video_thread.started)
	_zbar_event_trigger(&proc->video_thread.notify);

done:
    proc_leave(proc);
    return (rc);
}

int zbar_process_one(zbar_processor_t *proc, int timeout)
{
    int streaming, rc;
    zbar_timer_t timer;

    proc_enter(proc);
    streaming = proc->streaming;
    _zbar_mutex_unlock(&proc->mutex);

    rc = 0;
    if (!proc->video) {
	rc = err_capture(proc, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
			 "video input not initialized");
	goto done;
    }

    if (!streaming) {
	rc = zbar_processor_set_active(proc, 1);
	if (rc)
	    goto done;
    }

    rc = _zbar_processor_wait(proc, EVENT_OUTPUT,
			      _zbar_timer_init(&timer, timeout));

    if (!streaming && zbar_processor_set_active(proc, 0))
	rc = -1;

done:
    _zbar_mutex_lock(&proc->mutex);
    proc_leave(proc);
    return (rc);
}

int zbar_process_image(zbar_processor_t *proc, zbar_image_t *img)
{
    int rc = 0;

    proc_enter(proc);
    _zbar_mutex_unlock(&proc->mutex);

    if (img && proc->window)
	rc = _zbar_processor_set_size(proc, zbar_image_get_width(img),
				      zbar_image_get_height(img));
    if (!rc) {
	zbar_image_scanner_enable_cache(proc->scanner, 0);
	zbar_image_scanner_request_dbus(proc->scanner, proc->is_dbus_enabled);
	rc = _zbar_process_image(proc, img);
	if (proc->streaming)
	    zbar_image_scanner_enable_cache(proc->scanner, 1);
    }

    _zbar_mutex_lock(&proc->mutex);
    proc_leave(proc);
    return (rc);
}

int zbar_processor_request_dbus(zbar_processor_t *proc, int req_dbus_enabled)
{
#ifdef HAVE_DBUS
    proc_enter(proc);
    proc->is_dbus_enabled = req_dbus_enabled;
    proc_leave(proc);
    return (0);
#else
    return (1);
#endif
}
