#include "pulsedevice.h"
#include "pulsemodel.h"
#include "../ui/devicewidget.h"

#include <tqapplication.h>
#include <string.h>

// ---- peak-level event (PA thread → main thread) -----------------------------

static const int PA_LEVEL_EVENT = TQEvent::User + 2;

struct PALevelEvent : public TQCustomEvent {
    float level;
    PALevelEvent( float l ) : TQCustomEvent(PA_LEVEL_EVENT), level(l) {}
};

// ---- ctor / dtor ------------------------------------------------------------

PulseDevice::PulseDevice( AudioDevice::Category cat, uint32_t paIndex, TQObject *parent )
    : AudioDevice(parent),
      m_category(cat), m_paIndex(paIndex), m_sinkIndex(PA_INVALID_INDEX),
      m_volume(0), m_muted(false), m_pan(0), m_channels(2),
      m_context(0), m_mainloop(0),
      m_monitorStream(0), m_model(0), m_recordingCount(0)
{
}

PulseDevice::~PulseDevice()
{
    // detach() should have been called by PulseModel before destruction;
    // this is a safety net for cases where it wasn't.
    if ( m_monitorStream ) {
        pa_stream_set_read_callback( m_monitorStream, 0, 0 );
        pa_stream_unref( m_monitorStream );
        m_monitorStream = 0;
    }
}

// ---- context wiring ---------------------------------------------------------

void PulseDevice::setPAContext( pa_context *ctx, pa_threaded_mainloop *mainloop )
{
    m_context  = ctx;
    m_mainloop = mainloop;
    startMonitoring();
}

void PulseDevice::detach( bool paAlreadyDead )
{
    if ( m_monitorStream ) {
        if ( paAlreadyDead ) {
            pa_stream_set_read_callback( m_monitorStream, 0, 0 );
            pa_stream_unref( m_monitorStream );
        } else if ( m_mainloop ) {
            pa_threaded_mainloop_lock( m_mainloop );
            pa_stream_set_read_callback( m_monitorStream, 0, 0 );
            pa_stream_disconnect( m_monitorStream );
            pa_stream_unref( m_monitorStream );
            pa_threaded_mainloop_unlock( m_mainloop );
        }
        m_monitorStream = 0;
    }
    m_context  = 0;
    m_mainloop = 0;
}

// ---- update -----------------------------------------------------------------

void PulseDevice::update( const TQString &name, int volume, bool muted,
                          uint8_t channels, int pan,
                          const TQString &monitorName, const TQString &iconName )
{
    bool nameChange   = ( name   != m_name   );
    bool volumeChange = ( volume != m_volume );
    bool muteChange   = ( muted  != m_muted  );
    bool panChange    = ( pan    != m_pan    );

    m_name     = name;
    m_iconName = iconName;
    m_volume   = volume;
    m_muted    = muted;
    m_channels = channels ? channels : 2;
    m_pan      = pan;

    if ( !monitorName.isEmpty() && monitorName != m_monitorName ) {
        if ( m_monitorStream && m_mainloop ) {
            pa_threaded_mainloop_lock( m_mainloop );
            pa_stream_set_read_callback( m_monitorStream, 0, 0 );
            pa_stream_disconnect( m_monitorStream );
            pa_stream_unref( m_monitorStream );
            pa_threaded_mainloop_unlock( m_mainloop );
            m_monitorStream = 0;
        }
        m_monitorName = monitorName;
        if ( m_context ) startMonitoring();
    }

    if ( nameChange )   emit nameChanged( m_name );
    if ( volumeChange ) emit volumeChanged( m_volume );
    if ( muteChange )   emit muteChanged( m_muted );
    if ( panChange )    emit panChanged( m_pan );
}

// ---- peak monitoring --------------------------------------------------------

void PulseDevice::pauseMonitoring()
{
    if ( !m_monitorStream || !m_mainloop ) return;
    pa_threaded_mainloop_lock( m_mainloop );
    pa_stream_set_read_callback( m_monitorStream, 0, 0 );
    pa_threaded_mainloop_unlock( m_mainloop );
}

void PulseDevice::resumeMonitoring()
{
    if ( !m_monitorStream || !m_mainloop ) return;
    pa_threaded_mainloop_lock( m_mainloop );
    const void *data; size_t nbytes;
    while ( pa_stream_peek( m_monitorStream, &data, &nbytes ) == 0 && nbytes > 0 )
        pa_stream_drop( m_monitorStream );
    pa_stream_set_read_callback( m_monitorStream, monitorReadCb, this );
    pa_threaded_mainloop_unlock( m_mainloop );
}

void PulseDevice::startMonitoring()
{
    if ( !m_context || !m_mainloop || m_monitorName.isEmpty() || m_monitorStream )
        return;

    pa_sample_spec spec;
    spec.format   = PA_SAMPLE_FLOAT32NE;
    spec.rate     = 20;
    spec.channels = 1;

    pa_threaded_mainloop_lock( m_mainloop );

    pa_proplist *props = pa_proplist_new();
    pa_proplist_sets( props, PA_PROP_MEDIA_ROLE, "abstract" );
    m_monitorStream = pa_stream_new_with_proplist( m_context, "tmix-peak", &spec, 0, props );
    pa_proplist_free( props );
    if ( m_monitorStream ) {
        if ( !m_model || m_model->levelsActive() )
            pa_stream_set_read_callback( m_monitorStream, monitorReadCb, this );

        // For sink inputs, filter peak detection to just this stream.
        // Source outputs don't support pa_stream_set_monitor_stream; they connect to the source directly.
        if ( m_category == Playback )
            pa_stream_set_monitor_stream( m_monitorStream, m_paIndex );

        pa_buffer_attr attr;
        memset( &attr, 0xff, sizeof(attr) );
        attr.fragsize  = sizeof(float);
        attr.maxlength = sizeof(float) * 8;

        pa_stream_connect_record( m_monitorStream,
            m_monitorName.utf8().data(), &attr,
            (pa_stream_flags_t)(PA_STREAM_PEAK_DETECT |
                                PA_STREAM_ADJUST_LATENCY |
                                PA_STREAM_DONT_MOVE) );
    }

    pa_threaded_mainloop_unlock( m_mainloop );
}

