/*
 *
 * Copyright (C) 2002 George Staikos <staikos@kde.org>
 *               2004 Dirk Ziegelmeier <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 <qtextstream.h>
#include <fixx11h.h>
#include <qimage.h>
#include <qwidget.h>
#include <qtimer.h>
#include <klocale.h>
#include "kdetv_xv.h"
#include <kdebug.h>
#include <math.h>
#include <assert.h>

#include <qapplication.h>

#include <kscreensaver_vroot.h>
#include <QX11Info>

#include "channel.h"
#include "controlinterface.h"

class XvIntegerControl : public IntegerControl
{
public:
    XvIntegerControl(const QString& uiname, const QString& internalName,
                     const char* xvName, KXvDevice* dev)
        : IntegerControl(uiname, internalName),
          _dev(dev),
          _xvName(xvName)
    {
		_dev->getAttributeRange(_xvName, &minimumValue, &maximumValue);
        advanced     = false;
        defaultValue = (minimumValue + maximumValue) / 2;
        step         = 1;
    }

    virtual ~XvIntegerControl()
    {
    }

    virtual bool doSetValue(int value)
    {
        return _dev->setAttribute(_xvName, value);
    }

    virtual int value() const
    {
        int val;
        _dev->getAttribute(_xvName, &val);
        return val;
    }


private:
    KXvDevice*  _dev;
    const char* _xvName;
};

// ---------------------------------------------------------------

KdetvXv::KdetvXv(Kdetv *ktv, QWidget *parent, const char* name)
    : KdetvSourcePlugin(ktv, "xv",parent,name)
{
    xvDevice = NULL;
    xvHandle = NULL;
    kDebug() << "Kdetv XVideo plugin loaded successfully.";

    resizeTimer = new QTimer(this, "resizeTimer");
    connect(resizeTimer, SIGNAL( timeout() ),
            this, SLOT( startVideo() ));

    connect( parent, SIGNAL( resized(int, int) ),
             this, SLOT( viewResized() ) );
}


KdetvXv::~KdetvXv()
{
    delete resizeTimer;
    stopVideo();
    delete xvHandle;
    kDebug() << "Kdetv XVideo plugin unloaded.";
}


bool KdetvXv::isTuner()
{
	if (xvDevice) {
		return xvDevice->getAttribute("XV_FREQ", NULL);
	}
    return false;
}


QColor KdetvXv::colourKey()
{
	if (xvDevice) {
		int val = 0;
		xvDevice->getAttribute("XV_COLORKEY", &val);
		XColor a;
		memset(&a, 0, sizeof(a));
		a.pixel = val;
		XQueryColor(QX11Info::display(), 
                    QPaintDevice::x11AppColormap( qt_xscreen() ),
                    &a);
		return QColor(QColor(a.red, a.green, a.blue).rgb(), val);
	}
    
    return QColor();
}


Control::ControlList& KdetvXv::controls()
{
    return _controls;
}


int KdetvXv::signal()
{
	if (xvDevice) {
		int v;
		if (xvDevice->getAttribute("XV_SIGNAL", &v))
			return v;
	}
    
    return -1;
}


int KdetvXv::frequency()
{
	if (xvDevice) {
		int v;
		if (xvDevice->getAttribute("XV_FREQ", &v))
			return v*125/2;
	}
    
    return -1;
}


/* ARGH, this doesn't work on my card.  It always returns 0! */
bool KdetvXv::muted()
{
	if (xvDevice) {
		int v;
		if (xvDevice->getAttribute("XV_MUTE", &v)) {
			kDebug() << "XV_MUTE == " << v;
			return (v==1);
		}
	}
    
    return false;
}


const QStringList& KdetvXv::broadcastedAudioModes()
{
    static QStringList empty;
    return empty;
}


const QString& KdetvXv::defaultAudioMode()
{
    return QString::null;
}


int KdetvXv::setAudioMode( const QString& )
{
    return -1;
}


int KdetvXv::doSetEncoding( const QString &encoding, const QString &source )
{
	if (_device.isEmpty() || !xvDevice)
		return -1;
    
	// This works together with parseXvEncoding. If XV encoding does not contain
	// "-", the whole string is stored in encoding attribute and source remains empty.
    QString enc;
	if (source.isEmpty()) {
        enc = _encodingsMap[encoding];
	} else {
        enc = _encodingsMap[encoding] + '-' + _sourcesMap[source];
	}
    
	kDebug() << "KdetvXv: doSetEncoding: " << enc;
    
	if (xvDevice->encodings().contains(enc)) {
		xvDevice->setEncoding(enc);
		return 0;
	}
    
	return -2;
}


void KdetvXv::parseXvEncoding( const QString &xvEncoding, QString &source, QString &encoding )
{
    int dashes = xvEncoding.contains("-");

    if (dashes > 0) {
        source   = xvEncoding.section("-", dashes, -1      );
		encoding = xvEncoding.section("-", 0,      dashes-1);
	} else {
		source   = QString();
        encoding = xvEncoding;
	}
}


