/*
 *              KMix -- KDE's full featured mini mixer
 *
 *              Copyright (C) 1996-2000 Christian Esken
 *                        esken@kde.org
 *
 * This program 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 program 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 program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

//OSS4 mixer backend for KMix by Yoper Team released under GPL v2 or later


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

#include <tqregexp.h>
#include <kdebug.h>

// Since we're guaranteed an OSS setup here, let's make life easier
#if !defined(__NetBSD__) && !defined(__OpenBSD__)
#include <sys/soundcard.h>
#else
#include <soundcard.h>
#endif

#include "mixer_oss4.h"
#include <tdelocale.h>

Mixer_Backend* OSS4_getMixer(int device)
{
	Mixer_Backend *l_mixer;
	l_mixer = new Mixer_OSS4(device);
	return l_mixer;
}

Mixer_OSS4::Mixer_OSS4(int device) : Mixer_Backend(device)
{
	if ( device == -1 ) m_devnum = 0;
	m_numExtensions = 0;
}

Mixer_OSS4::~Mixer_OSS4()
{
	close();
}

bool Mixer_OSS4::setRecsrcHW(int ctrlnum, bool on)
{
	return true;
}

//dummy implementation only
bool Mixer_OSS4::isRecsrcHW(int ctrlnum)
{
	return false;
}

//classifies mixexts according to their name, last classification wins
MixDevice::ChannelType Mixer_OSS4::classifyAndRename(TQString &name, int flags)
{
	MixDevice::ChannelType cType = MixDevice::UNKNOWN;
	TQStringList classes = TQStringList::split ( TQRegExp ( "[-,.]" ), name );


	if ( flags & MIXF_PCMVOL  ||
	     flags & MIXF_MONVOL  ||
	     flags & MIXF_MAINVOL )
	{
		cType = MixDevice::VOLUME;
	}

	for ( TQStringList::Iterator it = classes.begin(); it != classes.end(); ++it )
	{
		if ( *it == "line" )
		{
			*it = "Line";
			cType = MixDevice::EXTERNAL;

		} else
		if ( *it == "mic" )
		{
			*it = "Microphone";
			cType = MixDevice::MICROPHONE;
		} else
		if ( *it == "vol" )
		{
			*it = "Volume";
			cType = MixDevice::VOLUME;
		} else
		if ( *it == "surr" )
		{
			*it = "Surround";
			cType = MixDevice::SURROUND;
		} else
		if ( *it == "bass" )
		{
			*it = "Bass";
			cType = MixDevice::BASS;
		} else
		if ( *it == "treble" )
		{
			*it = "Treble";
			cType = MixDevice::TREBLE;
		} else
		if ( (*it).startsWith ( "pcm" ) )
		{
			(*it).replace ( "pcm","PCM" );
			cType = MixDevice::AUDIO;
		} else
		if ( *it == "src" )
		{
			*it = "Source";
		} else
		if ( *it == "rec" )
		{
			*it = "Recording";
		} else
		if ( *it == "cd" )
		{
			*it = (*it).upper();
			cType = MixDevice::CD;
		}
		if ( (*it).startsWith("vmix") )
		{
			(*it).replace("vmix","Virtual Mixer");
			cType = MixDevice::VOLUME;
		} else
		if ( (*it).endsWith("vol") )
		{
			TQChar &ref = (*it).ref(0);
			ref = ref.upper();
			cType = MixDevice::VOLUME;
		}
		else
		{
			TQChar &ref = (*it).ref(0);
			ref = ref.upper();
		}
	}
	name = classes.join( " ");
	return cType;
}

int Mixer_OSS4::open()
{
	if ( (m_fd= ::open("/dev/mixer", O_RDWR)) < 0 )
	{
		if ( errno == EACCES )
			return Mixer::ERR_PERM;
		else
			return Mixer::ERR_OPEN;
	}

	if (wrapIoctl( ioctl (m_fd, OSS_GETVERSION, &m_ossVersion) ) < 0)
	{
		return Mixer::ERR_READ;
	}
	if (m_ossVersion < 0x040000)
	{
		return Mixer::ERR_NOTSUPP;
	}


	wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NRMIX, &m_numMixers) );

	if ( m_mixDevices.isEmpty() )
	{
		if ( m_devnum >= 0 && m_devnum < m_numMixers )
		{
			m_numExtensions = m_devnum;
			bool masterChosen = false;
			oss_mixext ext;
			ext.dev = m_devnum;

			if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_NREXT, &m_numExtensions) ) < 0 )
			{
				//TODO: more specific error handling here
				if ( errno == ENODEV ) return Mixer::ERR_NODEV;
				return Mixer::ERR_READ;
			}

			if( m_numExtensions == 0 )
			{
				return Mixer::ERR_NODEV;
			}

			ext.ctrl = 0;

			//read MIXT_DEVROOT, return Mixer::NODEV on error
			if ( wrapIoctl ( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) < 0 )
			{
				return Mixer::ERR_NODEV;
			}

			oss_mixext_root *root = (oss_mixext_root *) ext.data;
			m_mixerName = root->name;

			for ( int i = 1; i < m_numExtensions; i++ )
			{
				bool isCapture = false;

				ext.dev = m_devnum;
				ext.ctrl = i;
	
				//wrapIoctl handles reinitialization, cancel loading on EIDRM
				if ( wrapIoctl( ioctl( m_fd, SNDCTL_MIX_EXTINFO, &ext) ) == EIDRM )
				{
					return 0;
				}

				TQString name = ext.extname;

				//skip vmix volume controls and mute controls
				if ( (name.find("vmix") > -1 && name.find( "pcm") > -1) ||
				     name.find("mute") > -1
#ifdef MIXT_MUTE
				|| (ext.type == MIXT_MUTE)
#endif
				)
				{
					continue;
				}

				//fix for old legacy names, according to Hannu's suggestions
				if ( name.contains('_') )
				{
					name = name.section('_',1,1).lower();
				}

				if ( ext.flags & MIXF_RECVOL || ext.flags & MIXF_MONVOL || name.find(".in") > -1  )
				{
					isCapture = true;
				}

				Volume::ChannelMask chMask = Volume::MNONE;

				MixDevice::ChannelType cType = classifyAndRename(name, ext.flags);

				if ( ext.type == MIXT_STEREOSLIDER16 ||
				        ext.type == MIXT_STEREOSLIDER   ||
				        ext.type == MIXT_MONOSLIDER16   ||
				        ext.type == MIXT_MONOSLIDER     ||
				        ext.type == MIXT_SLIDER
				   )
				{
					if ( ext.type == MIXT_STEREOSLIDER16 ||
					        ext.type == MIXT_STEREOSLIDER
					   )
					{
						if ( isCapture )
						{
							chMask = Volume::ChannelMask(Volume::MLEFT|Volume::MRIGHT);
						}
						else
						{
							chMask = Volume::ChannelMask(Volume::MLEFT|Volume::MRIGHT );
						}
					}
					else
					{
						if ( isCapture )
						{
							chMask = Volume::MLEFT;
						}
						else
						{
							chMask = Volume::MLEFT;
						}
					}

					Volume vol (chMask, ext.maxvalue, ext.minvalue, isCapture);

					MixDevice* md =	new MixDevice(i, vol, isCapture, true,
						                      name, cType, MixDevice::SLIDER);
					
					m_mixDevices.append(md);
					
					if ( !masterChosen && ext.flags & MIXF_MAINVOL )
					{
						m_recommendedMaster = md;
						masterChosen = true;
					}
				}
				else if ( ext.type == MIXT_ONOFF )
				{
					Volume vol;
					vol.setMuted(true);
					MixDevice* md = new MixDevice(i, vol, false, true, name, MixDevice::VOLUME, MixDevice::SWITCH);
					m_mixDevices.append(md);
				}
				else if ( ext.type == MIXT_ENUM )
				{
					oss_mixer_enuminfo ei;
					ei.dev = m_devnum;
					ei.ctrl = i;

					if ( wrapIoctl( ioctl (m_fd, SNDCTL_MIX_ENUMINFO, &ei) ) != -1 )
					{
						Volume vol(Volume::MLEFT, ext.maxvalue, ext.minvalue, false);

						MixDevice* md = new MixDevice (i, vol, false, false,
						                               name, MixDevice::UNKNOWN,
						                               MixDevice::ENUM);

						TQPtrList<TQString> &enumValuesRef = md->enumValues();
						TQString thisElement;

						for ( int i = 0; i < ei.nvalues; i++ )
						{
							thisElement = &ei.strings[ ei.strindex[i] ];

							if ( thisElement.isEmpty() )
							{
								thisElement = TQString::number(i);
							}
							enumValuesRef.append( new TQString(thisElement) );
						}
						m_mixDevices.append(md);
					}
				}

			}
		}
		else
		{
			return -1;
		}
	}
	m_isOpen = true;
	return 0;
}

int Mixer_OSS4::close()
{
	m_isOpen = false;
	int l_i_ret = ::close(m_fd);
	m_mixDevices.clear();
	return l_i_ret;
}

TQString Mixer_OSS4::errorText(int mixer_error)
{
	TQString l_s_errmsg;

	switch( mixer_error )
	{
		case Mixer::ERR_PERM:
			l_s_errmsg = i18n("kmix: You do not have permission to access the mixer device.\n" \
			                   "Login as root and do a 'chmod a+rw /dev/mixer*' to allow the access.");
			break;
		case Mixer::ERR_OPEN:
			l_s_errmsg = i18n("kmix: Mixer cannot be found.\n" \
			                  "Please check that the soundcard is installed and the\n" \
			                  "soundcard driver is loaded.\n" \
			                  "On Linux you might need to use 'insmod' to load the driver.\n" \
			                  "Use 'soundon' when using OSS4 from 4front.");
			break;
		case Mixer::ERR_NOTSUPP:
			l_s_errmsg = i18n("kmix expected an OSSv4 mixer module,\n" \
			                   "but instead found an older version.");
			break;
		default:
			l_s_errmsg = Mixer_Backend::errorText(mixer_error);
	}
	return l_s_errmsg;
}

int Mixer_OSS4::readVolumeFromHW(int ctrlnum, Volume &vol)
{

	oss_mixext extinfo;
	oss_mixer_value mv;

	extinfo.dev = m_devnum;
	extinfo.ctrl = ctrlnum;

	if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 )
	{
		//TODO: more specific error handling
		return Mixer::ERR_READ;
	}

	mv.dev = extinfo.dev;
	mv.ctrl = extinfo.ctrl;
	mv.timestamp = extinfo.timestamp;

	if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_READ, &mv) ) < 0 )
	{
		/* Oops, can't read mixer */
		return Mixer::ERR_READ;
	}
	else
	{
		if ( vol.isMuted() && extinfo.type != MIXT_ONOFF )
		{
			return 0;
		}

		if ( vol.isCapture() )
		{
			switch ( extinfo.type )
			{
				case MIXT_ONOFF:
					vol.setMuted(mv.value != extinfo.maxvalue);
					break;

				case MIXT_MONOSLIDER:
					vol.setVolume(Volume::LEFT, mv.value & 0xff);
					break;

				case MIXT_STEREOSLIDER:
					vol.setVolume(Volume::LEFT, mv.value & 0xff);
					vol.setVolume(Volume::RIGHT, ( mv.value >> 8 ) & 0xff);
					break;

				case MIXT_SLIDER:
					vol.setVolume(Volume::LEFT, mv.value);
					break;

				case MIXT_MONOSLIDER16:
					vol.setVolume(Volume::LEFT, mv.value & 0xffff);
					break;

				case MIXT_STEREOSLIDER16:
					vol.setVolume(Volume::LEFT, mv.value & 0xffff);
					vol.setVolume(Volume::RIGHT, ( mv.value >> 16 ) & 0xffff);
					break;
			}
		}
		else
		{
			switch( extinfo.type )
			{
				case MIXT_ONOFF:
					vol.setMuted(mv.value != extinfo.maxvalue);
					break;
				case MIXT_MONOSLIDER:
					vol.setVolume(Volume::LEFT, mv.value & 0xff);
					break;

				case MIXT_STEREOSLIDER:
					vol.setVolume(Volume::LEFT, mv.value & 0xff);
					vol.setVolume(Volume::RIGHT, ( mv.value >> 8 ) & 0xff);
					break;

				case MIXT_SLIDER:
					vol.setVolume(Volume::LEFT, mv.value);
					break;

				case MIXT_MONOSLIDER16:
					vol.setVolume(Volume::LEFT, mv.value & 0xffff);
					break;

				case MIXT_STEREOSLIDER16:
					vol.setVolume(Volume::LEFT, mv.value & 0xffff);
					vol.setVolume(Volume::RIGHT, ( mv.value >> 16 ) & 0xffff);
					break;
			}
		}
	}
	return 0;
}

