#include "devicewidget.h"
#include "kledbutton.h"
#include "levelmeter.h"
#include "../model/audiodevice.h"
#include "../model/pulsedevice.h"
#include "../model/pulsemodel.h"

#include <tqlayout.h>
#include <tqslider.h>
#include <tqlabel.h>
#include <tqframe.h>
#include <tqpainter.h>
#include <tqtooltip.h>
#include <tqevent.h>
#include "balanceknob.h"
#include <tqradiobutton.h>
#include <kled.h>
#include <kiconloader.h>
#include <tdeglobal.h>
#include <tdeconfig.h>
#include <tdestandarddirs.h>
#include <tdepopupmenu.h>
#include <tdelocale.h>

static TQPixmap kmixPic( const char *name )
{
    TQString path = TDEGlobal::dirs()->findResource( "data",
        TQString("tmix/pics/") + name );
    return path.isEmpty() ? TQPixmap() : TQPixmap( path );
}

static TQPixmap kmixIconForDevice( const TQString &paIconName, AudioDevice::Category cat )
{
    // Input devices always get the mic icon — overrides device name hints.
    if ( cat == AudioDevice::Input )
        return kmixPic("mix_microphone.png");

    // Map PA device icon name keywords to KMix pics before falling back to category.
    TQString n = paIconName.lower();
    if ( n.contains("headset") || n.contains("headphone") )
        return kmixPic("mix_headphone.png");
    if ( n.contains("microphone") || n.contains("mic") )
        return kmixPic("mix_microphone.png");
    if ( n.contains("digital") || n.contains("hdmi") || n.contains("iec") )
        return kmixPic("mix_digital.png");
    if ( n.contains("surround") )
        return kmixPic("mix_surround.png");
    if ( n.contains("cd") )
        return kmixPic("mix_cd.png");
    if ( n.contains("midi") )
        return kmixPic("mix_midi.png");
    if ( n.contains("video") )
        return kmixPic("mix_video.png");

    // Category fallback
    switch ( cat ) {
        case AudioDevice::Output:    return kmixPic("mix_volume.png");
        case AudioDevice::Input:     return kmixPic("mix_microphone.png");
        case AudioDevice::Playback:  return kmixPic("mix_audio.png");
        case AudioDevice::Recording: return kmixPic("mix_record.png");
    }
    return TQPixmap();
}

// Rotated label — text reads bottom-to-top. Mirrors KMix VerticalText.
class VerticalLabel : public TQWidget
{
public:
    VerticalLabel( const TQString &text, TQWidget *parent )
        : TQWidget(parent, text.utf8()), m_text(text)
    {
        resize( 20, 100 );
        setMinimumSize( 20, 10 );
    }

    void setText( const TQString &t ) { m_text = t; update(); }

    TQSize sizeHint() const { return TQSize( 20, 100 ); }

    TQSizePolicy sizePolicy() const
    {
        return TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Expanding );
    }

protected:
    void paintEvent( TQPaintEvent * )
    {
        TQPainter p( this );
        TQFontMetrics fm = p.fontMetrics();
        int available = height() - 6;
        TQString text = m_text;
        if ( fm.width(text) > available ) {
            while ( text.length() > 1 && fm.width(text) + fm.width("...") > available )
                text.truncate( text.length() - 1 );
            text += "...";
        }
        p.rotate( 270 );
        p.translate( 0, -4 );
        p.drawText( -height() + 2, width(), text );
    }

private:
    TQString m_text;
};


