/***************************************************************************
                           kdetv_grabber.cpp
                           -----------------
    begin                : Thu Oct 27 2004
    copyright            : (C) 2004 by Dirk Ziegelmeier
    email                : dziegel@gmx.de
***************************************************************************/

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "kdetv_grabber.h"

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>

#include <qapplication.h>
#include <qmutex.h>

#include <klocale.h>
#include <kdebug.h>

#include <v4l2dev.h>
#include <qvideostream.h>

#include "kdetvvideo/kdetvimage.h"
#include "kdetvvideo/kdetvimagepool.h"
#include "kdetvvideo/kdetvimagefilter.h"
#include "kdetvvideo/kdetvimagefiltercontext.h"
#include "kdetvvideo/kdetvformatconversionfilter.h"


V4L2Grabber::V4L2Grabber(QObject *owner, V4L2Dev *dev, QVideoStream* vs, KdetvImage::ImageFormat fmt)
    : _owner(owner),
      _d(dev),
      _stop(false),
      _vs(vs),
      _fmt(fmt)
{
    _inputSize = _d->inputSize();

    _poolWithBuffer    = new KdetvImagePool(2, (_inputSize.width()+3)*_inputSize.height()*KdetvImage::bytesppForFormat(_fmt));
    _poolWithoutBuffer = new KdetvImagePool(V4L2GRABBER_HISTORY+2, 0);

    for(unsigned int i=0; i<V4L2GRABBER_HISTORY; i++) {
        _images[i] = _poolWithoutBuffer->getImage();
    }

    _ctx = new KdetvImageFilterContext();
    _ctx->images              = _images;
    _ctx->imageCount          = 0;
    _ctx->imagesWithBuffer    = _poolWithBuffer;
    _ctx->imagesWithoutBuffer = _poolWithoutBuffer;
}

V4L2Grabber::~V4L2Grabber()
{
    fprintf(stderr, "V4L2Grabber::~V4L2Grabber(): wait().\n");

    stop();
    wait();

    for(unsigned int i=0; i<V4L2GRABBER_HISTORY; i++) {
        _images[i]->deleteRef();
    }

    delete _poolWithBuffer;
    delete _poolWithoutBuffer;
    delete _ctx;

    fprintf(stderr, "V4L2Grabber::~V4L2Grabber(): deleted.\n");
}

void V4L2Grabber::run()
{
    struct timeval grab, temp;
    int diff;
    KdetvSharedImage* oddField;
    unsigned int id;
    int rc = 0;
	int errors = 0;

	if (!_d)
		return;

	while (!_stop) {
        // grab image
        _devMutex.lock();
        if (_stop) {
            _devMutex.unlock();
            break;
        }
        if(!_d->waitBuffer(&id)) {
            rc = -1;
        } else {
            rc = 0;
        }
        _devMutex.unlock();

        if(!rc) {
            // Lock out Qt so we can safely paint to the TV widget
            // Don't drop the lock until the second field was painted
            // so we do not loose too much performance
            qApp->lock();
            if (_stop) {
                _d->enqueueBuffer(id);
                qApp->unlock();
                break;
            }

            KdetvSharedImage* img = _poolWithoutBuffer->getImage();
            img->setBuffer((unsigned char*)_d->getStreamingBuffer(id), 0, false);
            img->setFormat(_fmt);
            img->setSize(_inputSize);
            img->setStride(0);
            img = _fmtConv->filter(img, _poolWithBuffer->getImage());

            // Update image history
            _images[V4L2GRABBER_HISTORY-1]->deleteRef();
            _images[V4L2GRABBER_HISTORY-2]->deleteRef();
            for(unsigned int i=V4L2GRABBER_HISTORY-1; i!=1; i--) {
                _images[i] = _images[i-2];
            }

            // Make the just grabbed progressive image the even field
            img->setStride(img->bytesPerLine());
            img->setType(KdetvImage::TYPE_INTERLACED_EVEN);
            
            // Create a "dummy" image containing the odd field
            oddField = _poolWithoutBuffer->getImage();
            oddField->setFormat(img->format());
            oddField->setSize(img->size());
            oddField->setStride(img->bytesPerLine());
            oddField->setType(KdetvImage::TYPE_INTERLACED_ODD);
            oddField->setBuffer(img->buffer() + img->bytesPerLine(), 0, false);

            // and enqueue them to the image history
            if(_mostRecentField == KdetvImage::TYPE_INTERLACED_EVEN) {
                _images[0] = img;
                _images[1] = oddField;
            } else {
                _images[0] = oddField;
                _images[1] = img;
            }

            // Update context and filter
            _ctx->images = &_images[1];
            _ctx->out = _poolWithoutBuffer->getImage();
            _ctx->out->setFormat(img->format());
            _ctx->out->setSize(img->size());
            _ctx->out->setStride(0);
            _ctx->out->setBuffer(img->buffer(), 0, false);
            _ctx->out_x = 0;
            _ctx->out_y = 0;
            _ctx->out_width  = _ctx->out->size().width();
            _ctx->out_height = _ctx->out->size().height();
            _ctx->imageCount += 1;
            if(_ctx->imageCount > V4L2GRABBER_CONTEXT_HISTORY) {
                _ctx->imageCount = V4L2GRABBER_CONTEXT_HISTORY;
            }
            _ctx = *_flt << _ctx;

			// paint image to TV widget
            gettimeofday(&grab, 0);
			_vs->setInputSize(_ctx->out->size());
            rc = _vs->displayFrame(_ctx->out->buffer(), _ctx->out_x, _ctx->out_y, _ctx->out_width, _ctx->out_height);
            _ctx->out->deleteRef();

            if(_fullFrameRate && _flt->supportsFullFrameRate()) {
                // Update context and filter
                _ctx->images = &_images[0];
                _ctx->out = _poolWithoutBuffer->getImage();
                _ctx->out->setFormat(img->format());
                _ctx->out->setSize(img->size());
                _ctx->out->setStride(0);
                _ctx->out->setBuffer(img->buffer(), 0, false);
                _ctx->out_x = 0;
                _ctx->out_y = 0;
                _ctx->out_width  = _ctx->out->size().width();
                _ctx->out_height = _ctx->out->size().height();
                _ctx->imageCount += 1;
                if(_ctx->imageCount > V4L2GRABBER_CONTEXT_HISTORY) {
                    _ctx->imageCount = V4L2GRABBER_CONTEXT_HISTORY;
                }
                _ctx = *_flt << _ctx;
                
                // Delay until next field time
                do {
                    gettimeofday(&temp, 0);
                    diff = (temp.tv_sec*1000*1000 + temp.tv_usec) - (grab.tv_sec*1000*1000 + grab.tv_usec);
                } while(diff < _fieldTime);
                
                // paint image to TV widget
                _vs->setInputSize(_ctx->out->size());
                rc = _vs->displayFrame(_ctx->out->buffer(), _ctx->out_x, _ctx->out_y, _ctx->out_width, _ctx->out_height);
                _ctx->out->deleteRef();
            }
            qApp->unlock();
        }

        _d->enqueueBuffer(id);

        if (!rc) {
			errors = 0;
        } else if (++errors > 20) {
            qApp->postEvent( _owner,
                             new V4L2ErrorEvent(i18n("Unable to grab video.\nVideo display is not possible with the current plugin configuration.\
                                                Try playing with the configuration options of the V4L2 plugin.") ));
			fprintf(stderr, "Too many errors. Ending V4L2 grabbing.\n");
			break;
		}
	}

	while (!_stop)
        msleep(100); // if we bail out above because of an error, don't delete the object
	deleteLater();
}
