/*  This file is part of the KDE project
    Copyright (C) 2007 Matthias Kretz <kretz@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 version 2 as published by the Free Software Foundation.

    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 Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

*/

#include "mixer_alsa.h"
#include "mixerintegercontrol_p.h"
#include "mixertogglecontrol_p.h"
#include "mixercontrol_p.h"
#include "mixerelement.h"
#include "mixerenumcontrol_p.h"
#include "mixer.h"
#include "mixer_p.h"
#include "mixercontrolset.h"

#include <QtCore/QCoreApplication>
#include <QtCore/QEvent>
#include <QtCore/QSocketNotifier>
#include <QtCore/QStringList>
#include <QtCore/QTimer>

#include <alsa/asoundlib.h>
#include <kdebug.h>
#include <klocale.h>
#include "alsaclasses.h"

#ifndef HAS_LIBASOUND
#error "build system error"
#endif

namespace Phonon
{
namespace Mixer
{

AlsaNotifier::AlsaNotifier(AlsaControl ctl, QObject *parent)
    : QObject(parent)
{
    ctl.subscribeEvents(true);

    const QList<int> fds = ctl.pollDescriptors();
    foreach (int fd, fds) {
        QSocketNotifier *n = new QSocketNotifier(fd, QSocketNotifier::Read, this);
        connect(n, SIGNAL(activated(int)), this, SIGNAL(readyRead()));
        //n = new QSocketNotifier(fd, QSocketNotifier::Write, this);
        //connect(n, SIGNAL(activated(int)), this, SIGNAL(readyWrite()));
    }
}

static bool isValidMixerDevice(const QByteArray &alsaDeviceName)
{
    if (alsaDeviceName.isEmpty()) {
        return false;
    }
    snd_ctl_t *ctl = 0;
    const int success = snd_ctl_open(&ctl, alsaDeviceName, SND_CTL_NONBLOCK);
    if (success == 0) {
        snd_ctl_close(ctl);
        return true;
    }
    return false;
}

QByteArray getDeviceId(const QVariant &mixerDeviceId)
{
    switch (mixerDeviceId.type()) {
    case QVariant::String:
        if (isValidMixerDevice(mixerDeviceId.toString().toUtf8())) {
            return mixerDeviceId.toString().toUtf8();
        }
        break;
    case QVariant::StringList:
        foreach (const QString &id, mixerDeviceId.toStringList()) {
            if (isValidMixerDevice(id.toUtf8())) {
                return id.toUtf8();
            }
        }
        break;
    case QVariant::Int:
        if (isValidMixerDevice("hw:" + QByteArray::number(mixerDeviceId.toInt()))) {
            return "hw:" + QByteArray::number(mixerDeviceId.toInt());
        }
        break;
    default:
        break;
    }
    return QByteArray();
}

bool MixerPrivate::isValidDeviceId(const QVariant &mixerDeviceId)
{
    return !getDeviceId(mixerDeviceId).isEmpty();
}

bool MixerSharedData::operator==(const QVariant &mixerDeviceId) const
{
    const QByteArray name = getDeviceId(mixerDeviceId);
    AlsaControl ctl(name);
    AlsaCardInfo info;
    ctl.readCardInfo(&info);
    return info.card() == m_cardNumber;
}

bool MixerSharedData::operator==(Mixer::Type) const
{
    return false;
}

bool MixerSharedData::isValid() const
{
    if (m_ctl) {
        for (int card = AlsaCard::first(); card >= 0; card = AlsaCard::next(card)) {
            if (m_cardNumber == card) {
                // the card is still there
                return true;
            }
        }
        kDebug() << "cleaning up after the card disappeared";
        // the card is gone
        delete m_notifier;
        m_notifier = 0;
        delete m_ctl;
        m_ctl = 0;
        foreach (MixerControl c, m_controls) {
            c.d_func()->invalidate();
        }
        m_controlSets.clear();
        m_controls.clear();
    } else if (m_cardNumber >= 0) {
        // the card was there once but is gone now
        for (int card = AlsaCard::first(); card >= 0; card = AlsaCard::next(card)) {
            if (m_cardNumber == card) {
                kDebug() << "restore the mixer after the card reappeared";
                // the card returned
                m_ctl = new AlsaControl("hw:" + QByteArray::number(m_cardNumber));
                m_notifier = new AlsaNotifier(*m_ctl, const_cast<MixerSharedData *>(this));
                connect(m_notifier, SIGNAL(readyRead()), SLOT(eventReady()));
                return true;
            }
        }
    }
    return false;
}

MixerSharedData::MixerSharedData(const QVariant &mixerDeviceId)
    : m_ctl(new AlsaControl(getDeviceId(mixerDeviceId)))
{
    init();
}

MixerSharedData::MixerSharedData(Mixer::Type type)
    : m_ctl(new AlsaControl("hw:0"))
{
    Q_ASSERT(type == Mixer::PhononControls);
    init();
}

void MixerSharedData::cleanup()
{
    delete m_ctl;
}

void MixerSharedData::init()
{
    AlsaCardInfo info;
    m_ctl->readCardInfo(&info);
    m_cardNumber = info.card();
    kDebug() << info.id() << info.driver() << info.name() << info.longname() << info.mixername();

    m_notifier = new AlsaNotifier(*m_ctl, this);
    connect(m_notifier, SIGNAL(readyRead()), SLOT(eventReady()));
}

void MixerSharedData::eventReady()
{
    if (!isValid()) {
        return;
    }
    AlsaEvent event;
    AlsaElementId elementId;
    AlsaElementValue value;
    AlsaElementInfo info;

    for (int err = m_ctl->readEvent(&event); err > 0; err = m_ctl->readEvent(&event)) {
        kDebug() << event;
        const unsigned int mask = event.mask();
        if (mask == SND_CTL_EVENT_MASK_REMOVE) {
            kDebug() << "element removed";
            continue; // the element is gone, the rest of the code might crash
        }
        if (mask & SND_CTL_EVENT_MASK_ADD) {
            kDebug() << "element added";
        }
        if (mask & SND_CTL_EVENT_MASK_INFO) {
            kDebug() << "info changed";
        }
        if (mask & SND_CTL_EVENT_MASK_VALUE) {
            kDebug() << "value changed";
            foreach (MixerControl c, m_controls) {
                MixerControlSharedData *cs = c.d_func()->s_func();
                if (cs && cs->m_id.numid() == event.numid()) {
                    cs->valueChangeEvent();
                }
            }
        }
        if (mask & SND_CTL_EVENT_MASK_TLV) {
            kDebug() << "TLV changed";
            foreach (MixerControl c, m_controls) {
                MixerControlSharedData *cs = c.d_func()->s_func();
                if (cs && cs->m_id.numid() == event.numid()) {
                    cs->extraInfoChangeEvent();
                }
            }
        }

        event.readElementId(&elementId);
        m_ctl->readElementValue(&value, elementId);
        m_ctl->readElementInfo(&info, elementId);
        for (unsigned int index = 0; index < info.count(); ++index) {
            value.setIndex(index);
            kDebug() << "type" << snd_ctl_elem_type_name(info.type());
            switch (info.type()) {
            case SND_CTL_ELEM_TYPE_NONE:
                abort();
            case SND_CTL_ELEM_TYPE_BOOLEAN:
                kDebug() << "boolean" << value.boolean(index);
                break;
            case SND_CTL_ELEM_TYPE_INTEGER:
                kDebug() << "integer" << value.integer(index);
                break;
            case SND_CTL_ELEM_TYPE_INTEGER64:
                kDebug() << "integer64" << value.integer64(index);
                break;
            case SND_CTL_ELEM_TYPE_ENUMERATED:
                kDebug() << "enumerated" << value.enumerated(index);
                break;
            case SND_CTL_ELEM_TYPE_BYTES:
                kDebug() << "bytes" << value.bytes();
                break;
            case SND_CTL_ELEM_TYPE_IEC958:
                {
                    snd_aes_iec958_t iec958;
                    value.iec958(&iec958);
                    kDebug() << "iec958" << iec958.status << iec958.subcode << iec958.dig_subframe;
                }
                break;
            }
        }
    }
}

QString MixerSharedData::name() const
{
    if (!isValid()) {
        return i18n("invalid");
    }
    if (m_name.isEmpty()) {
        AlsaCardInfo info;
        m_ctl->readCardInfo(&info);
        m_name = info.name();
    }
    return m_name;
}

AlsaControl *MixerSharedData::ctl() const
{
    if (isValid()) {
        return m_ctl;
    }
    return 0;
}

void MixerSharedData::updateControls()
{
    kDebug();
    if (!isValid()) {
        return;
    }
    AlsaElementList list;
    AlsaElementId id;
    AlsaElementInfo info;

    m_ctl->readElementList(&list);
    const unsigned int used = list.used();
    kDebug() << used << list.count();
    for (unsigned int i = 0; i < used; ++i) {
        list.readId(i, &id);
        m_ctl->readElementInfo(&info, id);

        const QString name = QString::fromUtf8(id.name());
        kDebug() << name;
        MixerControlSet set = controlSetFor(name);
        switch (info.type()) {
        case SND_CTL_ELEM_TYPE_BOOLEAN:
            {
                //TODO: fix type
                for (uint i = 0; i < info.count(); ++i) {
                    id.setIndex(i);
                    MixerToggleControlSharedData *s = new MixerToggleControlSharedData(MixerSharedData::Ptr(this), id,
                                (info.count() == 1) ? MixerToggleControl::MuteMonoSwitch :
                                (i == 0 ?  MixerToggleControl::MuteLeftSwitch :
                                 MixerToggleControl::MuteRightSwitch));
                    s->m_controlSet = set;
                    m_controls << MixerToggleControl(*new MixerToggleControlPrivate(s));
                }
            }
            break;
        case SND_CTL_ELEM_TYPE_INTEGER:
            {
                for (uint i = 0; i < info.count(); ++i) {
                    id.setIndex(i);
                    kDebug() << id.index() << info.count();
                    MixerIntegerControlSharedData *s = new MixerIntegerControlSharedData(
                                MixerSharedData::Ptr(this), id,
                                (info.count() == 1) ? MixerIntegerControl::MonoChannel :
                                (i == 0 ?  MixerIntegerControl::LeftChannel :
                                 MixerIntegerControl::RightChannel));
                    s->m_controlSet = set;
                    m_controls << MixerIntegerControl(*new MixerIntegerControlPrivate(s));
                }
            }
            break;
        case SND_CTL_ELEM_TYPE_ENUMERATED:
            {
                //TODO: fix type
                for (uint i = 0; i < info.count(); ++i) {
                    id.setIndex(i);
                    MixerEnumControlSharedData *s = new MixerEnumControlSharedData(
                            MixerSharedData::Ptr(this), id, MixerEnumControl::CaptureSource);
                    s->m_controlSet = set;
                    m_controls << MixerEnumControl(*new MixerEnumControlPrivate(s));
                }
            }
            break;

        case SND_CTL_ELEM_TYPE_NONE:
        case SND_CTL_ELEM_TYPE_BYTES:
        case SND_CTL_ELEM_TYPE_IEC958:
        case SND_CTL_ELEM_TYPE_INTEGER64:
            kWarning() << "element type not handled";
            continue;
        }

    }
    qSort(m_controls);
    //qSort(m_controlSets);
}

bool Mixer::removeControl(const MixerControl &control)
{
    K_S(Mixer);
    Q_ASSERT(control.d_func()->s_func());
    return 0 == s->m_ctl->removeElement(control.d_func()->s_func()->m_id);
}

} // namespace Mixer
} // namespace Phonon