void PulseDevice::monitorReadCb( pa_stream *s, size_t /*nbytes*/, void *userdata )
{
    PulseDevice *self = static_cast<PulseDevice*>(userdata);
    const void *data = 0;
    size_t len = 0;

    if ( pa_stream_peek( s, &data, &len ) < 0 ) return;

    float level = 0.0f;
    if ( data && len >= sizeof(float) )
        level = *static_cast<const float*>(data);
    if ( level < 0.0f ) level = 0.0f;
    if ( level > 1.0f ) level = 1.0f;

    pa_stream_drop( s );

    TQApplication::postEvent( self, new PALevelEvent(level) );
}

void PulseDevice::customEvent( TQCustomEvent *e )
{
    if ( e->type() == PA_LEVEL_EVENT )
        emit levelChanged( static_cast<PALevelEvent*>(e)->level );
}

void PulseDevice::adjustRecordingCount( int delta )
{
    bool wasBefore = ( m_recordingCount > 0 );
    m_recordingCount += delta;
    if ( m_recordingCount < 0 ) m_recordingCount = 0;
    bool isNow = ( m_recordingCount > 0 );
    if ( isNow != wasBefore )
        emit recordingActiveChanged( isNow );
}

// ---- helpers ----------------------------------------------------------------

static pa_volume_t percentToPA( int pct )
{
    if ( pct <= 0 )   return 0;
    if ( pct >= 100 ) return PA_VOLUME_NORM;
    return (pa_volume_t)( (double)pct / 100.0 * PA_VOLUME_NORM + 0.5 );
}

static pa_cvolume buildCVolume( uint8_t channels, pa_volume_t vol, int pan )
{
    pa_cvolume cv;
    cv.channels = channels;
    if ( channels >= 2 ) {
        double lf = ( pan >= 0 ) ? ( 50.0 - pan ) / 50.0 : 1.0;
        double rf = ( pan <= 0 ) ? ( 50.0 + pan ) / 50.0 : 1.0;
        cv.values[0] = (pa_volume_t)( vol * lf );
        cv.values[1] = (pa_volume_t)( vol * rf );
        for ( uint8_t i = 2; i < channels; i++ )
            cv.values[i] = vol;
    } else {
        pa_cvolume_set( &cv, channels, vol );
    }
    return cv;
}

// ---- write back to PA -------------------------------------------------------

void PulseDevice::pushCVolume( const pa_cvolume &cv )
{
    pa_threaded_mainloop_lock( m_mainloop );
    pa_operation *op = 0;
    switch ( m_category ) {
        case Output:    op = pa_context_set_sink_volume_by_index( m_context, m_paIndex, &cv, 0, 0 );   break;
        case Input:     op = pa_context_set_source_volume_by_index( m_context, m_paIndex, &cv, 0, 0 ); break;
        case Playback:  op = pa_context_set_sink_input_volume( m_context, m_paIndex, &cv, 0, 0 );      break;
        case Recording: op = pa_context_set_source_output_volume( m_context, m_paIndex, &cv, 0, 0 );   break;
    }
    if ( op ) pa_operation_unref( op );
    pa_threaded_mainloop_unlock( m_mainloop );
}

void PulseDevice::setVolume( int v )
{
    if ( !m_context ) return;
    v = v < 0 ? 0 : v > 100 ? 100 : v;
    m_volume = v;
    emit volumeChanged( v );
    pushCVolume( buildCVolume( m_channels, percentToPA(v), m_pan ) );
}

void PulseDevice::setPan( int p )
{
    if ( !m_context ) return;
    p = p < -50 ? -50 : p > 50 ? 50 : p;
    m_pan = p;
    emit panChanged( p );
    pushCVolume( buildCVolume( m_channels, percentToPA(m_volume), p ) );
}

void PulseDevice::setMuted( bool m )
{
    if ( !m_context ) return;
    pa_threaded_mainloop_lock( m_mainloop );
    pa_operation *op = 0;
    switch ( m_category ) {
        case Output:    op = pa_context_set_sink_mute_by_index( m_context, m_paIndex, m, 0, 0 );    break;
        case Input:     op = pa_context_set_source_mute_by_index( m_context, m_paIndex, m, 0, 0 );  break;
        case Playback:  op = pa_context_set_sink_input_mute( m_context, m_paIndex, m, 0, 0 );       break;
        case Recording: op = pa_context_set_source_output_mute( m_context, m_paIndex, m, 0, 0 );    break;
    }
    if ( op ) pa_operation_unref( op );
    pa_threaded_mainloop_unlock( m_mainloop );
}

TQWidget *PulseDevice::createWidget( TQWidget *parent )
{
    return new DeviceWidget( this, m_model, parent );
}

#include "pulsedevice.moc"
