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

#include "elem.hpp"
#include "qsnd/mixer/mixer.hpp"
#include <QCoreApplication>
#include <QEvent>
#include <iostream>
#include <sstream>

namespace QSnd::Mixer
{

Elem::Elem ( QObject * parent_n,
             snd_mixer_t * mixer_n,
             snd_mixer_elem_t * mixer_elem_n )
: QObject ( parent_n )
, _snd_mixer ( mixer_n )
, _snd_mixer_elem ( mixer_elem_n )
{
  _volumes_equal[ 0 ] = true;
  _volumes_equal[ 1 ] = true;

  _switches_equal[ 0 ] = true;
  _switches_equal[ 1 ] = true;

  _enums_equal[ 0 ] = true;
  _enums_equal[ 1 ] = true;

  _num_volume_channels[ 0 ] = 0;
  _num_volume_channels[ 1 ] = 0;

  _num_switch_channels[ 0 ] = 0;
  _num_switch_channels[ 1 ] = 0;

  _num_enum_channels[ 0 ] = 0;
  _num_enum_channels[ 1 ] = 0;

  _channels[ 0 ].clear ();
  _channels[ 1 ].clear ();

  _volume_min[ 0 ] = 0;
  _volume_min[ 1 ] = 0;

  _volume_max[ 0 ] = 0;
  _volume_max[ 1 ] = 0;

  _dB_min[ 0 ] = 0;
  _dB_min[ 1 ] = 0;

  _dB_max[ 0 ] = 0;
  _dB_max[ 1 ] = 0;

  if ( ( _snd_mixer == nullptr ) || ( _snd_mixer_elem == nullptr ) ) {
    return;
  }

  // Untranslated element name
  _elem_name =
      QString::fromUtf8 ( snd_mixer_selem_get_name ( snd_mixer_elem () ), -1 );

  // Translated display name
  _display_name = QCoreApplication::translate (
      "ALSA::Elem_Name", snd_mixer_selem_get_name ( snd_mixer_elem () ) );

  snd_mixer_selem_id_malloc ( &_snd_mixer_selem_id );
  snd_mixer_selem_get_id ( _snd_mixer_elem, _snd_mixer_selem_id );

  for ( int ii = 0; ii <= SND_MIXER_SCHN_LAST; ++ii ) {
    snd_mixer_selem_channel_id_t ch_id (
        static_cast< snd_mixer_selem_channel_id_t > ( ii ) );

    if ( snd_mixer_selem_has_playback_channel ( _snd_mixer_elem, ch_id ) ) {
      _channels[ SD_PLAYBACK ].push_back ( ch_id );
    }

    if ( snd_mixer_selem_has_capture_channel ( _snd_mixer_elem, ch_id ) ) {
      _channels[ SD_CAPTURE ].push_back ( ch_id );
    }
  }

  if ( snd_mixer_selem_has_playback_volume ( _snd_mixer_elem ) > 0 ) {
    const std::size_t snd_dir = SD_PLAYBACK;
    if ( _channels[ snd_dir ].size () > 0 ) {

      if ( snd_mixer_selem_has_playback_volume_joined ( _snd_mixer_elem ) ) {
        _num_volume_channels[ snd_dir ] = 1;
      } else {
        _num_volume_channels[ snd_dir ] = _channels[ snd_dir ].size ();
      }

      snd_mixer_selem_get_playback_volume_range (
          _snd_mixer_elem, &_volume_min[ snd_dir ], &_volume_max[ snd_dir ] );

      int err = snd_mixer_selem_get_playback_dB_range (
          _snd_mixer_elem, &_dB_min[ snd_dir ], &_dB_max[ snd_dir ] );
      if ( err < 0 ) {
        _dB_min[ snd_dir ] = 0;
        _dB_max[ snd_dir ] = 0;
      }

    } else {

      QString msg (
          "[WW] %1 has a playback volume but no playback channels\n" );
      msg = msg.arg ( elem_name () );
      std::cerr << msg.toLocal8Bit ().constData ();
    }
  }

  if ( snd_mixer_selem_has_capture_volume ( _snd_mixer_elem ) > 0 ) {
    const std::size_t snd_dir = SD_CAPTURE;
    if ( _channels[ snd_dir ].size () > 0 ) {

      if ( snd_mixer_selem_has_capture_volume_joined ( _snd_mixer_elem ) ) {
        _num_volume_channels[ snd_dir ] = 1;
      } else {
        _num_volume_channels[ snd_dir ] = _channels[ snd_dir ].size ();
      }

      snd_mixer_selem_get_capture_volume_range (
          _snd_mixer_elem, &_volume_min[ snd_dir ], &_volume_max[ snd_dir ] );

      int err = snd_mixer_selem_get_capture_dB_range (
          _snd_mixer_elem, &_dB_min[ snd_dir ], &_dB_max[ snd_dir ] );
      if ( err < 0 ) {
        _dB_min[ snd_dir ] = 0;
        _dB_max[ snd_dir ] = 0;
      }

    } else {

      QString msg ( "[WW] %1 has a capture volume but no capture channels\n" );
      msg = msg.arg ( elem_name () );
      std::cerr << msg.toLocal8Bit ().constData ();
    }
  }

  if ( snd_mixer_selem_has_playback_switch ( _snd_mixer_elem ) > 0 ) {
    const std::size_t snd_dir = SD_PLAYBACK;
    if ( _channels[ snd_dir ].size () > 0 ) {

      if ( snd_mixer_selem_has_playback_switch_joined ( _snd_mixer_elem ) ) {
        _num_switch_channels[ snd_dir ] = 1;
      } else {
        _num_switch_channels[ snd_dir ] = _channels[ snd_dir ].size ();
      }

    } else {

      QString msg (
          "[WW] %1 has a playback switch but no playback channels\n" );
      msg = msg.arg ( elem_name () );
      std::cerr << msg.toLocal8Bit ().constData ();
    }
  }

  if ( snd_mixer_selem_has_capture_switch ( _snd_mixer_elem ) > 0 ) {
    const std::size_t snd_dir = SD_CAPTURE;
    if ( _channels[ snd_dir ].size () > 0 ) {

      if ( snd_mixer_selem_has_capture_switch_joined ( _snd_mixer_elem ) ) {
        _num_switch_channels[ snd_dir ] = 1;
      } else {
        _num_switch_channels[ snd_dir ] = _channels[ snd_dir ].size ();
      }

    } else {

      QString msg ( "[WW] %1 has a capture switch but no capture channels\n" );
      msg = msg.arg ( elem_name () );
      std::cerr << msg.toLocal8Bit ().constData ();
    }
  }

  // Enumerated
  if ( snd_mixer_selem_is_enumerated ( _snd_mixer_elem ) ) {

    bool is_play ( snd_mixer_selem_is_enum_playback ( _snd_mixer_elem ) > 0 );
    bool is_cap ( snd_mixer_selem_is_enum_capture ( _snd_mixer_elem ) > 0 );

    if ( !( is_play || is_cap ) ) {
      // Workaround
      // Try to determine the direction from the name
      QString fstr ( elem_name () );
      fstr = fstr.toLower ();
      int fidx ( fstr.indexOf ( "mic" ) );
      if ( fidx < 0 ) {
        fidx = fstr.indexOf ( "adc " );
      }
      if ( fidx < 0 ) {
        fidx = fstr.indexOf ( "capture" );
      }
      if ( fidx < 0 ) {
        fidx = fstr.indexOf ( "input source" );
      }
      if ( fidx >= 0 ) {
        is_cap = true;
      }

      if ( !is_cap ) {
        if ( !_channels[ SD_PLAYBACK ].empty () ) {
          is_play = true;
        } else if ( !_channels[ SD_CAPTURE ].empty () ) {
          is_cap = true;
        } else {
          is_play = true;
        }
      }
    }

    // Changing the channels for broken alsa implementations
    if ( is_play ) {
      _num_enum_channels[ SD_PLAYBACK ] = 1;
      _channels[ SD_PLAYBACK ].resize ( 1, SND_MIXER_SCHN_MONO );
    }
    if ( is_cap ) {
      _num_enum_channels[ SD_CAPTURE ] = 1;
      _channels[ SD_CAPTURE ].resize ( 1, SND_MIXER_SCHN_MONO );
    }

    // Read enum item names
    const int num_items = snd_mixer_selem_get_enum_items ( _snd_mixer_elem );
    if ( num_items > 0 ) {
      _enum_num_items = num_items;
      _enum_item_names.resize ( _enum_num_items );

      const unsigned int buff_size ( 256 );
      char buff[ buff_size ];

      for ( int ii = 0; ii < num_items; ++ii ) {
        int err = snd_mixer_selem_get_enum_item_name (
            _snd_mixer_elem, ii, buff_size, buff );
        if ( err >= 0 ) {
          _enum_item_names[ ii ] =
              QCoreApplication::translate ( "ALSA::Enum_Value", buff );
        } else {
          print_alsa_error ( "snd_mixer_selem_get_enum_item_name", err );
        }
      }
    }
  }

  // Install ALSA callbacks
  snd_mixer_elem_set_callback_private ( _snd_mixer_elem, this );
  snd_mixer_elem_set_callback ( _snd_mixer_elem,
                                &Elem::alsa_callback_mixer_elem );

  update_values_mark ();
}

Elem::~Elem ()
{
  if ( _snd_mixer_elem != nullptr ) {
    snd_mixer_elem_set_callback ( _snd_mixer_elem, nullptr );
    snd_mixer_elem_set_callback_private ( _snd_mixer_elem, nullptr );
  }
  if ( _snd_mixer_selem_id != nullptr ) {
    snd_mixer_selem_id_free ( _snd_mixer_selem_id );
  }
}

unsigned int
Elem::elem_index () const
{
  unsigned int res = 0;
  if ( snd_mixer_elem () != nullptr ) {
    res = snd_mixer_selem_get_index ( snd_mixer_elem () );
  }
  return res;
}

const char *
Elem::channel_name ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const
{
  const char * res = nullptr;
  if ( channel_idx_n < _channels[ snd_dir_n ].size () ) {
    res = snd_mixer_selem_channel_name (
        _channels[ snd_dir_n ][ channel_idx_n ] );
  }
  return res;
}

void
Elem::set_display_name ( const QString & name_n )
{
  _display_name = name_n;
}

// State flags

bool
Elem::is_active () const
{
  return ( snd_mixer_selem_is_active ( snd_mixer_elem () ) != 0 );
}

// Joined feature

bool
Elem::volume_joined ( std::size_t snd_dir_n ) const
{
  bool res = false;

  snd_mixer_elem_t * snd_elem = snd_mixer_elem ();
  if ( snd_dir_n == SD_PLAYBACK ) {
    res = ( snd_mixer_selem_has_playback_volume_joined ( snd_elem ) != 0 );
  } else {
    res = ( snd_mixer_selem_has_capture_volume_joined ( snd_elem ) != 0 );
  }

  return res;
}

bool
Elem::switch_joined ( std::size_t snd_dir_n ) const
{
  bool res = false;

  snd_mixer_elem_t * snd_elem = snd_mixer_elem ();
  if ( snd_dir_n == SD_PLAYBACK ) {
    res = ( snd_mixer_selem_has_playback_switch_joined ( snd_elem ) != 0 );
  } else {
    res = ( snd_mixer_selem_has_capture_switch_joined ( snd_elem ) != 0 );
  }

  return res;
}

bool
Elem::enum_joined ( std::size_t snd_dir_n [[maybe_unused]] ) const
{
  return true;
}

unsigned int
Elem::num_volume_channels ( std::size_t snd_dir_n ) const
{
  return _num_volume_channels[ snd_dir_n ];
}

long
Elem::volume ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const
{
  long val ( 0 );

  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );
  if ( snd_dir_n == SD_PLAYBACK ) {
    int err ( snd_mixer_selem_get_playback_volume (
        snd_elem, _channels[ SD_PLAYBACK ][ channel_idx_n ], &val ) );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_get_playback_volume", err );
    }
  } else {
    int err ( snd_mixer_selem_get_capture_volume (
        snd_elem, _channels[ SD_CAPTURE ][ channel_idx_n ], &val ) );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_get_capture_volume", err );
    }
  }

  return val;
}

