//------------------------------------------------------------------------
//  Copyright 2008-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 "QZBarThread.h"
#include <iostream>

using namespace zbar;

static const QString textFormat("%1:%2");

QZBarThread::QZBarThread(int verbosity)
    : _videoOpened(false), reqWidth(DEFAULT_WIDTH), reqHeight(DEFAULT_HEIGHT),
      video(NULL), image(NULL), running(true), videoRunning(false),
      videoEnabled(false)
{
    zbar_set_verbosity(verbosity);
    scanner.set_handler(*this);
}

void QZBarThread::image_callback(Image &image)
{
    for (Image::SymbolIterator sym = image.symbol_begin();
	 sym != image.symbol_end(); ++sym)
	if (!sym->get_count()) {
	    QString data = QString::fromStdString(sym->get_data());
	    emit decoded(sym->get_type(), data);

	    emit decodedText(textFormat.arg(
		QString::fromStdString(sym->get_type_name()), data));
	}
}

void QZBarThread::processImage(Image &image)
{
    {
	scanner.recycle_image(image);
	Image tmp = image.convert(*(long *)"Y800");
	scanner.scan(tmp);
	image.set_symbols(tmp.get_symbols());
    }
    window.draw(image);
    if (this->image && this->image != &image) {
	delete this->image;
	this->image = NULL;
    }
    emit update();
}

void QZBarThread::enableVideo(bool enable)
{
    if (!video) {
	videoRunning = videoEnabled = false;
	return;
    }
    try {
	scanner.enable_cache(enable);
	video->enable(enable);
	videoRunning = enable;
    } catch (std::exception &e) {
	std::cerr << "ERROR: " << e.what() << std::endl;
    }
    if (!enable) {
	// release video image and revert to logo
	clear();
	emit update();
    }
}

void QZBarThread::openVideo(const QString &device)
{
    if (videoRunning)
	enableVideo(false);

    {
	QMutexLocker locker(&mutex);
	videoEnabled = _videoOpened = false;
    }

    // ensure old video doesn't have image ref
    // (FIXME handle video destroyed w/images outstanding)
    clear();
    emit update();

    if (video) {
	delete video;
	video = NULL;
	emit videoOpened(false);
    }

    if (device.isEmpty())
	return;

    try {
	std::string devstr = device.toStdString();
	video		   = new Video(devstr);

	if (reqWidth != DEFAULT_WIDTH || reqHeight != DEFAULT_HEIGHT)
	    video->request_size(reqWidth, reqHeight);

	negotiate_format(*video, window);
	{
	    QMutexLocker locker(&mutex);
	    videoEnabled = _videoOpened = true;
	    reqWidth			= video->get_width();
	    reqHeight			= video->get_height();
	}
	currentDevice = device;

	emit videoOpened(true);
    } catch (std::exception &e) {
	std::cerr << "ERROR: " << e.what() << std::endl;
	emit videoOpened(false);
    }
}

void QZBarThread::videoDeviceEvent(VideoDeviceEvent *e)
{
    openVideo(e->device);
}

void QZBarThread::videoEnabledEvent(VideoEnabledEvent *e)
{
    if (videoRunning && !e->enabled)
	enableVideo(false);
    videoEnabled = e->enabled;
}

void QZBarThread::scanImageEvent(ScanImageEvent *e)
{
    if (videoRunning)
	enableVideo(false);

    try {
	image = new QZBarImage(e->image);
	processImage(*image);
    } catch (std::exception &e) {
	std::cerr << "ERROR: " << e.what() << std::endl;
	clear();
    }
}

bool QZBarThread::event(QEvent *e)
{
    switch ((EventType)e->type()) {
    case VideoDevice:
	videoDeviceEvent((VideoDeviceEvent *)e);
	break;
    case VideoEnabled:
	videoEnabledEvent((VideoEnabledEvent *)e);
	break;
    case ScanImage:
	scanImageEvent((ScanImageEvent *)e);
	break;
    case Exit:
	if (videoRunning)
	    enableVideo(false);
	running = false;
	break;
    case ReOpen:
	openVideo(currentDevice);
	break;
    default:
	return (false);
    }
    return (true);
}

