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

class AlsaError
{
    public:
        AlsaError(int code);
        int errorCode() const;
        const char *errorString() const;

    private:
        const int m_code;
};

static inline void alsaErrorCheck(int err)
{
    if (err < 0) {
        throw AlsaError(err);
    }
}

#define ALSA_CALL(name, func) inline void name { const int err = func; alsaErrorCheck(err); }
#define ALSA_SETTER1(name, func, type1) ALSA_CALL(name(type1 a1), func(d->p, a1))
#define ALSA_SETTER2(name, func, type1, type2) ALSA_CALL(name(type1 a1, type2 a2), func(d->p, a1, a2))
#define ALSA_SETTER3(name, func, type1, type2, type3) ALSA_CALL(name(type1 a1, type2 a2, type3 a3), func(d->p, a1, a2, a3))
#define ALSA_GETTER(rettype, name, func) inline rettype name() const { return func(d->p); }
#define ALSA_GETTER_X(rettype, name, func, onerror) inline rettype name() const { rettype x; const int err = func(d->p, &x); alsaErrorCheck(err); return x; }
#define ALSA_GETTER_X_NOERR(rettype, name, func) inline rettype name() const { rettype x; func(d->p, &x); return x; }
#define ALSA_CAST_OPERATORS(type) \
    public: \
        inline operator type*() { return d->p; } \
        inline operator const type*() const { return d->p; }
#define ALSA_AMPERSAND_OPERATORS(type) \
    private: /* those are too evil to be public */ \
        inline type *operator&() { return d->p; } \
        inline const type *operator&() const { return d->p; }
#define ALSA_SIMPLE_DATA(Class, type) \
namespace { \
    struct Class##Data : public AlsaData \
    { \
        Class##Data() { type##_malloc(&p); } \
        ~Class##Data() { type##_free(p); } \
        type##_t *p; \
    }; \
}

namespace
{
    struct AlsaData
    {
        AlsaData() : ref(0) {}
        QAtomicInt ref;
    };

    template<class Data>
    class AlsaBase
    {
        public:
            inline ~AlsaBase() { if (!d->ref.deref()) delete d; }
            inline AlsaBase() : d(new Data) { d->ref.ref(); }
            inline AlsaBase(const AlsaBase<Data> &rhs) : d(rhs.d) { d->ref.ref(); }
            inline AlsaBase<Data> &operator=(const AlsaBase<Data> &rhs) { if (d != rhs.d) { if (!d->ref.deref()) delete d; d = rhs.d; d->ref.ref(); } return *this; }

        protected:
            Data *d;
    };

    struct AlsaPcmData : public AlsaData
    {
        AlsaPcmData() : p(0) {}
        ~AlsaPcmData() { if (p) snd_pcm_close(p); }
        snd_pcm_t *p;
    };
} // namespace

ALSA_SIMPLE_DATA(AlsaCardInfo, snd_ctl_card_info)
class AlsaCardInfo : public AlsaBase<AlsaCardInfoData>
{
    ALSA_CAST_OPERATORS(snd_ctl_card_info_t)
    public:
        ALSA_GETTER(int, card, snd_ctl_card_info_get_card)
        ALSA_GETTER(const char *, id, snd_ctl_card_info_get_id);
        ALSA_GETTER(const char *, driver, snd_ctl_card_info_get_driver)
        ALSA_GETTER(const char *, name, snd_ctl_card_info_get_name)
        ALSA_GETTER(const char *, longname, snd_ctl_card_info_get_longname)
        ALSA_GETTER(const char *, mixername, snd_ctl_card_info_get_mixername)
        ALSA_GETTER(const char *, components, snd_ctl_card_info_get_components)
};

