/// QasTools: Desktop toolset for the Linux sound system ALSA.
/// \copyright See COPYING file.

#include "proxies_model.hpp"
#include "mstate/mixer/state.hpp"
#include "mwdg/event_types.hpp"
#include "mwdg/mixer/proxy/group.hpp"
#include "mwdg/mixer/setup.hpp"
#include "qsnd/mixer/elem.hpp"
#include <QCoreApplication>
#include <iostream>

namespace MWdg::Mixer
{

Proxies_Model::Proxies_Model ( QObject * parent_n )
: QObject ( parent_n )
{
  _proxies_filtered_stats_stream.fill ( 0 );

  _tool_tip.l10n_stream_dir[ 0 ] = tr ( "Playback" );
  _tool_tip.l10n_stream_dir[ 1 ] = tr ( "Capture" );
}

Proxies_Model::~Proxies_Model () = default;

void
Proxies_Model::set_mixer_setup ( const MWdg::Mixer::Setup * setup_n )
{
  _mixer_setup = setup_n;
  reload ();
}

void
Proxies_Model::clear ()
{
  setup ( nullptr, std::shared_ptr< MState::Mixer::State > () );
}

void
Proxies_Model::reload ()
{
  // Reload on demand
  if ( ( qsnd_mixer () != nullptr ) && mixer_state () ) {
    {
      QSnd::Mixer::Mixer * qsm = qsnd_mixer ();
      std::shared_ptr< MState::Mixer::State > mstate = mixer_state ();
      setup ( qsm, mstate );
    }
    reload_proxies_sorted ();
    reload_proxies_filtered ();
    reload_proxies_visible ();
  }
}

void
Proxies_Model::setup (
    QSnd::Mixer::Mixer * qsnd_mixer_n,
    const std::shared_ptr< MState::Mixer::State > & mixer_state_n )
{
  // Visible proxies list is cleared but not refilled
  bool sig_sorted = !proxies_sorted ().empty ();
  bool sig_filtered = !proxies_filtered ().empty ();
  bool sig_visible = !proxies_visible ().empty ();

  // Emit begin signals
  Q_EMIT sig_reload_begin ();
  if ( sig_sorted ) {
    Q_EMIT sig_reload_sorted_begin ();
  }
  if ( sig_filtered ) {
    Q_EMIT sig_reload_filtered_begin ();
  }
  if ( sig_visible ) {
    Q_EMIT sig_reload_visible_begin ();
  }

  // Clear
  _proxies_visible.clear ();
  _proxies_filtered.clear ();
  _proxies_sorted.clear ();
  _proxies_all.clear ();
  _proxies_groups.clear ();

  // Accept values
  _qsnd_mixer = qsnd_mixer_n;
  _mixer_state = mixer_state_n;
  _mixer_state_controls = get_mixer_state_controls ();

  // Rebuild
  if ( ( _qsnd_mixer != nullptr ) && _mixer_state ) {
    this->create_proxies_groups ( _proxies_groups );
    // Create a non owning copy
    _proxies_all.reserve ( _proxies_groups.size () );
    for ( const auto & group : _proxies_groups ) {
      _proxies_all.push_back ( group.get () );
    }
    // Connect separation request signal
    for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_all ) {
      connect ( pgrp,
                &Proxy::Group::sig_separation_requested,
                this,
                &Proxies_Model::separation_requested );
    }
  }

  // Emit end signals
  if ( sig_visible ) {
    Q_EMIT sig_reload_visible_end ();
  }
  if ( sig_filtered ) {
    Q_EMIT sig_reload_filtered_end ();
  }
  if ( sig_sorted ) {
    Q_EMIT sig_reload_sorted_end ();
  }
  Q_EMIT sig_reload_end ();
}