void
Elem::set_volume ( std::size_t snd_dir_n, int channel_idx_n, long volume_n )
{
  int err;
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_volume " << volume_n << "\n";

  if ( snd_dir_n == SD_PLAYBACK ) {
    err = snd_mixer_selem_set_playback_volume (
        snd_elem, _channels[ SD_PLAYBACK ][ channel_idx_n ], volume_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_playback_volume", err );
    }
  } else {
    err = snd_mixer_selem_set_capture_volume (
        snd_elem, _channels[ SD_CAPTURE ][ channel_idx_n ], volume_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_capture_volume", err );
    }
  }

  value_was_set ();
}

void
Elem::set_volume_all ( std::size_t snd_dir_n, long volume_n )
{
  int err;
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_volume_all " << volume_n << "\n";

  if ( snd_dir_n == SD_PLAYBACK ) {
    err = snd_mixer_selem_set_playback_volume_all ( snd_elem, volume_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_playback_volume_all", err );
    }
  } else {
    err = snd_mixer_selem_set_capture_volume_all ( snd_elem, volume_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_capture_volume_all", err );
    }
  }

  value_was_set ();
}

void
Elem::level_volumes ( std::size_t snd_dir_n )
{
  const long num_volumes ( _num_volume_channels[ snd_dir_n ] );
  if ( volumes_equal ( snd_dir_n ) || ( num_volumes < 2 ) ) {
    return;
  }

  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );
  int ( *vol_func ) (
      snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long * );
  if ( snd_dir_n == SD_PLAYBACK ) {
    vol_func = &snd_mixer_selem_get_playback_volume;
  } else {
    vol_func = &snd_mixer_selem_get_capture_volume;
  }

  long vol_avrg_accu ( 0 );
  long vol_mod_accu ( 0 );

  for ( int ii = 0; ii < num_volumes; ++ii ) {
    long val ( 0 );
    vol_func ( snd_elem, _channels[ snd_dir_n ][ ii ], &val );

    long vol_mod;
    if ( ( val >= num_volumes ) || ( val <= ( -num_volumes ) ) ) {
      const long vol_avrg ( val / num_volumes );
      vol_avrg_accu += vol_avrg;
      vol_mod = val;
      vol_mod -= vol_avrg * ( num_volumes - 1 ); // To prevent an overflow
      vol_mod -= vol_avrg;
    } else {
      vol_mod = val;
    }

    vol_mod_accu += vol_mod;

    if ( ( vol_mod_accu >= num_volumes ) ||
         ( vol_mod_accu <= ( -num_volumes ) ) ) {
      const long vol_avrg ( vol_mod_accu / num_volumes );
      vol_avrg_accu += vol_avrg;
      vol_mod_accu -= ( vol_avrg * num_volumes );
    }
  }

  set_volume_all ( snd_dir_n, vol_avrg_accu );
}