ALSA_SIMPLE_DATA(AlsaPcmInfo, snd_pcm_info)
class AlsaPcmInfo : public AlsaBase<AlsaPcmInfoData>
{
    friend class AlsaPcm; // for operator&
    ALSA_CAST_OPERATORS(snd_pcm_info_t)
    ALSA_AMPERSAND_OPERATORS(snd_pcm_info_t)
    public:
        ALSA_GETTER(unsigned int, device, snd_pcm_info_get_device)
        ALSA_GETTER(unsigned int, subdevice, snd_pcm_info_get_subdevice)
        ALSA_GETTER(snd_pcm_stream_t, stream, snd_pcm_info_get_stream)
        ALSA_GETTER(int, card, snd_pcm_info_get_card)
        ALSA_GETTER(const char *,id, snd_pcm_info_get_id)
        ALSA_GETTER(const char *,name, snd_pcm_info_get_name)
        ALSA_GETTER(const char *,subdevice_name, snd_pcm_info_get_subdevice_name)
        ALSA_GETTER(snd_pcm_class_t, pcmClass, snd_pcm_info_get_class)
        ALSA_GETTER(snd_pcm_subclass_t, pcmSubclass, snd_pcm_info_get_subclass)
        ALSA_GETTER(unsigned int, subdevicesCount, snd_pcm_info_get_subdevices_count)
        ALSA_GETTER(unsigned int, subdevicesAvail, snd_pcm_info_get_subdevices_avail)
        ALSA_GETTER(snd_pcm_sync_id_t, sync, snd_pcm_info_get_sync)

        // the next three actually return void...
        inline void setDevice(unsigned int x) const { snd_pcm_info_set_device(d->p, x); }
        inline void setSubdevice(unsigned int x) const { snd_pcm_info_set_subdevice(d->p, x); }
        inline void setStream(snd_pcm_stream_t x) const { snd_pcm_info_set_stream(d->p, x); }
};

ALSA_SIMPLE_DATA(AlsaPcmStatus, snd_pcm_status)
class AlsaPcmStatus : public AlsaBase<AlsaPcmStatusData>
{
    friend class AlsaPcm;
    ALSA_CAST_OPERATORS(snd_pcm_status_t)
    ALSA_AMPERSAND_OPERATORS(snd_pcm_status_t)
    public:
        ALSA_GETTER(snd_pcm_state_t, state, snd_pcm_status_get_state)
        ALSA_GETTER_X_NOERR(snd_timestamp_t,  triggerTimestamp, snd_pcm_status_get_trigger_tstamp)
        ALSA_GETTER_X_NOERR(snd_htimestamp_t, triggerHiresTimestamp, snd_pcm_status_get_trigger_htstamp)
        ALSA_GETTER_X_NOERR(snd_timestamp_t,  timestamp, snd_pcm_status_get_tstamp)
        ALSA_GETTER_X_NOERR(snd_htimestamp_t, hiresTimestamp, snd_pcm_status_get_htstamp)
        ALSA_GETTER(snd_pcm_sframes_t, delay, snd_pcm_status_get_delay)
        ALSA_GETTER(snd_pcm_uframes_t, available, snd_pcm_status_get_avail)
        ALSA_GETTER(snd_pcm_uframes_t, availableMax, snd_pcm_status_get_avail_max)
        ALSA_GETTER(snd_pcm_uframes_t, overrange, snd_pcm_status_get_overrange)
};

