#include "devicespage.h"
#include "../model/pulsemodel.h"

#include <tqscrollview.h>
#include <tqlayout.h>
#include <tqlabel.h>
#include <tqcombobox.h>
#include <tqgroupbox.h>
#include <tqstringlist.h>
#include <tqpixmap.h>
#include <tqbitmap.h>
#include <tqpainter.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <kiconloader.h>

#include <pulse/pulseaudio.h>

static TQString portTypeIcon( uint32_t type )
{
    switch ( type ) {
        case PA_DEVICE_PORT_TYPE_HEADPHONES: return "audio-headphones";
        case PA_DEVICE_PORT_TYPE_HEADSET:    return "audio-headset";
        case PA_DEVICE_PORT_TYPE_MIC:        return "audio-input-microphone";
        case PA_DEVICE_PORT_TYPE_SPEAKER:    return "audio-speakers";
        case PA_DEVICE_PORT_TYPE_HDMI:       return "video-display";
        case PA_DEVICE_PORT_TYPE_SPDIF:      return "audio-card";
        case PA_DEVICE_PORT_TYPE_LINE:       return "audio-card";
        case PA_DEVICE_PORT_TYPE_USB:        return "audio-card";
        case PA_DEVICE_PORT_TYPE_BLUETOOTH:  return "bluetooth";
        default:                             return "audio-card";
    }
}

static TQPixmap availDot( bool plugged, int size )
{
    TQPixmap pm( size, size );
    TQBitmap mask( size, size, true );
    {
        TQPainter mp( &mask );
        mp.setBrush(TQt::color1 );
        mp.setPen(TQt::NoPen );
        mp.drawEllipse( 1, 1, size-2, size-2 );
    }
    pm.setMask( mask );
    pm.fill( plugged ? TQColor(80, 200, 80) : TQColor(160, 160, 160) );
    return pm;
}

DevicesPage::DevicesPage( PulseModel *model, TQWidget *parent )
    : TQWidget(parent), m_model(model), m_container(0)
{
    TQVBoxLayout *outer = new TQVBoxLayout( this, 0, 0 );
    m_scroll = new TQScrollView( this );
    m_scroll->setFrameStyle( TQFrame::NoFrame );
    m_scroll->setResizePolicy( TQScrollView::AutoOneFit );
    outer->addWidget( m_scroll );

    rebuild();

    connect( model, TQ_SIGNAL(cardAdded(uint32_t)),    this, TQ_SLOT(onCardAdded(uint32_t)) );
    connect( model, TQ_SIGNAL(cardRemoved(uint32_t)),  this, TQ_SLOT(onCardRemoved(uint32_t)) );
    connect( model, TQ_SIGNAL(cardUpdated(uint32_t)),  this, TQ_SLOT(onCardUpdated(uint32_t)) );
    connect( model, TQ_SIGNAL(sinkUpdated(uint32_t)),  this, TQ_SLOT(onSinkUpdated(uint32_t)) );
    connect( model, TQ_SIGNAL(sourceUpdated(uint32_t)),this, TQ_SLOT(onSourceUpdated(uint32_t)) );
}