int Mixer_OSS4::writeVolumeToHW(int ctrlnum, Volume &vol)
{
	int volume = 0;

	oss_mixext extinfo;
	oss_mixer_value mv;

	extinfo.dev = m_devnum;
	extinfo.ctrl = ctrlnum;

	if ( wrapIoctl( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 )
	{
		//TODO: more specific error handling
		kdDebug ( 67100 ) << "failed to read info for control " << ctrlnum << endl;
		return Mixer::ERR_READ;
	}

	if ( vol.isMuted() && extinfo.type != MIXT_ONOFF )
	{
		volume = 0;
	}
	else
	{
		switch ( extinfo.type )
		{
			case MIXT_ONOFF:
				volume = (vol.isMuted()) ? (extinfo.minvalue) : (extinfo.maxvalue);
				break;
			case MIXT_MONOSLIDER:
				volume = vol[Volume::LEFT];
				break;

			case MIXT_STEREOSLIDER:
				volume = vol[Volume::LEFT] | ( vol[Volume::RIGHT] << 8 );
				break;

			case MIXT_SLIDER:
				volume = vol[Volume::LEFT];
				break;

			case MIXT_MONOSLIDER16:
				volume = vol[Volume::LEFT];
				break;

			case MIXT_STEREOSLIDER16:
				volume = vol[Volume::LEFT] | ( vol[Volume::RIGHT] << 16 );
				break;
			default:
				return -1;
		}
	}

	mv.dev = extinfo.dev;
	mv.ctrl = extinfo.ctrl;
	mv.timestamp = extinfo.timestamp;
	mv.value = volume;

	if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 )
	{
		kdDebug ( 67100 ) << "error writing: " << endl;
		return Mixer::ERR_WRITE;
	}
	return 0;
}