void
Proxies_Model::reload_proxies_sorted ()
{
  // Subsequent proxies lists are cleared but not refilled
  bool sig_filtered = !proxies_filtered ().empty ();
  bool sig_visible = !proxies_visible ().empty ();

  // Emit begin signals
  Q_EMIT sig_reload_sorted_begin ();
  if ( sig_filtered ) {
    Q_EMIT sig_reload_filtered_begin ();
  }
  if ( sig_visible ) {
    Q_EMIT sig_reload_visible_begin ();
  }

  // Clear subsequent proxies lists
  _proxies_filtered.clear ();
  clear_proxies_visible ();

  // Update the sorted list
  _proxies_sorted = _proxies_all;
  if ( _mixer_state && _mixer_state->sort_stream_dir () ) {
    std::stable_partition ( _proxies_sorted.begin (),
                            _proxies_sorted.end (),
                            [] ( MWdg::Mixer::Proxy::Group * pgrp_n ) {
                              return ( pgrp_n->snd_dir () == 0 );
                            } );
  }

  // Emit end signals
  if ( sig_visible ) {
    Q_EMIT sig_reload_visible_end ();
  }
  if ( sig_filtered ) {
    Q_EMIT sig_reload_filtered_end ();
  }
  Q_EMIT sig_reload_sorted_end ();
}

void
Proxies_Model::reload_proxies_filtered ()
{
  // Subsequent proxies lists are cleared but not refilled
  bool sig_visible = !proxies_visible ().empty ();

  // Emit begin signals
  Q_EMIT sig_reload_filtered_begin ();
  if ( sig_visible ) {
    Q_EMIT sig_reload_visible_begin ();
  }

  // Clear subsequent proxies lists
  clear_proxies_visible ();

  // Update the filtered list
  _proxies_filtered.clear ();
  _proxies_filtered.reserve ( _proxies_sorted.size () );
  for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_sorted ) {
    if ( this->filter_passed ( pgrp ) ) {
      _proxies_filtered.push_back ( pgrp );
    }
  }
  _proxies_filtered.shrink_to_fit ();

  // Acquire filtered statistics
  _proxies_filtered_stats_stream.fill ( 0 );
  for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_filtered ) {
    _proxies_filtered_stats_stream[ pgrp->snd_dir () ] += 1;
  }

  // Emit end signals
  if ( sig_visible ) {
    Q_EMIT sig_reload_visible_end ();
  }
  Q_EMIT sig_reload_filtered_end ();
}

void
Proxies_Model::reload_proxies_visible ()
{
  Q_EMIT sig_reload_visible_begin ();

  clear_proxies_visible ();
  _proxies_visible.reserve ( _proxies_filtered.size () );
  for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_filtered ) {
    if ( !this->filter_visible_passed ( pgrp ) ) {
      continue;
    }
    pgrp->set_is_visible ( true );
    // Separate group on demand
    if ( pgrp->is_joined () && this->should_be_separated ( pgrp ) ) {
      this->separate_proxies_group ( pgrp );
    }
    _proxies_visible.push_back ( pgrp );
  }
  _proxies_visible.shrink_to_fit ();

  Q_EMIT sig_reload_visible_end ();
}

bool
Proxies_Model::proxy_sorted_blacklisted ( std::size_t index_n ) const
{
  if ( index_n < proxies_sorted ().size () ) {
    if ( mixer_state_controls () != nullptr ) {
      MWdg::Mixer::Proxy::Group * pgrp = proxies_sorted ()[ index_n ];
      return mixer_state_controls ()->proxies_group_blacklisted (
          pgrp->group_id () );
    }
  }
  return false;
}

void
Proxies_Model::proxy_sorted_set_blacklisted ( std::size_t index_n, bool flag_n )
{
  if ( index_n < proxies_sorted ().size () ) {
    if ( mixer_state_controls () != nullptr ) {
      MWdg::Mixer::Proxy::Group * pgrp = proxies_sorted ()[ index_n ];
      mixer_state_controls ()->set_proxies_group_blacklisted (
          pgrp->group_id (), flag_n );
    }
  }
}

bool
Proxies_Model::show_tool_tips () const
{
  if ( mixer_setup () != nullptr ) {
    return mixer_setup ()->show_tool_tips;
  }
  return true;
}

QString
Proxies_Model::create_group_id ( QSnd::Mixer::Elem * qsme_n,
                                 std::size_t snd_dir_n )
{
  const QString sep = QStringLiteral ( "_" );
  QString id = qsme_n->elem_name ();
  id += sep;
  id += QString::number ( snd_dir_n );
  id += sep;
  id += QString::number ( qsme_n->elem_index () );
  return id;
}