class AlsaPcm : public AlsaBase<AlsaPcmData>
{
    ALSA_CAST_OPERATORS(snd_pcm_t)
    public:
        ALSA_CALL(open(const char *name, snd_pcm_stream_t stream, int mode), snd_pcm_open(&d->p, name, stream, mode))
        ALSA_CALL(close(), snd_pcm_close(d->p); d->p = 0)
        ALSA_GETTER(const char *, name, snd_pcm_name)
        ALSA_GETTER(snd_pcm_type_t, type, snd_pcm_type)
        ALSA_GETTER(snd_pcm_stream_t, stream, snd_pcm_stream)
        inline int pollDescriptors(struct pollfd *pfds, unsigned int space) { return snd_pcm_poll_descriptors(d->p, pfds, space); }
        ALSA_GETTER(int, pollDescriptorsCount, snd_pcm_poll_descriptors_count)
        ALSA_CALL(pollDescriptorsRevents(struct pollfd *pfds, unsigned int nfds, unsigned short *revents), snd_pcm_poll_descriptors_revents(d->p, pfds, nfds, revents))
        ALSA_SETTER1(nonblock, snd_pcm_nonblock, bool)
        ALSA_GETTER_X(AlsaPcmInfo, info, snd_pcm_info, x)
        //inline AlsaPcmHwParams hwParams() { AlsaPcmHwParams params(*this); d->err = snd_pcm_hw_params_current(d->p, params); return params; }
        //inline AlsaPcmSwParams swParams() { AlsaPcmSwParams params(*this); d->err = snd_pcm_sw_params_current(d->p, params); return params; }
        ALSA_CALL(hwFree(), snd_pcm_hw_free(d->p))
        ALSA_CALL(prepare(), snd_pcm_prepare(d->p))
        ALSA_CALL(reset(), snd_pcm_reset(d->p))
        ALSA_CALL(start(), snd_pcm_start(d->p))
        ALSA_CALL(drop(), snd_pcm_drop(d->p))
        ALSA_CALL(drain(), snd_pcm_drain(d->p))
        ALSA_GETTER_X(AlsaPcmStatus, status, snd_pcm_status, x)
        ALSA_SETTER1(pause, snd_pcm_pause, bool)
        ALSA_GETTER(snd_pcm_state_t, state, snd_pcm_state)
        ALSA_CALL(hwsync(), snd_pcm_hwsync(d->p))
        ALSA_GETTER_X(snd_pcm_sframes_t, delay, snd_pcm_delay, 0)
        ALSA_CALL(resume(), snd_pcm_resume(d->p))
        inline snd_pcm_sframes_t availableUpdate() { const snd_pcm_sframes_t r = snd_pcm_avail_update(d->p); alsaErrorCheck(r); return r; }
        inline snd_pcm_sframes_t rewind(snd_pcm_uframes_t frames) { const snd_pcm_sframes_t r = snd_pcm_rewind(d->p, frames); alsaErrorCheck(r); return r; }
        inline snd_pcm_sframes_t forward(snd_pcm_uframes_t frames) { const snd_pcm_sframes_t r = snd_pcm_forward(d->p, frames); alsaErrorCheck(r); return r; }
        inline snd_pcm_sframes_t writei(const void *buffer, snd_pcm_uframes_t size) { const snd_pcm_sframes_t r = snd_pcm_writei(d->p, buffer, size); alsaErrorCheck(r); return r; }
        inline snd_pcm_sframes_t readi(void *buffer, snd_pcm_uframes_t size) { const snd_pcm_sframes_t r = snd_pcm_readi(d->p, buffer, size); alsaErrorCheck(r); return r; }
        inline snd_pcm_sframes_t writen(void **bufs, snd_pcm_uframes_t size) { const snd_pcm_sframes_t r = snd_pcm_writen(d->p, bufs, size); alsaErrorCheck(r); return r; }
        inline snd_pcm_sframes_t readn(void **bufs, snd_pcm_uframes_t size) { const snd_pcm_sframes_t r = snd_pcm_readn(d->p, bufs, size); alsaErrorCheck(r); return r; }
        ALSA_SETTER1(wait, snd_pcm_wait, int)
        ALSA_SETTER1(link, snd_pcm_link, AlsaPcm &)
        ALSA_CALL(unlink(), snd_pcm_unlink(d->p))
        //XXX better not use recover - it can poll(0, 0, 1000) == sleep for 1s. Use prepare/resume instead
        //inline int recover(int d->err, int silent) { return (d->err = snd_pcm_recover(d->p, d->err, silent)); }
        ALSA_CALL(setParams(snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, bool soft_resample, unsigned int latency),
                snd_pcm_set_params(d->p, format, access, channels, rate, soft_resample ? 1 : 0, latency))
        ALSA_CALL(getParams(snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t *period_size),
                snd_pcm_get_params(d->p, buffer_size, period_size))