void DevicesPage::rebuild()
{
    m_comboCard.clear();
    m_profileNames.clear();
    m_cardCombo.clear();
    m_cardPorts.clear();
    m_sinkPortComboSink.clear();
    m_sinkPortComboNames.clear();
    m_sinkPortCombos.clear();
    m_sourcePortComboSource.clear();
    m_sourcePortComboNames.clear();
    m_sourcePortCombos.clear();

    delete m_container;
    m_container = new TQWidget( m_scroll->viewport() );
    m_scroll->addChild( m_container );

    TQVBoxLayout *vbox = new TQVBoxLayout( m_container, 8, 6 );

    TQValueList<PulseCardInfo> cards = m_model->cards();
    for ( TQValueList<PulseCardInfo>::Iterator it = cards.begin(); it != cards.end(); ++it ) {
        PulseCardInfo &info = *it;

        TQGroupBox *grp = new TQGroupBox( info.description, m_container );
        grp->setColumnLayout( 0, TQt::Vertical );
        grp->layout()->setSpacing( 4 );
        grp->layout()->setMargin( 8 );
        vbox->addWidget( grp );

        TQGridLayout *grid = new TQGridLayout( grp->layout(), 0, 2, 4 );
        grid->setColStretch( 1, 1 );
        int gridRow = 0;

        auto addRow = [&]( const TQString &labelText, TQWidget *valueWidget ) {
            TQLabel *lbl = new TQLabel( labelText, grp );
            lbl->setAlignment( TQt::AlignRight | TQt::AlignVCenter );
            grid->addWidget( lbl,         gridRow, 0 );
            grid->addWidget( valueWidget, gridRow, 1 );
            ++gridRow;
        };
        auto addTextRow = [&]( const TQString &labelText, const TQString &valueText ) {
            addRow( labelText, new TQLabel( valueText, grp ) );
        };

        if ( !info.vendor.isEmpty() )
            addTextRow( i18n("Vendor:"), info.vendor );
        if ( !info.product.isEmpty() )
            addTextRow( i18n("Product:"), info.product );
        TQStringList typeparts;
        if ( !info.formFactor.isEmpty() ) typeparts << info.formFactor;
        if ( !info.busType.isEmpty() )    typeparts << info.busType;
        if ( !typeparts.isEmpty() )
            addTextRow( i18n("Type:"), typeparts.join(" / ") );

        // Profile dropdown
        TQComboBox *combo = new TQComboBox( false, grp );
        combo->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Fixed );
        addRow( i18n("Profile:"), combo );

        TQValueList<TQString> names;
        int activeIdx = 0, i = 0;
        for ( TQValueList<PulseCardProfile>::Iterator pit = info.profiles.begin();
              pit != info.profiles.end(); ++pit, ++i ) {
            combo->insertItem( pit->description );
            names.append( pit->name );
            if ( pit->name == info.activeProfile )
                activeIdx = i;
        }
        if ( info.profiles.count() > 0 )
            combo->setCurrentItem( activeIdx );

        m_profileNames[combo] = names;
        m_comboCard[combo]    = info.index;
        m_cardCombo[info.index] = combo;

        connect( combo, TQ_SIGNAL(activated(int)), this, TQ_SLOT(onProfileActivated(int)) );

        addPortCombos( grid, gridRow, grp, m_model->sinksForCard( info.index ),   true  );
        addPortCombos( grid, gridRow, grp, m_model->sourcesForCard( info.index ), false );

        // Ports — 2-column grid to keep tall cards compact
        if ( !info.ports.isEmpty() ) {
            TQLabel *portsHdr = new TQLabel( i18n("Ports:"), grp );
            portsHdr->setAlignment( TQt::AlignRight | TQt::AlignTop );
            TQWidget *portsWidget = new TQWidget( grp );
            TQGridLayout *portsGrid = new TQGridLayout( portsWidget, 0, 2, 0, 4 );
            portsGrid->setColStretch( 0, 1 );
            portsGrid->setColStretch( 1, 1 );

            TQValueList<PortWidgets> portWidgetList;
            int portIdx = 0;
            for ( TQValueList<PulseCardPort>::Iterator pit = info.ports.begin();
                  pit != info.ports.end(); ++pit, ++portIdx ) {
                bool plugged = ( pit->available != PA_PORT_AVAILABLE_NO );

                TQWidget *row = new TQWidget( portsWidget );
                TQHBoxLayout *hbox = new TQHBoxLayout( row, 0, 4 );

                TQLabel *dot = new TQLabel( row );
                dot->setPixmap( availDot( plugged, 10 ) );
                dot->setFixedSize( 10, 10 );

                TQLabel *icon = new TQLabel( row );
                TQPixmap px = TDEGlobal::iconLoader()->loadIcon(
                    portTypeIcon( pit->type ), TDEIcon::Small, 16,
                    TDEIcon::DefaultState, 0, true );
                if ( !px.isNull() )
                    icon->setPixmap( px );
                icon->setFixedSize( 16, 16 );

                TQLabel *name = new TQLabel( pit->description, row );

                hbox->addWidget( dot );
                hbox->addWidget( icon );
                hbox->addWidget( name );
                hbox->addStretch( 1 );

                portsGrid->addWidget( row, portIdx / 2, portIdx % 2 );

                PortWidgets pw;
                pw.dot = dot;
                portWidgetList.append( pw );
            }
            m_cardPorts[info.index] = portWidgetList;

            grid->addWidget( portsHdr,    gridRow, 0, TQt::AlignRight | TQt::AlignTop );
            grid->addWidget( portsWidget, gridRow, 1 );
            ++gridRow;
        }
    }

    vbox->addStretch( 1 );
    m_container->show();
}

