/*
 *
 * 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., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


#include <qwidget.h>
#include <klocale.h>
#include "kdetv_oss.h"
#include <kdebug.h>
#include <kconfig.h>
#include <kmessagebox.h>
#include <assert.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qcombobox.h>
#include <qframe.h>


#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#if defined(__linux__)
#include <sys/soundcard.h>
#elif defined(__FreeBSD__)
#include "machine/soundcard.h"
#elif defined(__NetBSD__)
#include <soundcard.h>
#endif


const char *const devNames[] = SOUND_DEVICE_LABELS;


QWidget *KdetvOSS::configWidget(QWidget *parent, const char *name)
{
    QFrame *w = new QFrame(parent, name);
    QGridLayout *g = new QGridLayout(w, 7, 7);

	g->addMultiCellWidget(new QLabel(i18n("Physical mixer:"), w), 0, 0, 0, 2);
	_cfg_devs = new QComboBox(w, "Device List");
	g->addMultiCellWidget(_cfg_devs, 0, 0, 3, 7);

	_cfg_devs->clear();
	_cfg_devs->insertStringList(_devlist);

	connect(_cfg_devs, SIGNAL(activated(const QString&)), 
            this, SLOT(deviceChanged(const QString&)));

	g->addMultiCellWidget(new QLabel(i18n("Mixer device:"), w), 1, 1, 0, 2);
	_cfg_mixers = new QComboBox(w, "Mixer List");
	g->addMultiCellWidget(_cfg_mixers, 1, 1, 3, 7);

	_cfg_mixers->clear();
	_cfg_mixers->insertStringList(_mixerMap[_devlist[0]]);
	//kdDebug() << "Mixer size: " << _mixers.count()
	//	  << " Dev size: " << _devlist.count() << endl;

	if (!_dev.isEmpty()) {
		for (int i = 0; i < _cfg_devs->count(); i++) {
			if (_cfg_devs->text(i) == _dev) {
				_cfg_devs->setCurrentItem(i);
				_cfg_devs->setEditText(_dev);
				break;
			}
		}
	} else {
        KMessageBox::error(0L,
                           i18n("No mixers found. Check you OSS driver installation."),
                           i18n("No OSS Mixers Found"));
        delete w;
        return NULL;
    }

	if (!_currentMixer.isEmpty()) {
		for (int i = 0; i < _cfg_mixers->count(); i++) {
			if (_cfg_mixers->text(i) == _currentMixer) {
				_cfg_mixers->setCurrentItem(i);
				_cfg_mixers->setEditText(_currentMixer);
				break;
			}
		}
	}

	//kdDebug() << "Kdetv OSS plugin: Created a configuration widget!" << endl;
	return w;
}


KdetvOSS::KdetvOSS(Kdetv *ktv, QObject *parent, const char* name)
    : KdetvMixerPlugin(ktv, "ossmixer", parent,name), _muted(false)
{
	kdDebug() << "Kdetv OSS plugin loaded successfully." << endl;
	
	_fd = -1;
	_dev = "";
	_devnum = 0;  
	QString dev;

    dev = "/dev/mixer%1";
	if (!access("/dev/sound/mixer", R_OK|W_OK)) {
		// we are using devfs, so change the device divining string 
		dev = "/dev/sound/mixer%1";	
		_devlist << "/dev/sound/mixer";
	}
	if (!access("/dev/mixer", R_OK|W_OK)) {
		_devlist << "/dev/mixer";
	}
	for (int i = 0; i < 9; i++) {
		if (!access(dev.arg(i).local8Bit(), R_OK|W_OK)) {
			_devlist << dev.arg(i);
		}
	}

	if (!access("/dev/dsp", R_OK|W_OK)) {
		_devlist << "/dev/dsp";
	}
    dev = "/dev/dsp%1";
	for (int i = 0; i < 9; i++) {
		if (!access(dev.arg(i).local8Bit(), R_OK|W_OK)) {
			_devlist << dev.arg(i);
		}
	}

	probeDevices();
	if (_fd != -1)
        close(_fd);

	_cfg->setGroup("Mixer");
	_dev = _cfg->readEntry("Device", "");
	_currentMixer = _cfg->readEntry("Mixer", "");
	if (!_devlist.contains(_dev)) {
		_dev = "";
		_currentMixer = "";
	}

	if (!_devlist.isEmpty()) {
		if (_dev.isEmpty())
			_dev = _devlist[0];
		_fd = open(_dev.latin1(), O_RDWR);
		if (_fd != -1) {
			_mixers = _mixerMap[_dev];
			setMixer(_currentMixer.isEmpty() ? i18n("Vol") 
                     : _currentMixer);
		}
	}
	
	preMuteLevels.left = -1;
	preMuteLevels.right = -1;
}


KdetvOSS::~KdetvOSS()
{
	kdDebug() << "Kdetv OSS plugin unloaded." << endl;
	if (_fd != -1)
        close(_fd);
}


void KdetvOSS::saveConfig()
{
	if (-1 != _fd) {
		close(_fd);
	}

	QString nd = _cfg_devs->currentText();
	_fd = open(nd.local8Bit(), O_RDWR);

	if (_fd != -1) {
		_dev = nd;
		setMixer(_cfg_mixers->currentText());
		_mixers = _mixerMap[_dev];
		_cfg->setGroup("Mixer");
		_cfg->writeEntry("Device", nd);
		_cfg->writeEntry("Mixer", _currentMixer);
		_cfg->sync();
		kdDebug() << "OSS successfully opened mixer " << _dev
                  << " (" << _currentMixer << ")" << endl;
	}
	volume();
	emit volumeChanged(_left, _right);
}


void KdetvOSS::deviceChanged(const QString& dev)
{
	_cfg_mixers->clear();
	_cfg_mixers->insertStringList(_mixerMap[dev]);
}


int KdetvOSS::setMixer(const QString& mixer)
{
	for (int i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
		if (mixer == devNames[i]) {
			kdDebug() << "OSS Plugin switched to mixer: " << devNames[i] << endl;
			_devnum = i;
			_currentMixer = mixer;
			volume();
			return 0;
		}
	}

	return -1;
}


int KdetvOSS::probeDevices()
{
	QStringList killList;

	if (_fd != -1) {
		close(_fd);
	}

	for (QStringList::ConstIterator it = _devlist.constBegin(); it != _devlist.constEnd(); ++it) {
		_fd = open((*it).local8Bit(), O_RDWR);
		if (_fd == -1) {
			killList << *it;
			continue;
		}

		_mixerMap[*it].clear();

		if (-1 != ioctl(_fd, SOUND_MIXER_READ_DEVMASK, &_mask)) {

			for (int i = 0; i < SOUND_MIXER_NRDEVICES; i++)
				if (_mask & (1 << i)) {
					kdDebug() << "OSS Plugin found a mixer for " << *it << " (" << i << "): " << devNames[i] << endl;
					_mixerMap[*it] << devNames[i];
				}
		}

		if (_mixerMap[*it].isEmpty()) {
			killList << *it;
		}

		close(_fd);
	}

	for (QStringList::ConstIterator it = killList.constBegin(); it != killList.constEnd(); ++it)
		_devlist.remove(*it);

	_fd = -1;

	if (!_dev.isEmpty()) {
		_fd = open(_dev.latin1(), O_RDWR);
		if (_fd == -1) {
			_fd = -1;
		} else {
			_mixers = _mixerMap[_dev];
			if (_mixers.count() > 0) {
				setMixer(_mixers[0]);
			}
		}
	}

	return 0;
}


int KdetvOSS::setVolume(int left, int right)
{
	// Unmute if already muted...
	if (_muted) {
		_muted = false;
		preMuteLevels.right = preMuteLevels.left = -1;
	}
	
	//kdDebug() << "KdetvOSS::setVolume: Left = " << left << ", right = "
	//		  << right << endl;
	int vol = left + (right << 8);
	
	if (_fd == -1)
		return -1;
	
	if (-1 == ioctl(_fd, MIXER_WRITE(_devnum), &vol))
		return -1;
	
	_left = left;
	_right = right;
	
	return 0;
}


int KdetvOSS::volume()
{
	int vol;
	
	if (_muted)  {
		vol = preMuteLevels.left + (preMuteLevels.right << 8);
	} else {
		if (_fd == -1)
			return -1;

		if (-1 == ioctl(_fd, MIXER_READ(_devnum), &vol)) {
			return -1;
		}

		_left = vol & 0xff;
		_right = (vol >> 8) & 0xff;
	}
	
	return vol;
}


int KdetvOSS::volumeLeft()
{
	return volume() & 0xff;
}


int KdetvOSS::volumeRight()
{
	return (volume() >> 8) & 0xff;
}


int KdetvOSS::setMuted(bool mute)
{  
    if (mute)  {
        // If we are already muted, ignore the request.
		if (mute != _muted) {
			preMuteLevels.right = _right;
	 		preMuteLevels.left = _left;
	
			int vol = 0;
	
			if (_fd == -1)
				return -1;

			if (-1 == ioctl(_fd, MIXER_WRITE(_devnum), &vol))
				return -1;
		}
		_muted = mute;
	} else {
		_muted = mute;
	
		if (preMuteLevels.left != -1 &&  preMuteLevels.right != -1) {
			setVolume (preMuteLevels.left,  preMuteLevels.right);
			preMuteLevels.right = preMuteLevels.left = -1;
		}	
	} 
	
	return 0;
}

bool KdetvOSS::muted()
{
	return _muted;
}


extern "C" {
	KdetvOSS* create_oss(Kdetv *ktv)
    {
		return new KdetvOSS(ktv, 0, "OSS plugin");
	}
}

#include "kdetv_oss.moc"