long
Elem::ask_dB_vol ( std::size_t snd_dir_n, long dB_value_n, int dir_n )
{
  long res = 0;
  if ( snd_dir_n == SD_PLAYBACK ) {
    snd_mixer_selem_ask_playback_dB_vol (
        snd_mixer_elem (), dB_value_n, dir_n, &res );
  } else {
    snd_mixer_selem_ask_capture_dB_vol (
        snd_mixer_elem (), dB_value_n, dir_n, &res );
  }
  return res;
}

long
Elem::ask_vol_dB ( std::size_t snd_dir_n, long volume_n )
{
  long res = 0;
  if ( snd_dir_n == SD_PLAYBACK ) {
    snd_mixer_selem_ask_playback_vol_dB ( snd_mixer_elem (), volume_n, &res );
  } else {
    snd_mixer_selem_ask_capture_vol_dB ( snd_mixer_elem (), volume_n, &res );
  }
  return res;
}

long
Elem::ask_dB_vol_nearest ( std::size_t snd_dir_n, long dB_value_n )
{
  long res = 0;

  const long vol_above ( ask_dB_vol ( snd_dir_n, dB_value_n, 1 ) );
  const long vol_below ( ask_dB_vol ( snd_dir_n, dB_value_n, -1 ) );
  long db_above ( ask_vol_dB ( snd_dir_n, vol_above ) );
  long db_below ( ask_vol_dB ( snd_dir_n, vol_below ) );

  db_above = std::abs ( db_above - dB_value_n );
  db_below = std::abs ( dB_value_n - db_below );

  if ( db_above > db_below ) {
    res = vol_below;
  } else {
    res = vol_above;
  }

  return res;
}