void DevicesPage::addPortCombos( TQGridLayout *grid, int &gridRow, TQWidget *grp,
    const TQValueList<PulseModel::DevicePortInfo> &devices, bool isSink )
{
    bool multi = false;
    for ( TQValueList<PulseModel::DevicePortInfo>::ConstIterator it = devices.begin();
          it != devices.end(); ++it )
        if ( (int)(*it).ports.count() >= 2 ) { multi = ( devices.count() > 1 ); break; }

    for ( TQValueList<PulseModel::DevicePortInfo>::ConstIterator sit = devices.begin();
          sit != devices.end(); ++sit ) {
        const PulseModel::DevicePortInfo &dev = *sit;
        if ( (int)dev.ports.count() < 2 ) continue;

        TQComboBox *pc = new TQComboBox( false, grp );
        pc->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Fixed );
        TQString label = multi
            ? dev.description + i18n(" port:")
            : ( isSink ? i18n("Output port:") : i18n("Input port:") );

        TQLabel *lbl = new TQLabel( label, grp );
        lbl->setAlignment( TQt::AlignRight | TQt::AlignVCenter );
        grid->addWidget( lbl, gridRow, 0 );
        grid->addWidget( pc,  gridRow, 1 );
        ++gridRow;

        TQValueList<TQString> portNames;
        int activePort = 0, pi = 0;
        for ( TQValueList<PulseDevicePort>::ConstIterator pit = dev.ports.begin();
              pit != dev.ports.end(); ++pit, ++pi ) {
            const PulseDevicePort &port = *pit;
            pc->insertItem( port.description );
            portNames.append( port.name );
            if ( port.name == dev.activePort )
                activePort = pi;
        }
        pc->setCurrentItem( activePort );

        if ( isSink ) {
            m_sinkPortComboNames[pc]        = portNames;
            m_sinkPortComboSink[pc]         = dev.paIndex;
            m_sinkPortCombos[dev.paIndex]   = pc;
            connect( pc, TQ_SIGNAL(activated(int)), this, TQ_SLOT(onSinkPortActivated(int)) );
        } else {
            m_sourcePortComboNames[pc]      = portNames;
            m_sourcePortComboSource[pc]     = dev.paIndex;
            m_sourcePortCombos[dev.paIndex] = pc;
            connect( pc, TQ_SIGNAL(activated(int)), this, TQ_SLOT(onSourcePortActivated(int)) );
        }
    }
}

void DevicesPage::onCardAdded( uint32_t )   { rebuild(); }
void DevicesPage::onCardRemoved( uint32_t ) { rebuild(); }
void DevicesPage::onCardUpdated( uint32_t ) { rebuild(); }