int KdetvXv::setDevice( const QString& dev )
{
	KXvDeviceList& xvdl(xvHandle->devices());
	KXvDevice *xvdev = NULL;
	QString encoding;
    
	kDebug() << "KdetvXv: setDevice: " << dev;
	for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) {
		QString label = i18n("XVideo: %1 (port %2)",xvdev->name(),xvdev->port());
		if (dev == label) {
			stopVideo();
			xvDevice = xvdev;
			_device  = dev;
			// init _src and _encoding members and set default source/encoding
			_encoding = _encodings[dev].first();
			setSource(_sources[dev].first());

            _controls.clear();
            _controls.append(new XvIntegerControl(i18n("Brightness"),
                                                  "Brightness",
                                                  "XV_BRIGHTNESS",
                                                  xvDevice));
            _controls.append(new XvIntegerControl(i18n("Contrast"),
                                                  "Contrast",
                                                  "XV_CONTRAST",
                                                  xvDevice));
            _controls.append(new XvIntegerControl(i18n("Hue"),
                                                  "Hue",
                                                  "XV_HUE",
                                                  xvDevice));
            _controls.append(new XvIntegerControl(i18n("Saturation"),
                                                  "Saturation",
                                                  "XV_SATURATION",
                                                  xvDevice));

			return 0;
		}
	}

    return -1;
}


int KdetvXv::setChannelProperties(const Channel::PropertyList& properties)
{
    setSource(properties["source"].toString());
    setEncoding(properties["encoding"].toString());
    setFrequency(properties["frequency"].toULongLong());
    return 0;
}


int KdetvXv::setSource( const QString &src )
{
    int rc = doSetEncoding(_encoding, src);
    
	if (rc == 0) {
        _source = src;
	}
	return rc;
}


int KdetvXv::setEncoding( const QString &encoding )
{
    int rc = doSetEncoding(encoding, _source);
    
	if (rc == 0) {
        _encoding = encoding;
	}
	return rc;
}


void KdetvXv::setFrequency( int freq )
{
	if (xvDevice && isTuner()) {
		xvDevice->setAttribute("XV_FREQ", freq*2/125);
		// I hate this hack.  How do we do this right?
	}
}


void KdetvXv::setMuted( bool muted )
{
	if (xvDevice) {
		xvDevice->setAttribute("XV_MUTE", muted ? 1 : 0);
	}
}


void KdetvXv::viewResized()
{
    resizeTimer->start(100, true);
}
    

int KdetvXv::probeDevices()
{
	KXvDeviceList& xvdl(xvHandle->devices());
	KXvDevice *xvdev = NULL;
	QStringList enc;
    
    
	kDebug() << "KdetvXv: probeDevices";
    
	_devices.clear();
	_sources.clear();
	_encodings.clear();
	_tuners.clear();

	for (xvdev = xvdl.first(); xvdev; xvdev = xvdl.next()) {
		if (xvdev->isVideoSource() && xvdev->supportsWidget(_widget)) {
            QString label = i18n("XVideo: %1 (port %2)",xvdev->name(),xvdev->port());
			kDebug() << "KdetvXv: found device: " << label;
			_devices << label;
            
			enc = xvdev->encodings();
			for (QStringList::ConstIterator it = enc.constBegin(); it != enc.constEnd(); ++it) {
                QString source;
				QString encoding;
				parseXvEncoding(*it, source, encoding);

                QString src = source.toLower();
                QString enc = encoding.toLower();

                _sourcesMap[src]   = source;
                _encodingsMap[enc] = encoding;

				if (!_sources[label].contains(src))
                    _sources[label].append(src);
				if (!_encodings[label].contains(enc))
                    _encodings[label].append(enc);
			}
            
			_tuners[label] = xvdev->getAttribute("XV_FREQ", NULL);
		}
	}
    return 0;
}


int KdetvXv::startVideo()
{
	assert(_widget);

	if (!xvDevice || _isVideoDesktop)
		return -1;

	bool rc = xvDevice->startVideo(_widget, _widget->width(), _widget->height());

    if (!rc) {
        kWarning() << "Error starting video in Xv plugin!";
        emit errorMessage("Unable to start video playback.\n\
                           Video playback may not be possible for the current device with the XVIDEO plugin.");
        stopVideo();
        return -2;
    } else {
        return 0;
    }
}


int KdetvXv::stopVideo()
{
	setMuted(true);
	if (!xvDevice || _isVideoDesktop)
		return -1;
	return xvDevice->stopVideo() ? 0 : -1;
}


bool KdetvXv::canVideoDesktop() const
{
    return true;
}


int KdetvXv::setVideoDesktop(bool on)
{
	if (!xvDevice)
		return -1;
    
	if (on) {
		Window w = DefaultRootWindow(QX11Info::display());
		const QRect& s = QApplication::desktop()->screenGeometry();
		stopVideo();
		bool rc = xvDevice->startVideo(w, s.width(), s.height());
		setMuted(false);
		_isVideoDesktop = true;
		if (rc != true) 
            return -1;
	} else {
		if (!_isVideoDesktop)
			return -1;
		_isVideoDesktop = false;
		xvDevice->stopVideo();
		setMuted(true);
		// Window w = DefaultRootWindow(QX11Info::display());
		// FIXME: force a repaint of w.
		return startVideo();
	}
    return 0;
}


bool KdetvXv::videoPlaying() const
{
	if (xvDevice)
		return xvDevice->videoPlaying();
	return false;
}


bool KdetvXv::canGrabStill() const
{
    return true;
}


bool KdetvXv::grabStill( QImage *pix )
{
    if (pix && xvDevice)
        return xvDevice->grabStill( pix, pix->width(), pix->height() );
    return false;
}


extern "C" {
    KdetvXv* create_xv(Kdetv *ktv, QWidget *w)
    {
        Drawable d;
        KXv *xv;
        KdetvXv *qv;
        
        if (!w || !KXv::haveXv())
            return NULL;
        
        d = w->winId();
        
        xv = KXv::connect(d);
        if (!xv)
            return NULL;
        
        qv = new KdetvXv(ktv, w);
        qv->xvHandle = xv;
        qv->setWidget(w);
        
        return qv;
    }
}

#include "kdetv_xv.moc"