long
Elem::dB_value ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const
{
  long val ( 0 );

  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );
  if ( snd_dir_n == SD_PLAYBACK ) {
    int err ( snd_mixer_selem_get_playback_dB (
        snd_elem, _channels[ SD_PLAYBACK ][ channel_idx_n ], &val ) );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_get_playback_dB", err );
    }
  } else {
    int err ( snd_mixer_selem_get_capture_dB (
        snd_elem, _channels[ SD_CAPTURE ][ channel_idx_n ], &val ) );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_get_capture_dB", err );
    }
  }

  return val;
}

void
Elem::set_dB ( std::size_t snd_dir_n,
               int channel_idx_n,
               long dB_val_n,
               int dir_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_dB " << dB_val_n << "\n";

  if ( snd_dir_n == SD_PLAYBACK ) {
    int err = snd_mixer_selem_set_playback_dB (
        snd_elem, _channels[ SD_PLAYBACK ][ channel_idx_n ], dB_val_n, dir_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_playback_dB", err );
    }
  } else {
    int err = snd_mixer_selem_set_capture_dB (
        snd_elem, _channels[ SD_CAPTURE ][ channel_idx_n ], dB_val_n, dir_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_capture_dB", err );
    }
  }

  value_was_set ();
}

void
Elem::set_dB_all ( std::size_t snd_dir_n, long dB_val_n, int dir_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_dB_all " << dB_val_n << "\n";

  if ( snd_dir_n == SD_PLAYBACK ) {
    int err = snd_mixer_selem_set_playback_dB_all ( snd_elem, dB_val_n, dir_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_playback_dB_all", err );
    }
  } else {
    int err = snd_mixer_selem_set_capture_dB_all ( snd_elem, dB_val_n, dir_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_capture_dB_all", err );
    }
  }

  value_was_set ();
}