DeviceWidget::DeviceWidget( AudioDevice *device, PulseModel *model, TQWidget *parent )
    : TQWidget(parent), m_device(device), m_model(model),
      m_sep(0), m_recordingLed(0), m_defaultBtn(0)
{
    // ---- widgets -------------------------------------------------------------
    m_label = new VerticalLabel( device->name(), this );

    // App / device icon — 22px, loaded from TDE icon theme with KMix pic fallbacks.
    m_iconLabel = new TQLabel( this );
    m_iconLabel->setFixedSize( 22, 22 );
    m_iconLabel->setAlignment( TQLabel::AlignCenter );
    {
        TQString icon = device->iconName();
        TQPixmap px;
        if ( !icon.isEmpty() )
            px = TDEGlobal::iconLoader()->loadIcon(
                icon, TDEIcon::Small, 22,
                TDEIcon::DefaultState, 0, true /*canReturnNull*/ );
        if ( px.isNull() )
            px = kmixIconForDevice( icon, device->category() );
        if ( !px.isNull() )
            m_iconLabel->setPixmap( px );
    }

    m_volLabel = new TQLabel( "100%", this );
    m_volLabel->setFixedWidth( m_volLabel->sizeHint().width() + 2 );
    m_volLabel->setAlignment( TQLabel::AlignRight | TQLabel::AlignVCenter );
    m_volLabel->setText( TQString::number(device->volume()) + "%" );

    m_levelMeter = new LevelMeter( this );
    m_levelMeter->setFixedWidth( 14 );

    m_volSlider = new TQSlider( 0, 100, 5, 100 - device->volume(), TQt::Vertical, this );
    m_volSlider->setTickmarks( TQSlider::Right );
    m_volSlider->setTickInterval( 5 );

    // ---- layout --------------------------------------------------------------

    // top row: icon + vol% — centered over the meter+slider column (not the label)
    TQHBoxLayout *topRow = new TQHBoxLayout( 0, 0, 2 );
    topRow->addStretch( 1 );
    topRow->addWidget( m_iconLabel, 0, TQt::AlignVCenter );
    topRow->addSpacing( 3 );
    topRow->addWidget( m_volLabel,  0, TQt::AlignVCenter );
    topRow->addStretch( 1 );

    // meter + slider column, with topRow sitting above them
    TQHBoxLayout *meterSlider = new TQHBoxLayout( 0, 0, 2 );
    meterSlider->addWidget( m_levelMeter );
    meterSlider->addWidget( m_volSlider, 1 );

    TQVBoxLayout *sliderCol = new TQVBoxLayout( 0, 0, 2 );
    sliderCol->addLayout( topRow,      0 );
    sliderCol->addLayout( meterSlider, 1 );

    // strip: vertical label beside the slider column
    TQHBoxLayout *strip = new TQHBoxLayout( 0, 0, 2 );
    strip->addWidget( m_label );
    strip->addLayout( sliderCol, 1 );

    // bottom row — fixed 30px height so all widgets align regardless of content.
    // Created before its child widgets so they can be parented directly to it.
    TQWidget *bottomWidget = new TQWidget( this );
    bottomWidget->setFixedHeight( 30 );
    TQHBoxLayout *bottom = new TQHBoxLayout( bottomWidget, 0, 2 );

    // Balance knob — not meaningful for Input (just attenuates capture channels)
    if ( device->category() != AudioDevice::Input ) {
        m_balanceDial = new BalanceKnob( -50, 50, device->pan(), bottomWidget );
        TQToolTip::add( m_balanceDial, i18n("Balance (drag or scroll; double-click to center)") );
    } else {
        m_balanceDial = 0;
    }

    // Red recording-active LED — Input only, shows when a stream is capturing
    if ( device->category() == AudioDevice::Input ) {
        KLed::State initState = device->recordingActive() ? KLed::On : KLed::Off;
        m_recordingLed = new KLed( TQt::red, initState, KLed::Raised, KLed::Circular, bottomWidget );
        m_recordingLed->setFixedSize( 16, 16 );
        TQToolTip::add( m_recordingLed, i18n("Microphone in use") );
        connect( device, TQ_SIGNAL(recordingActiveChanged(bool)),
                 this,   TQ_SLOT(onRecordingActive(bool)) );
    }

    // Green LED = live, off = muted. Click to toggle.
    m_muteLed = new KLedButton(
        TQt::green,
        device->muted() ? KLed::Off : KLed::On,
        KLed::Raised, KLed::Circular, bottomWidget );
    m_muteLed->setFixedSize( 16, 16 );
    TQToolTip::add( m_muteLed, i18n("Mute") );

    // Radio button = this device is the system default. Only for Output/Input.
    AudioDevice::Category cat = device->category();
    if ( model && ( cat == AudioDevice::Output || cat == AudioDevice::Input ) ) {
        m_defaultBtn = new TQRadioButton( TQString(), bottomWidget );
        TQToolTip::add( m_defaultBtn, i18n("Set as default") );
        connect( m_defaultBtn, TQ_SIGNAL(clicked()), this, TQ_SLOT(onDefaultClicked()) );

        if ( cat == AudioDevice::Output ) {
            connect( model, TQ_SIGNAL(defaultOutputChanged(AudioDevice*)),
                     this,  TQ_SLOT(onDefaultChanged(AudioDevice*)) );
            onDefaultChanged( model->defaultOutput() );
        } else {
            connect( model, TQ_SIGNAL(defaultInputChanged(AudioDevice*)),
                     this,  TQ_SLOT(onDefaultChanged(AudioDevice*)) );
            onDefaultChanged( model->defaultInput() );
        }
    }
    bottom->addStretch( 1 );
    if ( m_defaultBtn )
        bottom->addWidget( m_defaultBtn, 0, TQt::AlignVCenter );
    bottom->addStretch( 1 );
    if ( m_balanceDial )
        bottom->addWidget( m_balanceDial, 0, TQt::AlignVCenter );
    else if ( m_recordingLed )
        bottom->addWidget( m_recordingLed, 0, TQt::AlignVCenter );
    bottom->addStretch( 1 );
    bottom->addWidget( m_muteLed, 0, TQt::AlignVCenter );
    bottom->addStretch( 1 );

    // outer column: strip (label+sliderCol) | bottom row
    TQVBoxLayout *right = new TQVBoxLayout( 0, 0, 2 );
    right->addLayout( strip,        1 );
    right->addWidget( bottomWidget, 0 );

    // separator on right edge
    m_sep = new TQFrame( this );
    m_sep->setFrameStyle( TQFrame::VLine | TQFrame::Sunken );
    m_sep->setFixedWidth( 4 );

    TQHBoxLayout *outer = new TQHBoxLayout( this, 4, 2 );
    outer->addLayout( right, 1 );
    outer->addWidget( m_sep, 0, TQt::AlignVCenter );

    // ---- connections ---------------------------------------------------------
    connect( m_volSlider,   TQ_SIGNAL(valueChanged(int)),  this, TQ_SLOT(onVolumeChanged(int)) );
    if ( m_balanceDial )
        connect( m_balanceDial, TQ_SIGNAL(valueChanged(int)), this, TQ_SLOT(onBalanceChanged(int)) );
    connect( m_muteLed,     TQ_SIGNAL(stateChanged(bool)), this, TQ_SLOT(onMuteClicked()) );

    connect( device, TQ_SIGNAL(volumeChanged(int)),           this, TQ_SLOT(onDeviceVolume(int)) );
    connect( device, TQ_SIGNAL(muteChanged(bool)),            this, TQ_SLOT(onDeviceMute(bool)) );
    connect( device, TQ_SIGNAL(panChanged(int)),              this, TQ_SLOT(onDevicePan(int))  );
    connect( device, TQ_SIGNAL(levelChanged(float)),          this, TQ_SLOT(onDeviceLevel(float)) );
    connect( device, TQ_SIGNAL(nameChanged(const TQString&)), this, TQ_SLOT(onDeviceName(const TQString&)) );
}

