/***************************************************************************
                       alsa-sound-configuration.cpp  -  description
                             -------------------
    begin                : Thu Sep 30 2004
    copyright            : (C) 2004 by Martin Witte
    email                : witte@kawo1.rwth-aachen.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <tqcheckbox.h>
#include <tqgroupbox.h>
#include <tqlayout.h>
#include <tqscrollview.h>

#include <kurlrequester.h>
#include <knuminput.h>
#include <klineedit.h>
#include <kcombobox.h>
#include <ktabwidget.h>
#include <tdelocale.h>

#include "alsa-mixer-element.h"
#include "alsa-sound-configuration.h"
#include "alsa-sound.h"


AlsaSoundConfiguration::AlsaSoundConfiguration (TQWidget *parent, AlsaSoundDevice *dev)
 : AlsaSoundConfigurationUI(parent),
   m_SoundDevice (dev),
   m_groupMixerLayout(NULL),
   m_groupMixerScrollView(NULL),
   m_groupMixerSubFrame(NULL),
   m_dirty(true),
   m_ignore_updates(false)
{
    TQObject::connect(m_comboPlaybackCard,   TQ_SIGNAL(activated(int)),    this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(m_comboCaptureCard,    TQ_SIGNAL(activated(int)),    this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(m_comboPlaybackDevice, TQ_SIGNAL(activated(int)),    this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(m_comboCaptureDevice,  TQ_SIGNAL(activated(int)),    this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(editHWBufferSize,      TQ_SIGNAL(valueChanged(int)), this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(editBufferSize,        TQ_SIGNAL(valueChanged(int)), this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(chkDisablePlayback,    TQ_SIGNAL(toggled(bool)),     this, TQ_SLOT(slotSetDirty()));
    TQObject::connect(chkDisableCapture,     TQ_SIGNAL(toggled(bool)),     this, TQ_SLOT(slotSetDirty()));

    TQObject::connect(m_comboPlaybackCard, TQ_SIGNAL(activated(const TQString &)),
                     this, TQ_SLOT(slotPlaybackCardSelected(const TQString &)));
    TQObject::connect(m_comboCaptureCard, TQ_SIGNAL(activated(const TQString &)),
                     this, TQ_SLOT(slotCaptureCardSelected(const TQString &)));

    m_groupMixer->setColumnLayout(0, TQt::Horizontal );

    TQHBoxLayout *tmp_layout = new TQHBoxLayout( m_groupMixer->layout() );

    m_groupMixerScrollView = new TQScrollView (m_groupMixer);
    m_groupMixerScrollView->setFrameShape(TQFrame::NoFrame);
    m_groupMixerScrollView->setFrameShadow(TQFrame::Plain);
    m_groupMixerScrollView->enableClipper(true);
    m_groupMixerScrollView->setResizePolicy(TQScrollView::AutoOneFit);
    //m_groupMixerScrollView->setHScrollBarMode(TQScrollView::AlwaysOn);

    tmp_layout->addWidget(m_groupMixerScrollView);


    int card = -1;
    int ret  = 0;
    int idx_playback = 0;
    int idx_capture  = 0;
    while ((ret = snd_card_next(&card)) == 0) {
        char *name = NULL;
        if (card >= 0 && snd_card_get_longname(card, &name) == 0) {
            if (name) {
                m_name2card[name] = card;
                m_card2name[card] = name;
                if (listSoundDevices(NULL, NULL, NULL, NULL, card, SND_PCM_STREAM_PLAYBACK)) {
                    m_comboPlaybackCard->insertItem(name);
                    m_playbackCard2idx[card] = idx_playback++;
                }
                if (listSoundDevices(NULL, NULL, NULL, NULL, card, SND_PCM_STREAM_CAPTURE)) {
                    m_comboCaptureCard->insertItem(name);
                    m_captureCard2idx[card]  = idx_capture++;
                }
            }
        } else {
            break;
        }
    }

    slotCancel();
}


AlsaSoundConfiguration::~AlsaSoundConfiguration ()
{
}


void AlsaSoundConfiguration::slotPlaybackCardSelected(const TQString &cardname)
{
    if (!m_name2card.contains(cardname))
        return;

    listSoundDevices(m_comboPlaybackDevice, &m_playbackDeviceName2dev, &m_dev2playbackDeviceName, &m_playbackDevice2idx, m_name2card[cardname], SND_PCM_STREAM_PLAYBACK);
}


void AlsaSoundConfiguration::slotCaptureCardSelected(const TQString &cardname)
{
    if (!m_name2card.contains(cardname))
        return;

    saveCaptureMixerSettings();

    listSoundDevices(m_comboCaptureDevice, &m_captureDeviceName2dev, &m_dev2captureDeviceName, &m_captureDevice2idx, m_name2card[cardname], SND_PCM_STREAM_CAPTURE);

    m_currentCaptureCard = m_name2card[cardname];

    TQStringList                     vol_list,  sw_list, all_list;
    TQMap<TQString, AlsaMixerElement> vol_ch2id, sw_ch2id;
    AlsaSoundDevice::getCaptureMixerChannels(m_name2card[cardname], NULL, vol_list, vol_ch2id, sw_list, sw_ch2id, &all_list);

    for (TQMapIterator<TQString, QAlsaMixerElement*> it = m_MixerElements.begin(); it != m_MixerElements.end(); ++it) {
        delete *it;
    }
    m_MixerElements.clear();

    if (m_groupMixerSubFrame)
        delete m_groupMixerSubFrame;

    m_groupMixerSubFrame = new TQFrame(m_groupMixerScrollView->viewport());
    m_groupMixerSubFrame->setSizePolicy(TQSizePolicy::Maximum, TQSizePolicy::Maximum);
    m_groupMixerScrollView->addChild(m_groupMixerSubFrame);

    int rows = 1;
    int cols = (all_list.count()+rows-1)/rows;
    m_groupMixerLayout = new TQGridLayout( m_groupMixerSubFrame, rows, cols, 0, 0 );
    m_groupMixerLayout->setAlignment( TQt::AlignBottom );

    int idx = 0;
    for (TQValueListConstIterator<TQString> it = all_list.begin(); it != all_list.end(); ++it, ++idx) {
        QAlsaMixerElement *e = new QAlsaMixerElement(m_groupMixerSubFrame, *it,
                                                     sw_list.contains(*it), vol_list.contains(*it));
        TQObject::connect(e, TQ_SIGNAL(sigDirty()), this, TQ_SLOT(slotSetDirty()));
        m_groupMixerLayout->addWidget(e, idx > cols, idx % cols);
        e->show();
        m_MixerElements.insert(*it, e);
    }
    restoreCaptureMixerSettings();
    m_groupMixerSubFrame->show();
}

void AlsaSoundConfiguration::saveCaptureMixerSettings()
{
    for (TQMapIterator<TQString, QAlsaMixerElement*> it = m_MixerElements.begin(); it != m_MixerElements.end(); ++it) {
        const TQString     &name = it.key();
        int                card = m_currentCaptureCard;
        TQString            id   = AlsaConfigMixerSetting::getIDString(card, name);
        QAlsaMixerElement *e    = *it;
        float              vol    = e->getVolume();
        bool               use    = e->getOverride();
        bool               active = e->getActive();
        e->slotResetDirty();
        m_MixerSettings[id] = AlsaConfigMixerSetting(card,name,use,active,vol);
    }
}

void AlsaSoundConfiguration::restoreCaptureMixerSettings()
{
    for (TQMapIterator<TQString, QAlsaMixerElement*> it = m_MixerElements.begin(); it != m_MixerElements.end(); ++it) {
        const TQString     &name = it.key();
        int                card = m_currentCaptureCard;
        TQString            id   = AlsaConfigMixerSetting::getIDString(card, name);
        QAlsaMixerElement *e    = *it;

        if (m_MixerSettings.contains(id)) {
            const AlsaConfigMixerSetting &s = m_MixerSettings[id];
            e->setVolume(s.m_volume);
            e->setOverride(s.m_use);
            e->setActive(s.m_active);
            e->slotResetDirty();
        } else {
            if (name == "ADC") {
                e->setOverride(true);
                e->setActive(true);
                e->setVolume(1.0);
            }
            else if (name == "Digital") {
                e->setOverride(true);
                e->setActive(true);
                e->setVolume(1.0);
            }
            else if (name == "Wave") {
                e->setOverride(true);
                e->setActive(false);
                e->setVolume(0);
            }
            else if (name == "Capture") {
                e->setOverride(true);
                e->setActive(true);
                e->setVolume(0.01);
            }
            e->slotSetDirty();
        }
    }
}

int AlsaSoundConfiguration::listSoundDevices(KComboBox *combobox, TQMap<TQString, int> *devname2dev, TQMap<int, TQString> *dev2devname, TQMap<int, int> *dev2idx, int card, snd_pcm_stream_t stream)
{
    snd_ctl_t           *handle = NULL;
    int                  dev  = -1;
    snd_ctl_card_info_t *info = NULL;
    snd_pcm_info_t      *pcminfo = NULL;

    snd_ctl_card_info_alloca(&info);
    snd_pcm_info_alloca     (&pcminfo);

    TQString ctlname = "hw:"+TQString::number(card);

    if (combobox)
        combobox->clear();
    if (devname2dev)
        devname2dev->clear();
    if (dev2devname)
        dev2devname->clear();
    if (dev2idx)
        dev2idx->clear();

    int count = 0;

    if (snd_ctl_open (&handle, ctlname.ascii(), 0) == 0) {
        if (snd_ctl_card_info(handle, info) == 0) {

            dev = -1;
            while (1) {
                if (snd_ctl_pcm_next_device(handle, &dev) < 0) {
                    //logError("snd_ctl_pcm_next_device");
                }
                if (dev < 0)
                    break;
                snd_pcm_info_set_device(pcminfo, dev);
                snd_pcm_info_set_subdevice(pcminfo, 0);
                snd_pcm_info_set_stream(pcminfo, stream);
                int err = 0;
                if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
                    if (err != -ENOENT) {
                        //logError(TQString("control digital audio info (%1): %2").arg(card).arg(snd_strerror(err)));
                    }
                    continue;
                }
                const char *dev_name = snd_pcm_info_get_name(pcminfo);
                TQString devname = i18n("context-card-plus-device-number", "%1 device %2").arg(dev_name).arg(dev);
                if (combobox)
                    combobox->insertItem(devname);
                if (devname2dev)
                    (*devname2dev)[devname] = dev;
                if (dev2devname)
                    (*dev2devname)[dev] = devname;
                if (dev2idx)
                    (*dev2idx)[dev] = count;
                ++count;
            }
        }
        snd_ctl_close(handle);
    }
    return count;
}

void AlsaSoundConfiguration::slotOK()
{
    if (!m_dirty)
        return;

    if (m_SoundDevice) {
        m_SoundDevice->setHWBufferSize      ( editHWBufferSize  ->value() * 1024);
        m_SoundDevice->setBufferSize        ( editBufferSize    ->value() * 1024);
        m_SoundDevice->enablePlayback       (!chkDisablePlayback->isChecked());
        m_SoundDevice->enableCapture        (!chkDisableCapture ->isChecked());

        int card   = m_name2card[m_comboPlaybackCard->currentText()];
        int device = m_playbackDeviceName2dev[m_comboPlaybackDevice->currentText()];
        m_SoundDevice->setPlaybackDevice( card, device);
        card   = m_name2card[m_comboCaptureCard->currentText()];
        device = m_captureDeviceName2dev[m_comboCaptureDevice->currentText()];
        m_SoundDevice->setCaptureDevice     ( card, device);

        saveCaptureMixerSettings();
        m_SoundDevice->setCaptureMixerSettings(m_MixerSettings);
    }
    m_dirty = false;
}


void AlsaSoundConfiguration::slotCancel()
{
    if (!m_dirty)
        return;
    m_ignore_updates = true;

    int card = m_SoundDevice ?  m_SoundDevice->getPlaybackCard()   : 0;
    int dev  = m_SoundDevice ?  m_SoundDevice->getPlaybackDevice() : 0;
    m_comboPlaybackCard  ->setCurrentItem(m_playbackCard2idx[card]);
    slotPlaybackCardSelected(m_comboPlaybackCard->currentText());
    m_comboPlaybackDevice->setCurrentItem(m_playbackDevice2idx[dev]);

    card = m_SoundDevice ?  m_SoundDevice->getCaptureCard()   : 0;
    dev  = m_SoundDevice ?  m_SoundDevice->getCaptureDevice() : 0;
    m_comboCaptureCard  ->setCurrentItem(m_captureCard2idx[card]);
    slotCaptureCardSelected(m_comboCaptureCard->currentText());
    m_comboCaptureDevice->setCurrentItem(m_captureDevice2idx[dev]);

    //IErrorLogClient::staticLogDebug(TQString("capture: card = %1(%2), dev = %3").arg(card).arg(m_captureCard2idx[card]).arg(dev));

    editHWBufferSize  ->setValue  (m_SoundDevice ?  m_SoundDevice->getHWBufferSize()/1024 : 4);
    editBufferSize    ->setValue  (m_SoundDevice ?  m_SoundDevice->getBufferSize()/1024 : 4);
    chkDisablePlayback->setChecked(m_SoundDevice ? !m_SoundDevice->isPlaybackEnabled()  : false);
    chkDisableCapture ->setChecked(m_SoundDevice ? !m_SoundDevice->isCaptureEnabled()   : false);

    //IErrorLogClient::staticLogDebug(TQString("capture: card = %1").arg(m_comboCaptureCard->currentText()));


    if (m_SoundDevice)
        m_MixerSettings = m_SoundDevice->getCaptureMixerSettings();
    else
        m_MixerSettings.clear();
    restoreCaptureMixerSettings();

    m_ignore_updates = false;
    m_dirty = false;
}


void AlsaSoundConfiguration::slotUpdateConfig()
{
    slotSetDirty();
    slotCancel();
}

void AlsaSoundConfiguration::slotSetDirty()
{
    if (!m_dirty && !m_ignore_updates) {
        m_dirty = true;
        //emit sigDirty();
    }
}

#include "alsa-sound-configuration.moc"