void Mixer_OSS4::setEnumIdHW(int ctrlnum, unsigned int idx)
{
	oss_mixext extinfo;
	oss_mixer_value mv;

	extinfo.dev = m_devnum;
	extinfo.ctrl = ctrlnum;

	if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 )
	{
		//TODO: more specific error handling
		kdDebug ( 67100 ) << "failed to read info for control " << ctrlnum << endl;
		return;
	}

	if ( extinfo.type != MIXT_ENUM )
	{
		return;
	}


	//according to oss docs maxVal < minVal could be true - strange...
	unsigned int maxVal = (unsigned int) extinfo.maxvalue;
	unsigned int minVal = (unsigned int) extinfo.minvalue;

	if ( maxVal < minVal )
	{
		int temp;
		temp = maxVal;
		maxVal = minVal;
		minVal = temp;
	}

	if ( idx > maxVal || idx < minVal )
		idx = minVal;

	mv.dev = extinfo.dev;
	mv.ctrl = extinfo.ctrl;
	mv.timestamp = extinfo.timestamp;
	mv.value = idx;

	if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_WRITE, &mv) ) < 0 )
	{
		/* Oops, can't write to mixer */
		kdDebug ( 67100 ) << "error writing: " << endl;
	}
}

unsigned int Mixer_OSS4::enumIdHW(int ctrlnum)
{
	oss_mixext extinfo;
	oss_mixer_value mv;

	extinfo.dev = m_devnum;
	extinfo.ctrl = ctrlnum;

	if ( wrapIoctl ( ioctl(m_fd, SNDCTL_MIX_EXTINFO, &extinfo) ) < 0 )
	{
		//TODO: more specific error handling
		//TODO: check whether those return values are actually possible
		return Mixer::ERR_READ;
	}

	if ( extinfo.type != MIXT_ENUM )
	{
		return Mixer::ERR_READ;
	}

	mv.dev = extinfo.dev;
	mv.ctrl = extinfo.ctrl;
	mv.timestamp = extinfo.timestamp;

	if ( wrapIoctl ( ioctl (m_fd, SNDCTL_MIX_READ, &mv) ) < 0 )
	{
		/* Oops, can't read mixer */
		return Mixer::ERR_READ;
	}
	return mv.value;
}

int Mixer_OSS4::wrapIoctl(int ioctlRet)
{
	switch( ioctlRet )
	{
		case EIO:
		{
			kdDebug ( 67100 ) << "A hardware level error occured" << endl;
			break;
		}
		case EINVAL:
		{
			kdDebug ( 67100 ) << "Operation caused an EINVAL. You may have found a bug in kmix OSS4 module or in OSS4 itself" << endl;
			break;
		}
		case ENXIO:
		{
			kdDebug ( 67100 ) << "Operation index out of bounds or requested device does not exist - you likely found a bug in the kmix OSS4 module" << endl;
			break;
		}
		case EPERM:
		case EACCES:
		{
			kdDebug ( 67100 ) << errorText ( Mixer::ERR_PERM ) << endl;
			break;
		}
		case ENODEV:
		{
			kdDebug ( 67100 ) << "kmix received an ENODEV errors - are the OSS4 drivers loaded?" << endl;
			break;
		}
		case EPIPE:
		case EIDRM:
		{
			reinitialize();
		}

	}
	return ioctlRet;
}


TQString OSS4_getDriverName()
{
	return "OSS4";
}