#include "alsadevice.h"
#include <algorithm>
#include <cmath>

AlsaDevice::AlsaDevice( Category cat, int cardIndex, snd_mixer_elem_t *elem,
                         const TQString &cardName, TQObject *parent )
    : TQObject(parent), m_category(cat), m_type(Slider), m_cardIndex(cardIndex),
      m_elem(elem), m_cardName(cardName),
      m_stereo(false), m_hasMute(false), m_volMin(0), m_volMax(0),
      m_volume(0), m_muted(false), m_pan(0), m_enumId(0)
{
    m_name = TQString::fromUtf8( snd_mixer_selem_get_name(elem) );

    if ( snd_mixer_selem_is_enumerated(elem) ) {
        m_type = Enum;
        int n = snd_mixer_selem_get_enum_items(elem);
        for ( int i = 0; i < n; ++i ) {
            char buf[128];
            if ( snd_mixer_selem_get_enum_item_name(elem, i, sizeof(buf), buf) >= 0 )
                m_enumValues.append( TQString::fromUtf8(buf) );
        }
    } else if ( snd_mixer_selem_has_playback_volume(elem) ||
                snd_mixer_selem_has_capture_volume(elem) ) {
        m_type = Slider;
        if ( cat == Output ) {
            m_stereo  = snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT);
            m_hasMute = snd_mixer_selem_has_playback_switch(elem);
            snd_mixer_selem_get_playback_volume_range(elem, &m_volMin, &m_volMax);
        } else {
            m_stereo  = snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT);
            m_hasMute = snd_mixer_selem_has_capture_switch(elem);
            snd_mixer_selem_get_capture_volume_range(elem, &m_volMin, &m_volMax);
        }
    } else {
        m_type    = Switch;
        m_hasMute = true;
    }

    snd_mixer_elem_set_callback(elem, elemCallback);
    snd_mixer_elem_set_callback_private(elem, this);

    syncFromAlsa();
}

int AlsaDevice::elemCallback( snd_mixer_elem_t *elem, unsigned int /*mask*/ )
{
    AlsaDevice *self = static_cast<AlsaDevice*>( snd_mixer_elem_get_callback_private(elem) );
    if ( self ) self->syncFromAlsa();
    return 0;
}