        ALSA_SETTER3(mmapBegin, snd_pcm_mmap_begin, const snd_pcm_channel_area_t **, snd_pcm_uframes_t *, snd_pcm_uframes_t *)
        inline snd_pcm_sframes_t mmapCommit(snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { const snd_pcm_sframes_t r = snd_pcm_mmap_commit(d->p, offset, frames); alsaErrorCheck(r); return r; }
};

#undef ALSA_SETTER1
#undef ALSA_SETTER2
#undef ALSA_GETTER
#undef ALSA_GETTER_X
#undef ALSA_GETTER_X_NOERR
#define ALSA_SUPPORTS1(name, func, type1) inline bool name(type1 a1) { return 0 == func(d->pcm, d->p, a1); }
#define ALSA_SUPPORTS2(name, func, type1, type2) inline bool name(type1 a1, type2 a2) { return 0 == func(d->pcm, d->p, a1, a2); }
#define ALSA_SETTER0(name, func) ALSA_CALL(name(), func(d->pcm, d->p))
#define ALSA_SETTER1(name, func, type1) ALSA_CALL(name(type1 a1), func(d->pcm, d->p, a1))
#define ALSA_SETTER2(name, func, type1, type2) ALSA_CALL(name(type1 a1, type2 a2), func(d->pcm, d->p, a1, a2))
#define ALSA_GETTER(rettype, name, func) inline rettype name() const { return func(d->pcm, d->p); }
#define ALSA_GETTER_X(rettype, name, func) inline rettype name() const { rettype x; const int err = func(d->p, &x); alsaErrorCheck(err); return x; }
#define ALSA_GETTER_X_NOERR(rettype, name, func) inline rettype name() const { rettype x; func(d->p, &x); return x; }

namespace
{
    struct AlsaPcmHwParamsData : public AlsaData
    {
        AlsaPcmHwParamsData() { snd_pcm_hw_params_malloc(&p); }
        ~AlsaPcmHwParamsData() { snd_pcm_hw_params_free(p); }
        AlsaPcm pcm;
        snd_pcm_hw_params_t *p;
    };
} // namespace

class AlsaPcmHwParams : public AlsaBase<AlsaPcmHwParamsData>
{
    ALSA_CAST_OPERATORS(snd_pcm_hw_params_t)
    public:
        inline AlsaPcmHwParams(AlsaPcm &_pcm) { const int err = snd_pcm_hw_params_any(_pcm, d->p); alsaErrorCheck(err); d->pcm = _pcm; }

        ALSA_SUPPORTS1(supportsAccess, snd_pcm_hw_params_test_access, snd_pcm_access_t)
        ALSA_SETTER1(setAccess, snd_pcm_hw_params_set_access, snd_pcm_access_t)

        ALSA_SUPPORTS1(supportsFormat, snd_pcm_hw_params_test_format, snd_pcm_format_t)
        ALSA_SETTER1(setFormat, snd_pcm_hw_params_set_format, snd_pcm_format_t)

        ALSA_SUPPORTS2(supportsRate, snd_pcm_hw_params_test_rate, unsigned int, int)
        ALSA_SETTER2(setRate, snd_pcm_hw_params_set_rate, unsigned int, int)
        ALSA_SETTER2(setRateNear, snd_pcm_hw_params_set_rate_near, unsigned int *, int *)
        ALSA_SETTER2(setRateHighest, snd_pcm_hw_params_set_rate_last, unsigned int *, int *)
        ALSA_SETTER1(setRateResample, snd_pcm_hw_params_set_rate_resample, bool)

        ALSA_SUPPORTS1(supportsChannels, snd_pcm_hw_params_test_channels, unsigned int)
        ALSA_SETTER1(setChannels, snd_pcm_hw_params_set_channels, unsigned int)
        ALSA_SETTER1(setChannelsNear, snd_pcm_hw_params_set_channels_near, unsigned int *)
        ALSA_SETTER1(setChannelsHighest, snd_pcm_hw_params_set_channels_last, unsigned int *)

        ALSA_SETTER2(setBufferTimeNear, snd_pcm_hw_params_set_buffer_time_near, unsigned int *, int *)

        ALSA_SETTER0(setPeriodsInteger, snd_pcm_hw_params_set_periods_integer)
        ALSA_SUPPORTS2(supportsPeriodSize, snd_pcm_hw_params_test_period_size, snd_pcm_uframes_t, int)
        ALSA_SETTER2(setPeriodSize, snd_pcm_hw_params_set_period_size, snd_pcm_uframes_t, int)
#define ALSA_GETTER_X1(rettype, name, func, type1) inline rettype name(type1 a1) const { rettype x; const int err = func(d->p, &x, a1); alsaErrorCheck(err); return x; }
        ALSA_GETTER_X1(snd_pcm_uframes_t, periodSize, snd_pcm_hw_params_get_period_size, int *)

