#include "ossmodel.h"
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <tdeconfig.h>
#include <tdeglobal.h>
#include <tqtimer.h>

static const char *ossChannelNames[] = SOUND_DEVICE_NAMES;

OssModel::OssModel( TQObject *parent )
    : TQObject(parent), m_timer(0)
{
    m_devices.setAutoDelete(true);
    m_timer = new TQTimer(this);
    connect(m_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(onPollTimer()));
}

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

bool OssModel::open()
{
    openCard(0, "/dev/mixer");
    for ( int i = 1; i < 16; ++i ) {
        char path[32];
        snprintf(path, sizeof(path), "/dev/mixer%d", i);
        int probe = ::open(path, O_RDONLY);
        if ( probe < 0 ) break;
        ::close(probe);
        openCard(i, TQString::fromLocal8Bit(path));
    }
    if ( m_devices.isEmpty() ) {
        if ( m_lastError.isEmpty() )
            m_lastError = TQString("No OSS mixer devices found.");
        return false;
    }
    m_timer->start(500);
    emit ready();
    return true;
}

void OssModel::openCard( int cardIdx, const TQString &path )
{
    int fd = ::open(path.local8Bit().data(), O_RDWR);
    if ( fd < 0 ) {
        if ( m_lastError.isEmpty() )
            m_lastError = path + ": " + TQString::fromLocal8Bit(strerror(errno));
        return;
    }
    m_lastError = TQString();

    mixer_info info;
    TQString cardName;
    if ( ::ioctl(fd, SOUND_MIXER_INFO, &info) == 0 )
        cardName = TQString::fromLocal8Bit(info.name);
    else
        cardName = path;

    int devmask = 0, stmask = 0, recmask = 0;
    ::ioctl(fd, SOUND_MIXER_READ_DEVMASK,    &devmask);
    ::ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stmask);
    ::ioctl(fd, SOUND_MIXER_READ_RECMASK,    &recmask);

    OssCard card;
    card.index = cardIdx;
    card.path  = path;
    card.name  = cardName;
    m_cards.append(card);
    m_fds.append(fd);

    for ( int i = 0; i < SOUND_MIXER_NRDEVICES; ++i ) {
        if ( !(devmask & (1 << i)) ) continue;
        OssDevice::Category cat = (recmask & (1 << i))
                                  ? OssDevice::Input : OssDevice::Output;
        bool stereo = (stmask & (1 << i)) != 0;
        TQString name = TQString::fromLocal8Bit(ossChannelNames[i]);
        OssDevice *dev = new OssDevice(cat, cardIdx, path, fd, i, name, stereo, this);
        m_devices.append(dev);
        emit deviceAdded(dev);
    }
}

void OssModel::onPollTimer()
{
    for ( TQPtrListIterator<OssDevice> it(m_devices); *it; ++it )
        (*it)->syncFromOss();
}

void OssModel::close()
{
    if ( m_timer ) m_timer->stop();
    m_devices.clear();
    m_cards.clear();
    for ( TQValueListIterator<int> it = m_fds.begin(); it != m_fds.end(); ++it )
        ::close(*it);
    m_fds.clear();
}

TQPtrList<OssDevice> OssModel::devicesForCard( int cardIdx ) const
{
    TQPtrList<OssDevice> result;
    for ( TQPtrListIterator<OssDevice> it(m_devices); *it; ++it )
        if ( (*it)->cardIndex() == cardIdx )
            result.append(*it);
    return result;
}

OssDevice *OssModel::defaultOutput() const
{
    for ( TQPtrListIterator<OssDevice> it(m_devices); *it; ++it )
        if ( (*it)->category() == OssDevice::Output && (*it)->name() == "vol" )
            return *it;
    for ( TQPtrListIterator<OssDevice> it(m_devices); *it; ++it )
        if ( (*it)->category() == OssDevice::Output )
            return *it;
    return 0;
}

OssDevice *OssModel::preferredOutput() const
{
    TDEConfig *cfg = TDEGlobal::config();
    cfg->setGroup("OssTray");
    int card = cfg->readNumEntry("TrayCard", -1);
    TQString name = cfg->readEntry("TrayDevice", "");
    if ( card >= 0 && !name.isEmpty() ) {
        for ( TQPtrListIterator<OssDevice> it(m_devices); *it; ++it )
            if ( (*it)->cardIndex() == card && (*it)->name() == name &&
                 (*it)->category() == OssDevice::Output )
                return *it;
    }
    return defaultOutput();
}

void OssModel::setPreferredOutput( int cardIdx, const TQString &name )
{
    TDEConfig *cfg = TDEGlobal::config();
    cfg->setGroup("OssTray");
    cfg->writeEntry("TrayCard",   cardIdx);
    cfg->writeEntry("TrayDevice", name);
    cfg->sync();
    emit preferredOutputChanged( preferredOutput() );
}

#include "ossmodel.moc"