void DeviceWidget::onVolumeChanged( int v )
{
    int pct = 100 - v;
    m_volLabel->setText( TQString::number(pct) + "%" );
    m_device->setVolume( pct );
}

void DeviceWidget::onBalanceChanged( int v ) { m_device->setPan( v ); }
void DeviceWidget::onMuteClicked()         { m_device->setMuted( m_muteLed->state() == KLed::Off ); }

void DeviceWidget::onDeviceVolume( int v )
{
    m_volSlider->blockSignals( true );
    m_volSlider->setValue( 100 - v );
    m_volSlider->blockSignals( false );
    m_volLabel->setText( TQString::number(v) + "%" );
}

void DeviceWidget::onDeviceMute( bool m )
{
    m_muteLed->blockSignals( true );
    m_muteLed->setState( m ? KLed::Off : KLed::On );
    m_muteLed->blockSignals( false );
}

void DeviceWidget::onDevicePan( int p )
{
    m_balanceDial->blockSignals( true );
    m_balanceDial->setValue( p );
    m_balanceDial->blockSignals( false );
}

void DeviceWidget::onDeviceLevel( float level )  { m_levelMeter->setLevel( level ); }

void DeviceWidget::onDeviceName( const TQString &name )
{
    static_cast<VerticalLabel*>(m_label)->setText( name );
}