bool
Elem::switch_state ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const
{
  int val ( 0 );

  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );
  if ( snd_dir_n == SD_PLAYBACK ) {
    int err ( snd_mixer_selem_get_playback_switch (
        snd_elem, _channels[ SD_PLAYBACK ][ channel_idx_n ], &val ) );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_get_playback_switch", err );
    }
  } else {
    int err ( snd_mixer_selem_get_capture_switch (
        snd_elem, _channels[ SD_CAPTURE ][ channel_idx_n ], &val ) );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_get_capture_switch", err );
    }
  }

  return ( val != 0 );
}

void
Elem::set_switch ( std::size_t snd_dir_n, int channel_idx_n, bool state_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_switch " << state_n << "\n";

  if ( snd_dir_n == SD_PLAYBACK ) {
    int err = snd_mixer_selem_set_playback_switch (
        snd_elem, _channels[ SD_PLAYBACK ][ channel_idx_n ], state_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_playback_switch", err );
    }
  } else {
    int err = snd_mixer_selem_set_capture_switch (
        snd_elem, _channels[ SD_CAPTURE ][ channel_idx_n ], state_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_capture_switch", err );
    }
  }

  value_was_set ();
}

void
Elem::set_switch_all ( std::size_t snd_dir_n, bool state_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_switch_all " << state_n << "\n";

  if ( snd_dir_n == SD_PLAYBACK ) {
    int err = snd_mixer_selem_set_playback_switch_all ( snd_elem, state_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_playback_switch_all", err );
    }
  } else {
    int err = snd_mixer_selem_set_capture_switch_all ( snd_elem, state_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_capture_switch_all", err );
    }
  }

  value_was_set ();
}