QString
Proxies_Model::create_group_tool_tip ( QSnd::Mixer::Elem * qsme_n,
                                       std::size_t snd_dir_n )
{
  QString ttip = QLatin1StringView ( "<div><b>" );
  ttip += qsme_n->display_name ();
  ttip += QLatin1StringView ( "</b></div>\n" );
  ttip += QLatin1StringView ( "<div>" );
  ttip += _tool_tip.l10n_stream_dir[ snd_dir_n ];
  ttip += QLatin1StringView ( "</div>\n" );
  return ttip;
}

void
Proxies_Model::toggle_joined_separated ( MWdg::Mixer::Proxy::Group * pgrp_n )
{
  if ( pgrp_n == nullptr ) {
    return;
  }

  if ( !pgrp_n->can_be_separated () ) {
    return;
  }

  // Emit begin signal
  Q_EMIT sig_reload_visible_begin ();

  // Change
  if ( pgrp_n->is_joined () ) {
    this->separate_proxies_group ( pgrp_n );
  } else {
    this->join_proxies_group ( pgrp_n );
  }

  // Emit end signal
  Q_EMIT sig_reload_visible_end ();
}

bool
Proxies_Model::should_be_separated (
    const MWdg::Mixer::Proxy::Group * pgrp_n ) const
{
  if ( pgrp_n->can_be_separated () ) {
    if ( pgrp_n->should_be_separated () ) {
      return true;
    }
    if ( mixer_state_controls () != nullptr ) {
      if ( mixer_state_controls ()->proxies_group_separated (
               pgrp_n->group_id () ) ) {
        return true;
      }
    }
  }
  return false;
}

bool
Proxies_Model::filter_passed ( const MWdg::Mixer::Proxy::Group * pgrp_n ) const
{
  if ( !pgrp_n->qsnd_mixer_elem ()->is_active () ) {
    return false;
  }
  if ( mixer_state_controls () != nullptr ) {
    if ( mixer_state_controls ()->proxies_group_blacklisted (
             pgrp_n->group_id () ) ) {
      return false;
    }
  }
  return true;
}

bool
Proxies_Model::filter_visible_passed (
    const MWdg::Mixer::Proxy::Group * pgrp_n ) const
{
  if ( _mixer_state && !_mixer_state->show_stream ( pgrp_n->snd_dir () ) ) {
    return false;
  }
  return true;
}

void
Proxies_Model::clear_proxies_visible ()
{
  // Clear visibility flag in old list
  for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_visible ) {
    pgrp->set_is_visible ( false );
  }
  _proxies_visible.clear ();
}

void
Proxies_Model::separation_requested ()
{
  if ( !_separation_requested ) {
    _separation_requested = true;
    QCoreApplication::postEvent ( this, new QEvent ( MWdg::evt_separation ) );
  }
}

void
Proxies_Model::separate_where_requested ()
{
  // std::cout << "Proxies_Model::separate_where_requested" << "\n";

  _separation_requested = false;

  bool visible_request = false;
  for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_all ) {
    if ( !pgrp->separation_request () ) {
      continue;
    }
    if ( pgrp->is_visible () ) {
      // Visible group
      visible_request = true;
    } else {
      // Not a visible group. Just clear the flag.
      // Separation will be applied (on demand) when
      // the group becomes visible.
      pgrp->set_separation_request ( false );
    }
  }

  if ( visible_request ) {
    // Emit begin signal
    Q_EMIT sig_reload_visible_begin ();

    // Change
    for ( MWdg::Mixer::Proxy::Group * pgrp : _proxies_visible ) {
      if ( pgrp->separation_request () ) {
        pgrp->set_separation_request ( false );
        this->separate_proxies_group ( pgrp );
      }
    }

    // Emit end signal
    Q_EMIT sig_reload_visible_end ();
  }

  // std::cout << "Proxies_Model::separate_where_requested done" << "\n";
}

bool
Proxies_Model::event ( QEvent * event_n )
{
  if ( event_n->type () == MWdg::evt_separation ) {
    separate_where_requested ();
    return true;
  }
  return QObject::event ( event_n );
}

} // namespace MWdg::Mixer