void DeviceWidget::wheelEvent( TQWheelEvent *e )
{
    TDEConfig *cfg = TDEGlobal::config();
    cfg->setGroup("Tray");
    int step  = cfg->readNumEntry("ScrollStep", 5);
    int delta = e->delta() > 0 ? step : -step;
    int vol   = TQMAX( 0, TQMIN( 100, m_device->volume() + delta ) );
    m_device->setVolume( vol );
    e->accept();
}

void DeviceWidget::contextMenuEvent( TQContextMenuEvent *e )
{
    TDEPopupMenu menu( this );
    menu.insertTitle( m_device->name() );

    int muteId = menu.insertItem( i18n("Mute"), this, TQ_SLOT(onToggleMute()) );
    menu.setItemChecked( muteId, m_device->muted() );

    if ( m_model ) {
        AudioDevice::Category cat = m_device->category();

        if ( cat == AudioDevice::Output ) {
            menu.insertSeparator();
            int id = menu.insertItem( i18n("Set as Default Output"), this, TQ_SLOT(onDefaultClicked()) );
            menu.setItemChecked( id, m_model->defaultOutput() == m_device );

        } else if ( cat == AudioDevice::Input ) {
            menu.insertSeparator();
            int id = menu.insertItem( i18n("Set as Default Input"), this, TQ_SLOT(onDefaultClicked()) );
            menu.setItemChecked( id, m_model->defaultInput() == m_device );

        } else if ( cat == AudioDevice::Playback ) {
            TQPtrList<AudioDevice> sinks = m_model->devices( AudioDevice::Output );
            if ( !sinks.isEmpty() ) {
                menu.insertTitle( i18n("Move to Sink") );
                m_moveTargets.clear();

                PulseDevice *pd = dynamic_cast<PulseDevice*>(m_device);
                uint32_t currentSink = pd ? pd->sinkIndex() : PA_INVALID_INDEX;

                int idx = 0;
                for ( TQPtrListIterator<AudioDevice> it(sinks); *it; ++it ) {
                    m_moveTargets.append( *it );
                    int itemId = menu.insertItem( (*it)->name(), 1000 + idx );
                    PulseDevice *sink = dynamic_cast<PulseDevice*>(*it);
                    if ( sink && sink->paIndex() == currentSink )
                        menu.setItemChecked( itemId, true );
                    idx++;
                }
                connect( &menu, TQ_SIGNAL(activated(int)),
                         this,  TQ_SLOT(onMoveToSink(int)) );
            }
        }
    }

    menu.exec( e->globalPos() );
    e->accept();
}

void DeviceWidget::onToggleMute()
{
    m_device->setMuted( !m_device->muted() );
}

void DeviceWidget::onDefaultClicked()
{
    if ( !m_model ) return;
    if ( m_device->category() == AudioDevice::Output )
        m_model->setDefaultOutput( m_device );
    else if ( m_device->category() == AudioDevice::Input )
        m_model->setDefaultInput( m_device );
}

void DeviceWidget::onDefaultChanged( AudioDevice *dev )
{
    if ( !m_defaultBtn ) return;
    m_defaultBtn->blockSignals( true );
    m_defaultBtn->setChecked( dev == m_device );
    m_defaultBtn->blockSignals( false );
}

void DeviceWidget::onRecordingActive( bool active )
{
    if ( m_recordingLed )
        m_recordingLed->setState( active ? KLed::On : KLed::Off );
}

void DeviceWidget::setSeparatorVisible( bool v )
{
    if ( m_sep ) v ? m_sep->show() : m_sep->hide();
}

void DeviceWidget::onMoveToSink( int id )
{
    if ( !m_model ) return;
    int idx = id - 1000;
    if ( idx < 0 || (uint)idx >= m_moveTargets.count() ) return;
    m_model->moveSinkInputToSink( m_device, m_moveTargets.at(idx) );
}

#include "devicewidget.moc"