void
Elem::invert_switches ( std::size_t snd_dir_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  if ( snd_dir_n == SD_PLAYBACK ) {
    for ( std::size_t cii = 0; cii < _num_switch_channels[ SD_PLAYBACK ];
          ++cii ) {
      int err = snd_mixer_selem_set_playback_switch (
          snd_elem,
          _channels[ SD_PLAYBACK ][ cii ],
          !switch_state ( SD_PLAYBACK, cii ) );
      if ( err < 0 ) {
        print_alsa_error ( "snd_mixer_selem_set_playback_switch", err );
      }
    }
  } else {
    for ( std::size_t cii = 0; cii < _num_switch_channels[ SD_CAPTURE ];
          ++cii ) {
      int err = snd_mixer_selem_set_capture_switch (
          snd_elem,
          _channels[ SD_CAPTURE ][ cii ],
          !switch_state ( SD_CAPTURE, cii ) );
      if ( err < 0 ) {
        print_alsa_error ( "snd_mixer_selem_set_capture_switch", err );
      }
    }
  }

  value_was_set ();
}

void
Elem::level_switches ( std::size_t snd_dir_n )
{
  const std::size_t num_switches ( _num_switch_channels[ snd_dir_n ] );
  if ( switches_equal ( snd_dir_n ) || ( num_switches < 2 ) ) {
    return;
  }

  std::size_t num_on ( 0 );
  std::size_t num_off ( 0 );

  {
    snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );
    int ( *state_func ) (
        snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int * );
    if ( snd_dir_n == SD_PLAYBACK ) {
      state_func = &snd_mixer_selem_get_playback_switch;
    } else {
      state_func = &snd_mixer_selem_get_capture_switch;
    }

    for ( std::size_t ii = 0; ii < num_switches; ++ii ) {
      int val ( 0 );
      state_func ( snd_elem, _channels[ snd_dir_n ][ ii ], &val );
      if ( val != 0 ) {
        ++num_on;
      } else {
        ++num_off;
      }
    }
  }

  set_switch_all ( snd_dir_n, ( num_on >= num_off ) );
}

unsigned int
Elem::num_enum_channels ( std::size_t snd_dir_n ) const
{
  return _num_enum_channels[ snd_dir_n ];
}

unsigned int
Elem::enum_index ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const
{
  unsigned int val ( 0 );

  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );
  int err ( snd_mixer_selem_get_enum_item (
      snd_elem, _channels[ snd_dir_n ][ channel_idx_n ], &val ) );
  if ( err < 0 ) {
    print_alsa_error ( "snd_mixer_selem_get_enum_item", err );
  }

  return val;
}

void
Elem::set_enum_index ( std::size_t snd_dir_n,
                       int channel_idx_n,
                       unsigned int index_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_enum_index " << index_n << "\n";

  int err = snd_mixer_selem_set_enum_item (
      snd_elem, _channels[ snd_dir_n ][ channel_idx_n ], index_n );
  if ( err < 0 ) {
    print_alsa_error ( "snd_mixer_selem_set_enum_item", err );
  }

  value_was_set ();
}

void
Elem::set_enum_index_all ( std::size_t snd_dir_n, unsigned int index_n )
{
  snd_mixer_elem_t * snd_elem ( snd_mixer_elem () );

  // std::cout << "Elem::set_enum_index_all " << index_n << "\n";

  for ( std::size_t ii = 0; ii < _num_enum_channels[ snd_dir_n ]; ++ii ) {
    int err = snd_mixer_selem_set_enum_item (
        snd_elem, _channels[ snd_dir_n ][ ii ], index_n );
    if ( err < 0 ) {
      print_alsa_error ( "snd_mixer_selem_set_enum_item", err );
    }
  }

  value_was_set ();
}

