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

#include "mixer.hpp"
#include "qsnd/event_types.hpp"
#include "qsnd/hctl/elem.hpp"
#include "qsnd/hctl/info_db.hpp"
#include <QCoreApplication>
#include <QEvent>
#include <QSocketNotifier>
#include <iostream>
#include <sstream>

namespace QSnd::HCtl
{

Mixer::Mixer ( QObject * parent_n )
: QObject ( parent_n )
, _info_db ( std::make_unique< QSnd::HCtl::Info_Db > () )
{
  _iface_avail_types.fill ( 0 );
  _iface_type_count.fill ( 0 );
}

Mixer::~Mixer ()
{
  close ();
}

void
Mixer::close ()
{
  // Destroy socket notifiers
  _socket_notifiers.clear ();
  _pollfds.clear ();
  _mixer_elems.clear ();

  if ( snd_hctl () != nullptr ) {
    snd_hctl_set_callback ( snd_hctl (), nullptr );
    snd_hctl_set_callback_private ( snd_hctl (), nullptr );

    snd_hctl_close ( snd_hctl () );
    _snd_hctl = nullptr;
  }

  // Clear statistics
  _iface_num_types = 0;
  _iface_avail_types.fill ( 0 );
  _iface_type_count.fill ( 0 );
}

bool
Mixer::open ( const QString & dev_name_n )
{
  close ();

  _err_func.clear ();
  _err_message.clear ();

  if ( dev_name_n.isEmpty () ) {
    _err_func = "open";
    _err_message = tr ( "Empty device name" );
    return false;
  }

  // Open control handle
  {
    int err;
    err = snd_hctl_open (
        &_snd_hctl, dev_name_n.toLocal8Bit ().constData (), SND_CTL_NONBLOCK );
    if ( err < 0 ) {
      _err_func = "snd_hctl_open";
      _err_message = snd_error_qstring ( err );
      close ();
      return false;
    }
  }

  // Load mixer
  {
    int err = snd_hctl_load ( snd_hctl () );
    if ( err < 0 ) {
      _err_func = "snd_hctl_load";
      _err_message = snd_error_qstring ( err );
      close ();
      return false;
    }
  }

  snd_hctl_set_callback ( snd_hctl (), &Mixer::alsa_callback_hctl );
  snd_hctl_set_callback_private ( snd_hctl (), this );

  // Create mixer elements
  create_mixer_elems ();

  // Create socket notifiers
  if ( !create_socket_notifiers () ) {
    close ();
    return false;
  }

  // Update statistics
  for ( const auto & snd_elem : _mixer_elems ) {
    unsigned int type_idx = info_db ()->iface_type_idx ( snd_elem->iface () );
    _iface_type_count[ type_idx ] += 1;
  }

  _iface_num_types = 0;
  for ( std::size_t ii = 0; ii < info_db ()->num_iface_types (); ++ii ) {
    if ( _iface_type_count[ ii ] > 0 ) {
      _iface_avail_types[ _iface_num_types ] = ii;
      ++_iface_num_types;
    }
  }

  return true;
}

void
Mixer::create_mixer_elems ()
{
  // Create and connect Elem items
  snd_hctl_elem_t * hctl_elem = snd_hctl_first_elem ( snd_hctl () );
  while ( hctl_elem != nullptr ) {
    _mixer_elems.push_back ( std::make_unique< Elem > (
        *info_db (), this, snd_hctl (), hctl_elem ) );
    hctl_elem = snd_hctl_elem_next ( hctl_elem );
  }
}

bool
Mixer::create_socket_notifiers ()
{
  // std::cout << "Mixer::create_socket_notifiers" << "\n";

  // Setup socket notifiers
  const int num_fds = snd_hctl_poll_descriptors_count ( snd_hctl () );

  if ( num_fds <= 0 ) {
    _err_func = "snd_hctl_poll_descriptors_count";
    _err_message = snd_error_qstring ( num_fds );
    return false;
  }

  _pollfds.resize ( num_fds );
  memset ( &_pollfds[ 0 ], 0, num_fds * sizeof ( pollfd ) );

  {
    int err =
        snd_hctl_poll_descriptors ( snd_hctl (), &_pollfds[ 0 ], num_fds );
    if ( err < 0 ) {
      _err_func = "snd_hctl_poll_descriptors";
      _err_message = snd_error_qstring ( err );
      return false;
    }
  }

  for ( int ii = 0; ii < num_fds; ++ii ) {
    const int fd ( _pollfds[ ii ].fd );
    if ( fd != 0 ) {
      auto notifier = std::make_unique< QSocketNotifier > (
          fd, QSocketNotifier::Read, this );

      connect ( notifier.get (),
                &QSocketNotifier::activated,
                this,
                &Mixer::socket_event );

      _socket_notifiers.push_back ( std::move ( notifier ) );
    }
  }

  return true;
}

void
Mixer::set_socked_notifiers_enabled ( bool flag_n )
{
  for ( auto & notifier : _socket_notifiers ) {
    notifier->setEnabled ( flag_n );
  }
}

void
Mixer::request_reload_later ()
{
  // Request a reload delayed
  if ( !_reload_requested ) {
    _reload_requested = true;
    QCoreApplication::postEvent ( this, new QEvent ( QSnd::evt_reload ) );
  }
}

void
Mixer::signalize_all_changes_later ()
{
  if ( !_signalize_changes_requested ) {
    _signalize_changes_requested = true;
    QCoreApplication::postEvent ( this,
                                  new QEvent ( QSnd::evt_signalize_changes ) );
  }
}

void
Mixer::signalize_all_changes ()
{
  for ( auto & mx_elem : _mixer_elems ) {
    mx_elem->signalize_changes ();
  }
}

bool
Mixer::event ( QEvent * event_n )
{
  bool res = true;

  if ( event_n->type () == QSnd::evt_signalize_changes ) {

    _signalize_changes_requested = false;
    signalize_all_changes ();

  } else if ( event_n->type () == QSnd::evt_reload ) {

    _reload_requested = false;
    Q_EMIT sig_mixer_reload_request ();

  } else {
    res = QObject::event ( event_n );
  }

  return res;
}

void
Mixer::socket_event ( int )
{
  if ( snd_hctl () == nullptr ) {
    return;
  }

  int num_ev = snd_hctl_handle_events ( snd_hctl () );
  if ( num_ev < 0 ) {
    // Error. Disable socket notifiers
    set_socked_notifiers_enabled ( false );
    print_alsa_error ( "snd_hctl_handle_events ", num_ev );
  }

  signalize_all_changes ();
}

// Alsa callbacks

int
Mixer::alsa_callback_hctl ( snd_hctl_t * snd_hctl_n,
                            unsigned int mask_n,
                            snd_hctl_elem_t * )
{
  int res = 0;

  Mixer * qsnd_mixer = nullptr;
  {
    void * priv ( snd_hctl_get_callback_private ( snd_hctl_n ) );
    qsnd_mixer = reinterpret_cast< Mixer * > ( priv );
  }

  if ( qsnd_mixer != nullptr ) {
    const unsigned int change_mask =
        ( SND_CTL_EVENT_MASK_VALUE | SND_CTL_EVENT_MASK_INFO |
          SND_CTL_EVENT_MASK_ADD | SND_CTL_EVENT_MASK_TLV );

    if ( ( mask_n == SND_CTL_EVENT_MASK_REMOVE ) ||
         ( ( mask_n & change_mask ) != 0 ) ) {
      qsnd_mixer->request_reload_later ();
    } else {
      {
        std::ostringstream msg;
        msg << "Mixer::alsa_callback_hctl: ";
        msg << "Unknown mask ( " << mask_n << " )" << std::endl;
        std::cerr << msg.str ();
      }
      res = -1;
    }
  }

  return res;
}

} // namespace QSnd::HCtl
