/*
 *
 * Copyright (C) 2002 George Staikos <staikos@kde.org>
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */


#include "qvideostream.h"

#include <kdebug.h>
#include "kxv.h"

#include <sys/types.h>
#include <X11/Xutil.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_XSHM
extern "C" {
#include <sys/shm.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
}
#endif


class QVideoStreamPrivate {
	public:
		QVideoStreamPrivate();
		~QVideoStreamPrivate();
	KXv *xvHandle;
	KXvDevice *xvdev;
	XImage *xim;
	GC gc;
#ifdef HAVE_XSHM
	XShmSegmentInfo shmh;
#endif
};



QVideoStreamPrivate::QVideoStreamPrivate() {
	xvHandle = 0;
	xim = 0;
}


QVideoStreamPrivate::~QVideoStreamPrivate() {
	delete xvHandle;
}




QVideoStream::QVideoStream(QWidget *widget, const char* name)
: QObject(widget, name), d(new QVideoStreamPrivate), _w(widget), _methods(0), _method(0), _init(false) {
#ifdef HAVE_XSHM
	if (XShmQueryExtension(qt_xdisplay())) {
		_methods |= QVIDEO_METHOD_XSHM;
	}
#endif

	if (KXv::haveXv()) {
		_methods |= QVIDEO_METHOD_XV;
#ifdef HAVE_XSHM
		_methods |= QVIDEO_METHOD_XVSHM;
#endif
	}

	_methods |= QVIDEO_METHOD_X11;

	d->gc = XCreateGC(qt_xdisplay(), _w->winId(), 0, NULL);
}

QVideoStream::~QVideoStream() {
	deInit();
	XFreeGC(qt_xdisplay(), d->gc);
	delete d;
}

void QVideoStream::deInit() {
	if (!_init)
		return;
	_init = false;
	switch (_method) {
	case QVIDEO_METHOD_XSHM:
		Q_ASSERT(_methods & QVIDEO_METHOD_XSHM);
		if (!(_methods & QVIDEO_METHOD_XSHM))
			return;
#ifdef HAVE_XSHM
		XShmDetach(qt_xdisplay(), &(d->shmh));
		XDestroyImage(d->xim);
		d->xim = 0;
		shmdt(d->shmh.shmaddr);
#endif
	break;
	case QVIDEO_METHOD_XVSHM:
		Q_ASSERT(_methods & QVIDEO_METHOD_XVSHM);
		if (!(_methods & QVIDEO_METHOD_XVSHM))
			return;
		delete d->xvHandle;
		d->xvHandle = 0;
	break;
	case QVIDEO_METHOD_XV:
		Q_ASSERT(_methods & QVIDEO_METHOD_XV);
		if (!(_methods & QVIDEO_METHOD_XV))
			return;
		delete d->xvHandle;
		d->xvHandle = 0;
	break;
	case QVIDEO_METHOD_X11:
		Q_ASSERT(_methods & QVIDEO_METHOD_X11);
		if (!(_methods & QVIDEO_METHOD_X11))
			return;
		delete[] d->xim->data;
		d->xim->data = 0;
		XDestroyImage(d->xim);
		d->xim = 0;
	break;
	default:
		Q_ASSERT(0);
		return;
	}
}

void QVideoStream::init() {
	switch (_method) {
	case QVIDEO_METHOD_XSHM:
	{
		Q_ASSERT(_methods & QVIDEO_METHOD_XSHM);
		if (!(_methods & QVIDEO_METHOD_XSHM))
			return;
#ifdef HAVE_XSHM
		memset(&(d->shmh), 0, sizeof(XShmSegmentInfo));
		d->xim = XShmCreateImage(qt_xdisplay(),
					(Visual*)QPaintDevice::x11AppVisual(),
					QPaintDevice::x11AppDepth(),
					ZPixmap, 0, &(d->shmh),
					_inputSize.width(),
					_inputSize.height());
		d->shmh.shmid = shmget(IPC_PRIVATE,
					d->xim->bytes_per_line*d->xim->height,
					IPC_CREAT|0600);
		d->shmh.shmaddr = (char *)shmat(d->shmh.shmid, 0, 0);
		d->xim->data = (char*)d->shmh.shmaddr;
		d->shmh.readOnly = False;
		Status s = XShmAttach(qt_xdisplay(), &(d->shmh));
		if (s) {
			XSync(qt_xdisplay(), False);
			shmctl(d->shmh.shmid, IPC_RMID, 0);
			_init = true;
		} else {
			kdDebug() << "XShmAttach failed!" << endl;
			// FIXME: cleanup
		}
#endif
	}
	break;
	case QVIDEO_METHOD_X11:
		Q_ASSERT(_methods & QVIDEO_METHOD_X11);
		if (!(_methods & QVIDEO_METHOD_X11))
			return;
		d->xim = XCreateImage(qt_xdisplay(),
					(Visual*)QPaintDevice::x11AppVisual(),
					QPaintDevice::x11AppDepth(),
					ZPixmap, 0, 0,
					_inputSize.width(),
					_inputSize.height(),
					32, 0
					);
		d->xim->data = new char[d->xim->bytes_per_line*_inputSize.height()];
		_init = true;
	break;
	case QVIDEO_METHOD_XVSHM:
		{
		Q_ASSERT(_methods & QVIDEO_METHOD_XVSHM);
		if (!(_methods & QVIDEO_METHOD_XVSHM))
			return;

		if (d->xvHandle)
			delete d->xvHandle;

		d->xvHandle = KXv::connect(_w->winId());
		KXvDeviceList& xvdl(d->xvHandle->devices());
		KXvDevice *xvdev = NULL;

		for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) {
			if (xvdev->isImageBackend() && 
				xvdev->supportsWidget(_w)) {
				d->xvdev = xvdev;
				d->xvdev->useShm(true);
				_init = true;
				break;
			}
                }

		if (!_init) {
			delete d->xvHandle;
			d->xvHandle = 0;
		}
        }
	break;
	case QVIDEO_METHOD_XV:
		{
		Q_ASSERT(_methods & QVIDEO_METHOD_XV);
		if (!(_methods & QVIDEO_METHOD_XV))
			return;

		if (d->xvHandle)
			delete d->xvHandle;

		d->xvHandle = KXv::connect(_w->winId());
		KXvDeviceList& xvdl(d->xvHandle->devices());
		KXvDevice *xvdev = NULL;

		for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) {
			if (xvdev->isImageBackend() && 
				xvdev->supportsWidget(_w)) {
				d->xvdev = xvdev;
				d->xvdev->useShm(false);
				_init = true;
				break;
			}
                }

		if (!_init) {
			delete d->xvHandle;
			d->xvHandle = 0;
		}
        }
	break;
	default:
		Q_ASSERT(0);
		return;
	}
}