void
Elem::value_was_set ()
{
  // Read values back
  update_values_mark ();
  // Call into mixer
  if ( QObject * par = parent (); par != nullptr ) {
    if ( Mixer * mixer = dynamic_cast< Mixer * > ( par ); mixer != nullptr ) {
      mixer->signalize_all_changes_later ();
    }
  }
}

void
Elem::update_values ()
{
  snd_mixer_elem_t * snd_elem = snd_mixer_elem ();
  if ( snd_elem == nullptr ) {
    return;
  }

  // std::cout << "Elem::update_mixer_values" << "\n";

  // Playback volume equality
  if ( _num_volume_channels[ SD_PLAYBACK ] > 1 ) {
    const Channel_Buffer & chs ( _channels[ SD_PLAYBACK ] );
    bool vols_equal ( false );

    long val_first ( 0 );
    int err =
        snd_mixer_selem_get_playback_volume ( snd_elem, chs[ 0 ], &val_first );

    if ( err == 0 ) {
      vols_equal = true;

      const std::size_t num_ch ( _num_volume_channels[ SD_PLAYBACK ] );
      for ( std::size_t ii = 1; ii < num_ch; ++ii ) {
        long val ( 0 );
        err = snd_mixer_selem_get_playback_volume ( snd_elem, chs[ ii ], &val );

        if ( ( err < 0 ) || ( val != val_first ) ) {
          vols_equal = false;
          break;
        }
      }
    }

    _volumes_equal[ SD_PLAYBACK ] = vols_equal;
  }

  // Capture volume equality
  if ( _num_volume_channels[ SD_CAPTURE ] > 1 ) {
    const Channel_Buffer & chs ( _channels[ SD_CAPTURE ] );
    bool vols_equal ( false );

    long val_first ( 0 );
    int err =
        snd_mixer_selem_get_capture_volume ( snd_elem, chs[ 0 ], &val_first );

    if ( err == 0 ) {
      vols_equal = true;

      const std::size_t num_ch ( _num_volume_channels[ SD_CAPTURE ] );
      for ( std::size_t ii = 1; ii < num_ch; ++ii ) {
        long val ( 0 );
        err = snd_mixer_selem_get_capture_volume ( snd_elem, chs[ ii ], &val );

        if ( ( err < 0 ) || ( val != val_first ) ) {
          vols_equal = false;
          break;
        }
      }
    }

    _volumes_equal[ SD_CAPTURE ] = vols_equal;
  }

  // Playback switch equality
  if ( _num_switch_channels[ SD_PLAYBACK ] > 1 ) {
    const Channel_Buffer & chs ( _channels[ SD_PLAYBACK ] );
    bool states_equal ( false );

    int state_first ( 0 );
    int err = snd_mixer_selem_get_playback_switch (
        snd_elem, chs[ 0 ], &state_first );

    if ( err == 0 ) {
      states_equal = true;

      const std::size_t num_ch ( _num_switch_channels[ SD_PLAYBACK ] );
      for ( std::size_t ii = 1; ii < num_ch; ++ii ) {
        int state ( 0 );
        err =
            snd_mixer_selem_get_playback_switch ( snd_elem, chs[ ii ], &state );

        if ( ( err < 0 ) || ( state != state_first ) ) {
          states_equal = false;
          break;
        }
      }
    }

    _switches_equal[ SD_PLAYBACK ] = states_equal;
  }

  // Capture switch equality
  if ( _num_switch_channels[ SD_CAPTURE ] > 1 ) {
    const Channel_Buffer & chs ( _channels[ SD_CAPTURE ] );
    bool states_equal ( false );

    int state_first ( 0 );
    int err ( snd_mixer_selem_get_capture_switch (
        snd_elem, chs[ 0 ], &state_first ) );

    if ( err == 0 ) {
      states_equal = true;

      const std::size_t num_ch ( _num_switch_channels[ SD_CAPTURE ] );
      for ( std::size_t ii = 1; ii < num_ch; ++ii ) {
        int state ( 0 );
        err =
            snd_mixer_selem_get_capture_switch ( snd_elem, chs[ ii ], &state );

        if ( ( err < 0 ) || ( state != state_first ) ) {
          states_equal = false;
          break;
        }
      }
    }

    _switches_equal[ SD_CAPTURE ] = states_equal;
  }

  // Playback enum equality
  if ( _num_enum_channels[ SD_PLAYBACK ] > 1 ) {
    const Channel_Buffer & chs ( _channels[ SD_PLAYBACK ] );
    bool indices_equal ( false );

    unsigned int idx_first ( 0 );
    int err = snd_mixer_selem_get_enum_item ( snd_elem, chs[ 0 ], &idx_first );

    if ( err == 0 ) {
      indices_equal = true;

      const std::size_t num_ch ( _num_enum_channels[ SD_PLAYBACK ] );
      for ( std::size_t ii = 1; ii < num_ch; ++ii ) {
        unsigned int idx ( 0 );
        err = snd_mixer_selem_get_enum_item ( snd_elem, chs[ ii ], &idx );

        if ( ( err < 0 ) || ( idx != idx_first ) ) {
          indices_equal = false;
          break;
        }
      }
    }

    _enums_equal[ SD_PLAYBACK ] = indices_equal;
  }

  // Capture enum equality
  if ( _num_enum_channels[ SD_CAPTURE ] > 1 ) {
    const Channel_Buffer & chs ( _channels[ SD_CAPTURE ] );
    bool indices_equal ( false );

    unsigned int idx_first ( 0 );
    int err = snd_mixer_selem_get_enum_item ( snd_elem, chs[ 0 ], &idx_first );

    if ( err == 0 ) {
      indices_equal = true;

      const std::size_t num_ch ( _num_enum_channels[ SD_CAPTURE ] );
      for ( std::size_t ii = 1; ii < num_ch; ++ii ) {
        unsigned int idx ( 0 );
        err = snd_mixer_selem_get_enum_item ( snd_elem, chs[ ii ], &idx );

        if ( ( err < 0 ) || ( idx != idx_first ) ) {
          indices_equal = false;
          break;
        }
      }
    }

    _enums_equal[ SD_CAPTURE ] = indices_equal;
  }
}