void QZBarThread::run()
{
    QEvent *e = NULL;
    while (running) {
	if (!videoEnabled) {
	    QMutexLocker locker(&mutex);
	    while (queue.isEmpty())
		newEvent.wait(&mutex);
	    e = queue.takeFirst();
	} else {
	    // release reference to any previous QImage
	    clear();
	    enableVideo(true);

	    while (videoRunning && !e) {
		try {
		    Image image = video->next_image();
		    processImage(image);
		} catch (std::exception &e) {
		    std::cerr << "ERROR: " << e.what() << std::endl;
		    enableVideo(false);
		    openVideo("");
		}
		QMutexLocker locker(&mutex);
		if (!queue.isEmpty())
		    e = queue.takeFirst();
	    }

	    if (videoRunning)
		enableVideo(false);
	}
	if (e) {
	    event(e);
	    delete e;
	    e = NULL;
	}
    }
    clear();
    openVideo("");
}

QVector<QPair<int, QString> > QZBarThread::get_menu(int index)
{
    QVector<QPair<int, QString> > vector;
    struct video_controls_s *ctrl;

    if (!video)
	return vector;

    ctrl = video->get_controls(index);
    if (!ctrl)
	return vector;

    for (unsigned int i = 0; i < ctrl->menu_size; i++)
	vector.append(qMakePair((int)ctrl->menu[i].value,
				QString::fromUtf8(ctrl->menu[i].name)));

    return vector;
}

int QZBarThread::get_controls(int index, char **name, char **group,
			      enum QZBar::ControlType *type, int *min, int *max,
			      int *def, int *step)
{
    struct video_controls_s *ctrl;

    if (!video)
	return 0;

    ctrl = video->get_controls(index);
    if (!ctrl)
	return 0;

    if (name)
	*name = ctrl->name;
    if (group)
	*group = ctrl->group;
    if (min)
	*min = ctrl->min;
    if (max)
	*max = ctrl->max;
    if (def)
	*def = ctrl->def;
    if (step)
	*step = ctrl->step;

    if (type) {
	switch (ctrl->type) {
	case VIDEO_CNTL_INTEGER:
	    *type = QZBar::Integer;
	    break;
	case VIDEO_CNTL_MENU:
	    *type = QZBar::Menu;
	    break;
	case VIDEO_CNTL_BUTTON:
	    *type = QZBar::Button;
	    break;
	case VIDEO_CNTL_INTEGER64:
	    *type = QZBar::Integer64;
	    break;
	case VIDEO_CNTL_STRING:
	    *type = QZBar::String;
	    break;
	case VIDEO_CNTL_BOOLEAN:
	    *type = QZBar::Boolean;
	    break;
	default:
	    *type = QZBar::Unknown;
	    break;
	}
    }

    return 1;
}

int QZBarThread::set_control(char *name, bool value)
{
    if (!video)
	return 0;

    return video->set_control(name, value);
}

int QZBarThread::set_control(char *name, int value)
{
    if (!video)
	return 0;

    return video->set_control(name, value);
}

int QZBarThread::get_control(char *name, bool *value)
{
    if (!video)
	return 0;

    return video->get_control(name, value);
}

int QZBarThread::get_control(char *name, int *value)
{
    if (!video)
	return 0;

    return video->get_control(name, value);
}

void QZBarThread::request_size(unsigned width, unsigned height)
{
    reqWidth  = width;
    reqHeight = height;
}

int QZBarThread::get_resolution(int index, unsigned &width, unsigned &height,
				float &max_fps)
{
    struct video_resolution_s *res;

    if (!video)
	return 0;

    res = video->get_resolution(index);
    if (!res)
	return 0;

    width   = res->width;
    height  = res->height;
    max_fps = res->max_fps;

    return 1;
}