void DevicesPage::onProfileActivated( int idx )
{
    TQComboBox *combo = dynamic_cast<TQComboBox*>( const_cast<TQObject*>( sender() ) );
    if ( !combo ) return;

    TQMap<TQComboBox*, uint32_t>::Iterator cit = m_comboCard.find( combo );
    if ( cit == m_comboCard.end() ) return;
    uint32_t cardIndex = cit.data();

    TQMap<TQComboBox*, TQValueList<TQString>>::Iterator nit = m_profileNames.find( combo );
    if ( nit == m_profileNames.end() || idx >= (int)nit.data().count() ) return;

    m_model->setCardProfile( cardIndex, nit.data()[idx] );
}

void DevicesPage::onSinkPortActivated( int idx )
{
    TQComboBox *combo = dynamic_cast<TQComboBox*>( const_cast<TQObject*>( sender() ) );
    if ( !combo ) return;

    TQMap<TQComboBox*, uint32_t>::Iterator cit = m_sinkPortComboSink.find( combo );
    if ( cit == m_sinkPortComboSink.end() ) return;

    TQMap<TQComboBox*, TQValueList<TQString>>::Iterator nit = m_sinkPortComboNames.find( combo );
    if ( nit == m_sinkPortComboNames.end() || idx >= (int)nit.data().count() ) return;

    m_model->setSinkPort( cit.data(), nit.data()[idx] );
}

void DevicesPage::onSourcePortActivated( int idx )
{
    TQComboBox *combo = dynamic_cast<TQComboBox*>( const_cast<TQObject*>( sender() ) );
    if ( !combo ) return;

    TQMap<TQComboBox*, uint32_t>::Iterator cit = m_sourcePortComboSource.find( combo );
    if ( cit == m_sourcePortComboSource.end() ) return;

    TQMap<TQComboBox*, TQValueList<TQString>>::Iterator nit = m_sourcePortComboNames.find( combo );
    if ( nit == m_sourcePortComboNames.end() || idx >= (int)nit.data().count() ) return;

    m_model->setSourcePort( cit.data(), nit.data()[idx] );
}

void DevicesPage::updatePortCombo( uint32_t paIndex, bool isSink )
{
    TQMap<uint32_t, TQComboBox*> &combos = isSink ? m_sinkPortCombos : m_sourcePortCombos;
    TQMap<uint32_t, TQComboBox*>::Iterator cit = combos.find( paIndex );
    if ( cit == combos.end() ) { rebuild(); return; }

    TQComboBox *combo = cit.data();
    TQMap<TQComboBox*, TQValueList<TQString>> &nameMap =
        isSink ? m_sinkPortComboNames : m_sourcePortComboNames;
    TQMap<TQComboBox*, TQValueList<TQString>>::Iterator nit = nameMap.find( combo );
    if ( nit == nameMap.end() ) return;

    TQValueList<PulseCardInfo> cards = m_model->cards();
    for ( TQValueList<PulseCardInfo>::ConstIterator it = cards.begin(); it != cards.end(); ++it ) {
        TQValueList<PulseModel::DevicePortInfo> devices = isSink
            ? m_model->sinksForCard( (*it).index )
            : m_model->sourcesForCard( (*it).index );
        for ( TQValueList<PulseModel::DevicePortInfo>::ConstIterator sit = devices.begin();
              sit != devices.end(); ++sit ) {
            if ( (*sit).paIndex != paIndex ) continue;
            TQValueList<TQString> &names = nit.data();
            for ( int i = 0; i < (int)names.count(); ++i ) {
                if ( names[i] == (*sit).activePort ) {
                    combo->blockSignals( true );
                    combo->setCurrentItem( i );
                    combo->blockSignals( false );
                    return;
                }
            }
        }
    }
}

void DevicesPage::onSinkUpdated( uint32_t paIndex )   { updatePortCombo( paIndex, true  ); }
void DevicesPage::onSourceUpdated( uint32_t paIndex ) { updatePortCombo( paIndex, false ); }

#include "devicespage.moc"