bool QVideoStream::scaling() const {
	return _scale;
}

void QVideoStream::setScaling(bool scale) {
	_scale = scale;
}

bool QVideoStream::haveMethod(int method) const {
	return _methods & method;
}

int QVideoStream::method() const {
	return _method;
}

int QVideoStream::setMethod(int method) {
	if (_methods & method) {
		deInit();
		_method = method;
		init();
	}

return _method;
}

QSize QVideoStream::maxSize() const {
	if (_scale) {
		return _w->size();
	} else {
		return _size;
	}
}

int QVideoStream::maxWidth() const {
	if (_scale) {
		return _w->width();
	} else {
		return _size.width();
	}
}
 
int QVideoStream::maxHeight() const {
	if (_scale) {
		return _w->height();
	} else {
		return _size.height();
	}
}


QSize QVideoStream::size() const {
	return _size;
}

int QVideoStream::width() const {
	return _size.width();
}

int QVideoStream::height() const {
	return _size.height();
}

QSize QVideoStream::setSize(const QSize& sz) {
	_size = sz;
return _size;
}

int QVideoStream::setWidth(int width) {
	if (width < 0)
		width = 0;
	if (width > maxWidth())
		width = maxWidth();
	_size.setWidth(width);
return _size.width();
}

int QVideoStream::setHeight(int height) {
	if (height < 0)
		height = 0;
	if (height > maxHeight())
		height = maxHeight();
	_size.setHeight(height);
return _size.height();
}


QSize QVideoStream::inputSize() const {
	return _inputSize;
}

int QVideoStream::inputWidth() const {
	return _inputSize.width();
}

int QVideoStream::inputHeight() const {
	return _inputSize.height();
}

QSize QVideoStream::setInputSize(const QSize& sz) {
	if (sz == _inputSize)
		return _inputSize;
	_inputSize = sz;
	if (_method & (QVIDEO_METHOD_XSHM | QVIDEO_METHOD_X11)) {
		deInit();
		init();
	}
return _inputSize;
}

int QVideoStream::setInputWidth(int width) {
	if (width == _inputSize.width())
		return _inputSize.width();
	_inputSize.setWidth(width);
	if (_method & (QVIDEO_METHOD_XSHM | QVIDEO_METHOD_X11)) {
		deInit();
		init();
	}
return _inputSize.width();
}

int QVideoStream::setInputHeight(int height) {
	if (height == _inputSize.height())
		return _inputSize.height();
	_inputSize.setHeight(height);
	if (_method & (QVIDEO_METHOD_XSHM | QVIDEO_METHOD_X11)) {
		deInit();
		init();
	}
return _inputSize.height();
}


int QVideoStream::displayFrame(const unsigned char *const img) {

	Q_ASSERT(_init);
	if (!_init)
		return -1;

	switch (_method) {
	case QVIDEO_METHOD_XV:
	case QVIDEO_METHOD_XVSHM:
		Q_ASSERT(_methods & QVIDEO_METHOD_XV);
		if (!(_methods & QVIDEO_METHOD_XV))
			return -1;
		Q_ASSERT(d->xvdev);
		d->xvdev->displayImage(_w, img, _inputSize.width(), _inputSize.height(), _size.width(), _size.height());
	break;
	case QVIDEO_METHOD_XSHM:
	{
		Q_ASSERT(_methods & QVIDEO_METHOD_XSHM);
		if (!(_methods & QVIDEO_METHOD_XSHM))
			return -1;
#ifdef HAVE_XSHM
		memcpy(d->xim->data,img,d->xim->bytes_per_line*d->xim->height);
		XShmPutImage(qt_xdisplay(), _w->winId(), d->gc, d->xim,
				0, 0,
				0, 0,
				_inputSize.width(), _inputSize.height(),
				0);
		XSync(qt_xdisplay(), False);
#endif
	}
	break;
	case QVIDEO_METHOD_X11:
	{
		Q_ASSERT(_methods & QVIDEO_METHOD_X11);
		if (!(_methods & QVIDEO_METHOD_X11))
			return -1;
		memcpy(d->xim->data,img,d->xim->bytes_per_line*d->xim->height);
		XPutImage(qt_xdisplay(), _w->winId(), d->gc, d->xim,
				0, 0,
				0, 0,
				_inputSize.width(), _inputSize.height());
		XSync(qt_xdisplay(), False);
	}
	break;
	default:
		Q_ASSERT(0);
		return -1;
	}

return 0;
}


QVideoStream& QVideoStream::operator<<(const unsigned char *const img) {
	displayFrame(img);
return *this;
}



#include "qvideostream.moc"


