/*  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.

*/

#ifndef PHONON_ALSACLASSES_H
#define PHONON_ALSACLASSES_H

#ifdef HAS_LIBASOUND

#include <alsa/asoundlib.h>
#include <QtCore/QDebug>

namespace Phonon
{
namespace Mixer
{

class AlsaElementId
{
    public:
        inline ~AlsaElementId() { if (0 == --*ref) { delete ref; snd_ctl_elem_id_free(p); } }
        inline AlsaElementId(const AlsaElementId &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaElementId &operator=(const AlsaElementId &rhs) { if (0 == --*ref) { delete ref; snd_ctl_elem_id_free(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }

        inline AlsaElementId() : p(0), ref(new int) { *ref = 1; snd_ctl_elem_id_malloc(&p); }
        inline operator snd_ctl_elem_id_t*() { return p; }
        inline operator const snd_ctl_elem_id_t*() const { return p; }
        inline void copy(AlsaElementId *id) const { snd_ctl_elem_id_copy(*id, p); }

        inline unsigned int numid() const { return snd_ctl_elem_id_get_numid(p); }
        inline snd_ctl_elem_iface_t interface() const { return snd_ctl_elem_id_get_interface(p); }
        inline unsigned int device() const { return snd_ctl_elem_id_get_device(p); }
        inline unsigned int subdevice() const { return snd_ctl_elem_id_get_subdevice(p); }
        inline const char *name() const { return snd_ctl_elem_id_get_name(p); }
        inline unsigned int index() const { return snd_ctl_elem_id_get_index(p); }

        inline void setNumid(unsigned int val) { snd_ctl_elem_id_set_numid(p, val); }
        inline void setInterface(snd_ctl_elem_iface_t val) { snd_ctl_elem_id_set_interface(p, val); }
        inline void setDevice(unsigned int val) { snd_ctl_elem_id_set_device(p, val); }
        inline void setSubdevice(unsigned int val) { snd_ctl_elem_id_set_subdevice(p, val); }
        inline void setName(const char *val) { snd_ctl_elem_id_set_name(p, val); }
        inline void setIndex(unsigned int val) { snd_ctl_elem_id_set_index(p, val); }

    private:
        snd_ctl_elem_id_t *p;
        int *ref;
};

class AlsaElementValue
{
    public:
        inline ~AlsaElementValue() { if (0 == --*ref) { delete ref; snd_ctl_elem_value_free(p); } }
        inline AlsaElementValue(const AlsaElementValue &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaElementValue &operator=(const AlsaElementValue &rhs) { if (0 == --*ref) { delete ref; snd_ctl_elem_value_free(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }

        inline AlsaElementValue() : p(0), ref(new int) { *ref = 1; snd_ctl_elem_value_malloc(&p); }
        inline operator snd_ctl_elem_value_t*() { return p; }
        inline operator const snd_ctl_elem_value_t*() const { return p; }

        inline void id(snd_ctl_elem_id_t *ptr) const { snd_ctl_elem_value_get_id(p, ptr); }
        inline unsigned int numid() const { return snd_ctl_elem_value_get_numid(p); }
        inline snd_ctl_elem_iface_t interface() const { return snd_ctl_elem_value_get_interface(p); }
        inline unsigned int device() const { return snd_ctl_elem_value_get_device(p); }
        inline unsigned int subdevice() const { return snd_ctl_elem_value_get_subdevice(p); }
        inline const char *name() const { return snd_ctl_elem_value_get_name(p); }
        inline unsigned int index() const { return snd_ctl_elem_value_get_index(p); }
        inline int boolean(unsigned int idx) const { return snd_ctl_elem_value_get_boolean(p, idx); }
        inline long integer(unsigned int idx) const { return snd_ctl_elem_value_get_integer(p, idx); }
        inline long long integer64(unsigned int idx) const { return snd_ctl_elem_value_get_integer64(p, idx); }
        inline unsigned int enumerated(unsigned int idx) const { return snd_ctl_elem_value_get_enumerated(p, idx); }
        inline unsigned char byte(unsigned int idx) const { return snd_ctl_elem_value_get_byte(p, idx); }
        inline const void * bytes() const { return snd_ctl_elem_value_get_bytes(p); }
        inline void iec958(snd_aes_iec958_t *ptr) const { return snd_ctl_elem_value_get_iec958(p, ptr); }

        inline void setId(const snd_ctl_elem_id_t *ptr) { snd_ctl_elem_value_set_id(p, ptr); }
        inline void setNumid(unsigned int val) { snd_ctl_elem_value_set_numid(p, val); }
        inline void setInterface(snd_ctl_elem_iface_t val) { snd_ctl_elem_value_set_interface(p, val); }
        inline void setDevice(unsigned int val) { snd_ctl_elem_value_set_device(p, val); }
        inline void setSubdevice(unsigned int val) { snd_ctl_elem_value_set_subdevice(p, val); }
        inline void setName(const char *val) { snd_ctl_elem_value_set_name(p, val); }
        inline void setIndex(unsigned int val) { snd_ctl_elem_value_set_index(p, val); }
        inline void setBoolean(unsigned int idx, long val) { snd_ctl_elem_value_set_boolean(p, idx, val); }
        inline void setInteger(unsigned int idx, long val) { snd_ctl_elem_value_set_integer(p, idx, val); }
        inline void setInteger64(unsigned int idx, long long val) { snd_ctl_elem_value_set_integer64(p, idx, val); }
        inline void setEnumerated(unsigned int idx, unsigned int val) { snd_ctl_elem_value_set_enumerated(p, idx, val); }
        inline void setByte(unsigned int idx, unsigned char val) { snd_ctl_elem_value_set_byte(p, idx, val); }
        inline void setBytes(void *data, size_t size) { snd_ctl_elem_set_bytes(p, data, size); }
        inline void setIec958(const snd_aes_iec958_t *ptr) { snd_ctl_elem_value_set_iec958(p, ptr); }

    private:
        snd_ctl_elem_value_t *p;
        int *ref;
};

class AlsaElementInfo
{
    public:
        inline ~AlsaElementInfo() { if (0 == --*ref) { delete ref; snd_ctl_elem_info_free(p); } }
        inline AlsaElementInfo(const AlsaElementInfo &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaElementInfo &operator=(const AlsaElementInfo &rhs) { if (0 == --*ref) { delete ref; snd_ctl_elem_info_free(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }

        inline AlsaElementInfo() : p(0), ref(new int) { *ref = 1; snd_ctl_elem_info_malloc(&p); }
        inline operator snd_ctl_elem_info_t*() { return p; }
        inline operator const snd_ctl_elem_info_t*() const { return p; }

        inline snd_ctl_elem_type_t type() const { return snd_ctl_elem_info_get_type(p); }
        inline bool isReadable() const { return snd_ctl_elem_info_is_readable(p); }
        inline bool isWritable() const { return snd_ctl_elem_info_is_writable(p); }
        inline bool isVolatile() const { return snd_ctl_elem_info_is_volatile(p); }
        inline bool isInactive() const { return snd_ctl_elem_info_is_inactive(p); }
        inline bool isLocked() const { return snd_ctl_elem_info_is_locked(p); }
        inline bool isTlvReadable() const { return snd_ctl_elem_info_is_tlv_readable(p); }
        inline bool isTlvWritable() const { return snd_ctl_elem_info_is_tlv_writable(p); }
        inline bool isTlvCommandable() const { return snd_ctl_elem_info_is_tlv_commandable(p); }
        inline bool isOwner() const { return snd_ctl_elem_info_is_owner(p); }
        inline bool isUser() const { return snd_ctl_elem_info_is_user(p); }
        inline pid_t owner() const { return snd_ctl_elem_info_get_owner(p); }
        inline unsigned int count() const { return snd_ctl_elem_info_get_count(p); }
        inline long min() const { return snd_ctl_elem_info_get_min(p); }
        inline long max() const { return snd_ctl_elem_info_get_max(p); }
        inline long step() const { return snd_ctl_elem_info_get_step(p); }
        inline long long min64() const { return snd_ctl_elem_info_get_min64(p); }
        inline long long max64() const { return snd_ctl_elem_info_get_max64(p); }
        inline long long step64() const { return snd_ctl_elem_info_get_step64(p); }
        inline unsigned int items() const { return snd_ctl_elem_info_get_items(p); }
        inline const char *itemName() const { return snd_ctl_elem_info_get_item_name(p); }
        inline int dimensions() const { return snd_ctl_elem_info_get_dimensions(p); }
        inline int dimension(unsigned int idx) const { return snd_ctl_elem_info_get_dimension(p, idx); }
        inline void readId(AlsaElementId *ptr) const { return snd_ctl_elem_info_get_id(p, *ptr); }
        inline unsigned int numid() const { return snd_ctl_elem_info_get_numid(p); }
        inline snd_ctl_elem_iface_t interface() const { return snd_ctl_elem_info_get_interface(p); }
        inline unsigned int device() const { return snd_ctl_elem_info_get_device(p); }
        inline unsigned int subdevice() const { return snd_ctl_elem_info_get_subdevice(p); }
        inline const char *name() const { return snd_ctl_elem_info_get_name(p); }
        inline unsigned int index() const { return snd_ctl_elem_info_get_index(p); }

        inline void setItem(unsigned int val) { snd_ctl_elem_info_set_item(p, val); }
        inline void setId(const snd_ctl_elem_id_t *ptr) { snd_ctl_elem_info_set_id(p, ptr); }
        inline void setNumid(unsigned int val) { snd_ctl_elem_info_set_numid(p, val); }
        inline void setInterface(snd_ctl_elem_iface_t val) { snd_ctl_elem_info_set_interface(p, val); }
        inline void setDevice(unsigned int val) { snd_ctl_elem_info_set_device(p, val); }
        inline void setSubdevice(unsigned int val) { snd_ctl_elem_info_set_subdevice(p, val); }
        inline void setName(const char *val) { snd_ctl_elem_info_set_name(p, val); }
        inline void setIndex(unsigned int val) { snd_ctl_elem_info_set_index(p, val); }

    private:
        snd_ctl_elem_info_t *p;
        int *ref;
};

class AlsaEvent
{
    public:
        inline ~AlsaEvent() { if (0 == --*ref) { delete ref; snd_ctl_event_free(p); } }
        inline AlsaEvent(const AlsaEvent &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaEvent &operator=(const AlsaEvent &rhs) { if (0 == --*ref) { delete ref; snd_ctl_event_free(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }

        inline AlsaEvent() : p(0), ref(new int) { *ref = 1; snd_ctl_event_malloc(&p); }
        inline operator snd_ctl_event_t*() { return p; }
        inline operator const snd_ctl_event_t*() const { return p; }

        inline snd_ctl_event_type_t type() const { return snd_ctl_event_get_type(p); }
        inline unsigned int mask() const { return snd_ctl_event_elem_get_mask(p); }
        inline unsigned int numid() const { return snd_ctl_event_elem_get_numid(p); }
        inline void readElementId(AlsaElementId *ptr) const { return snd_ctl_event_elem_get_id(p, *ptr); }
        inline snd_ctl_elem_iface_t interface() const { return snd_ctl_event_elem_get_interface(p); }
        inline unsigned int device() const { return snd_ctl_event_elem_get_device(p); }
        inline unsigned int subdevice() const { return snd_ctl_event_elem_get_subdevice(p); }
        inline const char *name() const { return snd_ctl_event_elem_get_name(p); }

        // useless:
        inline unsigned int index() const { return snd_ctl_event_elem_get_index(p); }

    private:
        snd_ctl_event_t *p;
        int *ref;
};

class AlsaCardInfo
{
    public:
        inline ~AlsaCardInfo() { if (0 == --*ref) { delete ref; snd_ctl_card_info_free(p); } }
        inline AlsaCardInfo(const AlsaCardInfo &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaCardInfo &operator=(const AlsaCardInfo &rhs) { if (0 == --*ref) { delete ref; snd_ctl_card_info_free(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }

        inline AlsaCardInfo() : ref(new int) { *ref = 1; snd_ctl_card_info_malloc(&p); }
        inline operator snd_ctl_card_info_t*() { return p; }
        inline operator const snd_ctl_card_info_t*() const { return p; }

        inline int card() const { return snd_ctl_card_info_get_card(p); }
        inline const char *id() const { return snd_ctl_card_info_get_id(p); }
        inline const char *driver() const { return snd_ctl_card_info_get_driver(p); }
        inline const char *name() const { return snd_ctl_card_info_get_name(p); }
        inline const char *longname() const { return snd_ctl_card_info_get_longname(p); }
        inline const char *mixername() const { return snd_ctl_card_info_get_mixername(p); }
        inline const char *components() const { return snd_ctl_card_info_get_components(p); }
    private:
        snd_ctl_card_info_t *p;
        int *ref;
};

class AlsaElementList
{
    public:
        inline ~AlsaElementList() { if (0 == --*ref) { delete ref; snd_ctl_elem_list_free_space(p); snd_ctl_elem_list_free(p); } }
        inline AlsaElementList(const AlsaElementList &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaElementList &operator=(const AlsaElementList &rhs) { if (0 == --*ref) { delete ref; snd_ctl_elem_list_free_space(p); snd_ctl_elem_list_free(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }

        inline AlsaElementList() : ref(new int) { *ref = 1; snd_ctl_elem_list_malloc(&p); }
        inline operator snd_ctl_elem_list_t*() { return p; }
        inline operator const snd_ctl_elem_list_t*() const { return p; }

        inline unsigned int used() const { return snd_ctl_elem_list_get_used(p); }
        inline unsigned int count() const { return snd_ctl_elem_list_get_count(p); }
        inline unsigned int numid(unsigned int idx) const { return snd_ctl_elem_list_get_numid(p, idx); }
        inline snd_ctl_elem_iface_t interface(unsigned int idx) const { return snd_ctl_elem_list_get_interface(p, idx); }
        inline unsigned int device(unsigned int idx) const { return snd_ctl_elem_list_get_device(p, idx); }
        inline unsigned int subdevice(unsigned int idx) const { return snd_ctl_elem_list_get_subdevice(p, idx); }
        inline const char * name(unsigned int idx) const { return snd_ctl_elem_list_get_name(p, idx); }
        inline unsigned int index(unsigned int idx) const { return snd_ctl_elem_list_get_index(p, idx); }

        inline void         setOffset(unsigned int val) { snd_ctl_elem_list_set_offset(p, val); }
        inline void         readId(unsigned int idx, AlsaElementId *id) const { snd_ctl_elem_list_get_id(p, idx, *id); }

    private:
        snd_ctl_elem_list_t *p;
        int *ref;
};

class AlsaTlv
{
    public:
        QVector<unsigned int> data;

        static unsigned int size() { return 4096; } // in bytes
        AlsaTlv() : data(1024, 0) {} // size in 4bytes

        void parse();

        int convertToMillibel(int volume, const AlsaElementInfo &info) const;

    private:
        class DBInterface;
        class DBScale;
        class DBLinear;
        class DBRange;

        void parseTlv(unsigned int *tlv, unsigned int size);
        DBScale parsedBScale(unsigned int *tlv, unsigned int tlv_size);
        DBLinear parsedBLinear(unsigned int *tlv, unsigned int tlv_size);
        DBRange parsedBRange(unsigned int *tlv, unsigned int tlv_size);

        QList<DBInterface *> m_dbinterfaces;
};

class AlsaControl
{
    public:
        inline AlsaControl(const char *name, int mode = SND_CTL_NONBLOCK) : p(0), ref(new int) { *ref = 1; snd_ctl_open(&p, name, mode); Q_ASSERT(p); }
        inline AlsaControl(const char *name, int mode, snd_config_t *lconf) : p(0), ref(new int) { *ref = 1; snd_ctl_open_lconf(&p, name, mode, lconf); Q_ASSERT(p); }
        inline ~AlsaControl() { if (0 == --*ref) { delete ref; snd_ctl_close(p); } }
        inline AlsaControl(const AlsaControl &rhs) : p(rhs.p), ref(rhs.ref) { ++*ref; }
        inline AlsaControl &operator=(const AlsaControl &rhs) { if (0 == --*ref) { delete ref; snd_ctl_close(p); } p = rhs.p; ref = rhs.ref; ++*ref; return *this; }
        inline operator snd_ctl_t*() { return p; }
        inline operator const snd_ctl_t*() const { return p; }

        inline bool setNonBlocking(bool nonBlocking) { return snd_ctl_nonblock(p, nonBlocking ? 1 : 0); }
        inline int pollDescriptorCount() { return snd_ctl_poll_descriptors_count(p); }
        inline QList<int> pollDescriptors() {
            const int npfds = snd_ctl_poll_descriptors_count(p); Q_ASSERT(npfds > 0 && npfds < 16);
            struct pollfd *pfd = reinterpret_cast<struct pollfd *>(alloca(sizeof(struct pollfd) * npfds));
            int err = snd_ctl_poll_descriptors(p, pfd, npfds); Q_ASSERT(err > 0);
            QList<int> ret; for (int i = 0; i < npfds; ++i) ret << pfd[i].fd;
            return ret;
        }
        inline void subscribeEvents(bool subscribe) { snd_ctl_subscribe_events(p, subscribe ? 1 : 0); }
        inline int readEvent(AlsaEvent *e) const { return snd_ctl_read(p, *e); }
        inline int readCardInfo(AlsaCardInfo *info) const { return snd_ctl_card_info(p, *info); }
        inline int readElementInfo(AlsaElementInfo *i) const { return snd_ctl_elem_info(p, *i); }
        inline int readElementInfo(AlsaElementInfo *i, const AlsaElementId &id) const { i->setId(id); return snd_ctl_elem_info(p, *i); }
        inline int readElementList(AlsaElementList *list) const { snd_ctl_elem_list_free_space(*list); int err; if (0 == (err = snd_ctl_elem_list(p, *list))) { snd_ctl_elem_list_alloc_space(*list, list->count()); return snd_ctl_elem_list(p, *list); } return err; }
        inline int readElementValue(AlsaElementValue *v) const { return snd_ctl_elem_read(p, *v); }
        inline int readElementValue(AlsaElementValue *v, const AlsaElementId &id) const { v->setId(id); return snd_ctl_elem_read(p, *v); }
        inline int writeElementValue(const AlsaElementValue &v) { return snd_ctl_elem_write(p, const_cast<AlsaElementValue &>(v)); }

        inline int lockElement(const AlsaElementId &id) { return snd_ctl_elem_lock(p, const_cast<AlsaElementId &>(id)); }
        inline int unlockElement(const AlsaElementId &id) { return snd_ctl_elem_unlock(p, const_cast<AlsaElementId &>(id)); }

        inline int addIntElement(const AlsaElementId &id, unsigned int count, long min, long max, long step) { return snd_ctl_elem_add_integer(p, id, count, min, max, step); }
        inline int addInt64Element(const AlsaElementId &id, unsigned int count, long long min, long long max, long long step) { return snd_ctl_elem_add_integer64(p, id, count, min, max, step); }
        inline int addBoolElement(const AlsaElementId &id, unsigned int count) { return snd_ctl_elem_add_boolean(p, id, count); }
        inline int addIec958Element(const AlsaElementId &id) { return snd_ctl_elem_add_iec958(p, id); }
        inline int removeElement(AlsaElementId id) { return snd_ctl_elem_remove(p, id); }

        AlsaTlv readTlv(const AlsaElementId &id) { AlsaTlv r; snd_ctl_elem_tlv_read(p, id, r.data.data(), AlsaTlv::size()); return r; }
        void writeTlv(const AlsaElementId &id, const AlsaTlv &tlv) { snd_ctl_elem_tlv_write(p, id, tlv.data.data()); }
        void tlvCommand(const AlsaElementId &id, const AlsaTlv &tlv) { snd_ctl_elem_tlv_command(p, id, tlv.data.data()); }

        //int snd_ctl_pcm_next_device(snd_ctl_t *ctl, int *device);
        //int snd_ctl_pcm_info(snd_ctl_t *ctl, snd_pcm_info_t * info);
        //int snd_ctl_pcm_prefer_subdevice(snd_ctl_t *ctl, int subdev);

        enum PowerState {
            D0PowerState = SND_CTL_POWER_D0,
            D1PowerState = SND_CTL_POWER_D1,
            D2PowerState = SND_CTL_POWER_D2,
            D3PowerState = SND_CTL_POWER_D3,
            D3HotPowerState = SND_CTL_POWER_D3hot,
            D3ColdPowerState = SND_CTL_POWER_D3cold
        };
        inline void setPowerState(PowerState state) { snd_ctl_set_power_state(p, state); }
        inline PowerState powerState() const { unsigned int state; snd_ctl_get_power_state(p, &state); return static_cast<PowerState>(state); }

    private:
        snd_ctl_t *p;
        int *ref;
};

namespace AlsaCard
{
    inline int first() { int c = -1; snd_card_next(&c); return c; }
    inline int next(int c) { snd_card_next(&c); return c; }
    inline int numberFor(const QByteArray string) { return snd_card_get_index(string.constData()); }
    inline QByteArray nameFor(int c) { char *s; if (0 == snd_card_get_name(c, &s)) { QByteArray r(s); free(s); return r; } return QByteArray(); }
    inline QByteArray longnameFor(int c) { char *s; if (0 == snd_card_get_longname(c, &s)) { QByteArray r(s); free(s); return r; } return QByteArray(); }
} // namespace AlsaCard

inline QDebug operator<<(QDebug s, AlsaEvent e)
{
    AlsaElementId eId;
    e.readElementId(&eId);

    s.nospace() << "AlsaEvent(" << e.type() << '|' << e.mask() << '|' << "|AlsaElementId(" << eId.numid() << '|' << eId.interface() << '|' << eId.device() << '|' << eId.subdevice() << '|' << eId.name() << '|' << eId.index() << "))";
    return s.space();
}

#endif // HAS_LIBASOUND

} // namespace Mixer
} // namespace Phonon
#endif // PHONON_ALSACLASSES_H