        ALSA_SUPPORTS2(supportsPeriods, snd_pcm_hw_params_test_periods, unsigned int, int)
        ALSA_SETTER2(setPeriodsMinimum, snd_pcm_hw_params_set_periods_min, unsigned int *, int *);
        ALSA_SETTER2(setPeriodsNear, snd_pcm_hw_params_set_periods_near, unsigned int *, int *);

        ALSA_SETTER1(setBufferSize, snd_pcm_hw_params_set_buffer_size, snd_pcm_uframes_t)
        ALSA_GETTER_X(snd_pcm_uframes_t, bufferSize, snd_pcm_hw_params_get_buffer_size)
        ALSA_GETTER_X1(unsigned int, bufferTime, snd_pcm_hw_params_get_buffer_time, int *)
        ALSA_SETTER2(setPeriodTimeNear, snd_pcm_hw_params_set_period_time_near, unsigned int *, int *)
        ALSA_SETTER1(setBufferSizeNear, snd_pcm_hw_params_set_buffer_size_near, snd_pcm_uframes_t *)

        ALSA_SETTER0(current, snd_pcm_hw_params_current)
        ALSA_SETTER0(apply, snd_pcm_hw_params)
};

namespace
{
    struct AlsaPcmSwParamsData : public AlsaData
    {
        AlsaPcmSwParamsData() { snd_pcm_sw_params_malloc(&p); }
        ~AlsaPcmSwParamsData() { snd_pcm_sw_params_free(p); }
        AlsaPcm pcm;
        snd_pcm_sw_params_t *p;
    };
} // namespace

class AlsaPcmSwParams : public AlsaBase<AlsaPcmSwParamsData>
{
    ALSA_CAST_OPERATORS(snd_pcm_sw_params_t)
    public:
        inline AlsaPcmSwParams(AlsaPcm &_pcm) { const int err = snd_pcm_sw_params_current(_pcm, d->p); alsaErrorCheck(err); d->pcm = _pcm; }

        ALSA_SETTER1(setStartThreshold, snd_pcm_sw_params_set_start_threshold, snd_pcm_uframes_t)
        ALSA_SETTER1(setStopThreshold, snd_pcm_sw_params_set_stop_threshold, snd_pcm_uframes_t)
        ALSA_SETTER1(setSilenceThreshold, snd_pcm_sw_params_set_silence_threshold, snd_pcm_uframes_t)
        ALSA_SETTER1(setAvailableMinimum, snd_pcm_sw_params_set_avail_min, snd_pcm_uframes_t)
        ALSA_SETTER1(setTransferAlignment, snd_pcm_sw_params_set_xfer_align, snd_pcm_uframes_t)

        ALSA_SETTER0(current, snd_pcm_sw_params_current)
        ALSA_SETTER0(apply, snd_pcm_sw_params)
};

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

//X inline QDebug operator<<(QDebug s, AlsaEvent e)
//X {
//X     AlsaElementId eId;
//X     e.readElementId(&eId);
//X 
//X     s.nospace() << "AlsaEvent(" << e.type() << '|' << e.mask() << '|' << "|AlsaElementId(" << eId.numid() << '|' << eId.interface() << '|' << eId.device() << '|' << eId.subdevice() << '|' << eId.name() << '|' << eId.index() << "))";
//X     return s.space();
//X }

#undef ALSA_CALL
#undef ALSA_SETTER0
#undef ALSA_SETTER1
#undef ALSA_SETTER2
#undef ALSA_GETTER
#undef ALSA_GETTER_X
#undef ALSA_GETTER_X_NOERR
#undef ALSA_CAST_OPERATORS
#undef ALSA_AMPERSAND_OPERATORS

#endif // HAS_LIBASOUND

} // namespace Phonon
#endif // PHONON_ALSACLASSES_H