void AlsaDevice::syncFromAlsa()
{
    if ( m_type == Enum ) {
        unsigned int idx = 0;
        snd_mixer_selem_get_enum_item(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &idx);
        int newId = (int)idx;
        if ( newId != m_enumId ) { m_enumId = newId; emit enumChanged(m_enumId); }
        return;
    }

    if ( m_type == Switch ) {
        int sw = 1;
        if ( m_category == Output && snd_mixer_selem_has_playback_switch(m_elem) )
            snd_mixer_selem_get_playback_switch(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
        else if ( m_category == Input && snd_mixer_selem_has_capture_switch(m_elem) )
            snd_mixer_selem_get_capture_switch(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
        else if ( snd_mixer_selem_has_playback_switch(m_elem) )
            snd_mixer_selem_get_playback_switch(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
        bool newMute = (sw == 0);
        if ( newMute != m_muted ) { m_muted = newMute; emit muteChanged(m_muted); }
        return;
    }

    // Slider
    long range = m_volMax - m_volMin;
    if ( range <= 0 ) return;

    long l = m_volMin, r = m_volMin;
    int  sw = 1;

    if ( m_category == Output ) {
        snd_mixer_selem_get_playback_volume(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &l);
        if ( m_stereo )
            snd_mixer_selem_get_playback_volume(m_elem, SND_MIXER_SCHN_FRONT_RIGHT, &r);
        else
            r = l;
        if ( m_hasMute )
            snd_mixer_selem_get_playback_switch(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
    } else {
        snd_mixer_selem_get_capture_volume(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &l);
        if ( m_stereo )
            snd_mixer_selem_get_capture_volume(m_elem, SND_MIXER_SCHN_FRONT_RIGHT, &r);
        else
            r = l;
        if ( m_hasMute )
            snd_mixer_selem_get_capture_switch(m_elem, SND_MIXER_SCHN_FRONT_LEFT, &sw);
    }

    long lv = l - m_volMin, rv = r - m_volMin;
    long mx = std::max(lv, rv);

    int  newVol  = (int)std::lround( (double)mx / range * 100.0 );
    bool newMute = (sw == 0);
    int  newPan  = 0;
    if ( m_stereo && mx > 0 ) {
        if ( lv >= rv )
            newPan = (int)std::lround( -50.0 * (1.0 - (double)rv / lv) );
        else
            newPan = (int)std::lround(  50.0 * (1.0 - (double)lv / rv) );
    }

    if ( newVol  != m_volume ) { m_volume = newVol;  emit volumeChanged(m_volume); }
    if ( newMute != m_muted  ) { m_muted  = newMute; emit muteChanged(m_muted); }
    if ( newPan  != m_pan    ) { m_pan    = newPan;  emit panChanged(m_pan); }
}

long AlsaDevice::toAlsaVol( int pct ) const
{
    if ( pct <= 0 )   return m_volMin;
    if ( pct >= 100 ) return m_volMax;
    return m_volMin + (long)std::lround( (double)pct / 100.0 * (m_volMax - m_volMin) );
}

void AlsaDevice::pushVolume()
{
    long vol = toAlsaVol( m_volume );
    double lf = m_pan >= 0 ? (50.0 - m_pan) / 50.0 : 1.0;
    double rf = m_pan <= 0 ? (50.0 + m_pan) / 50.0 : 1.0;
    long lv = (long)(vol * lf + 0.5);
    long rv = (long)(vol * rf + 0.5);

    if ( m_category == Output ) {
        snd_mixer_selem_set_playback_volume(m_elem, SND_MIXER_SCHN_FRONT_LEFT, lv);
        if ( m_stereo )
            snd_mixer_selem_set_playback_volume(m_elem, SND_MIXER_SCHN_FRONT_RIGHT, rv);
    } else {
        snd_mixer_selem_set_capture_volume(m_elem, SND_MIXER_SCHN_FRONT_LEFT, lv);
        if ( m_stereo )
            snd_mixer_selem_set_capture_volume(m_elem, SND_MIXER_SCHN_FRONT_RIGHT, rv);
    }
}

void AlsaDevice::setVolume( int v )
{
    v = v < 0 ? 0 : v > 100 ? 100 : v;
    m_volume = v;
    emit volumeChanged(v);
    pushVolume();
}

void AlsaDevice::setPan( int p )
{
    p = p < -50 ? -50 : p > 50 ? 50 : p;
    m_pan = p;
    emit panChanged(p);
    pushVolume();
}

void AlsaDevice::setMuted( bool m )
{
    m_muted = m;
    int sw = m ? 0 : 1;
    if ( m_type == Switch || m_type == Slider ) {
        if ( snd_mixer_selem_has_playback_switch(m_elem) )
            snd_mixer_selem_set_playback_switch_all(m_elem, sw);
        if ( snd_mixer_selem_has_capture_switch(m_elem) )
            snd_mixer_selem_set_capture_switch_all(m_elem, sw);
    }
    emit muteChanged(m);
}

void AlsaDevice::setEnumId( int id )
{
    if ( m_type != Enum ) return;
    if ( id < 0 || id >= (int)m_enumValues.count() ) return;
    m_enumId = id;
    emit enumChanged(id);
    for ( int ch = 0; ch <= SND_MIXER_SCHN_LAST; ++ch ) {
        snd_mixer_selem_channel_id_t chId = (snd_mixer_selem_channel_id_t)ch;
        if ( snd_mixer_selem_has_playback_channel(m_elem, chId) ||
             snd_mixer_selem_has_capture_channel(m_elem, chId) )
            snd_mixer_selem_set_enum_item(m_elem, chId, (unsigned int)id);
    }
}

#include "alsadevice.moc"