void
Elem::update_values_mark ()
{
  _values_changed = true;
  update_values ();
}

void
Elem::signalize_changes ()
{
  if ( _values_changed ) {
    _values_changed = false;
    Q_EMIT sig_values_changed ();
  }
}

void
Elem::signalize_element_changed ()
{
  // Call into mixer to reload later
  if ( QObject * par = parent (); par != nullptr ) {
    if ( Mixer * mixer = dynamic_cast< Mixer * > ( par ); mixer != nullptr ) {
      mixer->request_reload_later ();
    }
  }
}

// Alsa callbacks

int
Elem::alsa_callback_mixer_elem ( snd_mixer_elem_t * elem_n,
                                 unsigned int mask_n )
{
  int res = 0;

  Elem * smse = nullptr;
  {
    void * priv ( snd_mixer_elem_get_callback_private ( elem_n ) );
    smse = reinterpret_cast< Elem * > ( priv );
  }

  if ( smse != nullptr ) {
    const unsigned int change_mask =
        ( 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 ) ) {
      smse->signalize_element_changed ();
    } else if ( ( mask_n & SND_CTL_EVENT_MASK_VALUE ) != 0 ) {
      smse->update_values_mark ();
    } else {
      // Unusual mask
      {
        std::ostringstream msg;
        msg << "Elem::alsa_callback_mixer_elem: ";
        msg << "Unknown mask ( " << mask_n << " )" << std::endl;
        std::cerr << msg.str ();
      }
      res = -1;
    }
  }

  return res;
}

} // namespace QSnd::Mixer
