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

#pragma once

#include "qsnd/alsa.hpp"
#include "qsnd/mixer/stream.hpp"
#include <QObject>
#include <QString>
#include <vector>

namespace QSnd::Mixer
{

/// @brief Elem
///
/// Connects an ALSA simple mixer element with Qt
///
class Elem : public QObject
{
  Q_OBJECT

  public:
  // -- Public types

  using Channel_Buffer = std::vector< snd_mixer_selem_channel_id_t >;
  using Enum_Names_Buffer = std::vector< QString >;

  // -- Construction

  Elem ( QObject * parent_n = nullptr,
         snd_mixer_t * mixer_n = nullptr,
         snd_mixer_elem_t * mixer_elem_n = nullptr );

  ~Elem ();

  // -- ALSA structs

  snd_mixer_t *
  snd_mixer () const
  {
    return _snd_mixer;
  }

  snd_mixer_elem_t *
  snd_mixer_elem () const
  {
    return _snd_mixer_elem;
  }

  snd_mixer_selem_id_t *
  snd_mixer_selem_id () const
  {
    return _snd_mixer_selem_id;
  }

  // -- Element info

  const QString &
  elem_name () const
  {
    return _elem_name;
  }

  const QString &
  display_name () const
  {
    return _display_name;
  }

  void
  set_display_name ( const QString & name_n );

  unsigned int
  elem_index () const;

  std::size_t
  num_channels ( std::size_t snd_dir_n ) const
  {
    return _channels[ snd_dir_n ].size ();
  }

  snd_mixer_selem_channel_id_t
  channel ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const
  {
    return _channels[ snd_dir_n ][ channel_idx_n ];
  }

  const char *
  channel_name ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const;

  // -- Availability

  bool
  has_volume ( std::size_t snd_dir_n ) const
  {
    return ( _num_volume_channels[ snd_dir_n ] > 0 );
  }

  bool
  has_dB ( std::size_t snd_dir_n ) const
  {
    return ( _dB_min[ snd_dir_n ] != _dB_max[ snd_dir_n ] );
  }

  bool
  has_switch ( std::size_t snd_dir_n ) const
  {
    return ( _num_switch_channels[ snd_dir_n ] > 0 );
  }

  bool
  has_enum ( std::size_t snd_dir_n ) const
  {
    return ( _num_enum_channels[ snd_dir_n ] > 0 );
  }

  // -- State flags

  bool
  is_active () const;

  // -- Joined feature

  bool
  volume_joined ( std::size_t snd_dir_n ) const;

  bool
  switch_joined ( std::size_t snd_dir_n ) const;

  bool
  enum_joined ( std::size_t snd_dir_n ) const;

  // -- Equality

  bool
  volumes_equal ( std::size_t snd_dir_n ) const
  {
    return _volumes_equal[ snd_dir_n ];
  }

  bool
  switches_equal ( std::size_t snd_dir_n ) const
  {
    return _switches_equal[ snd_dir_n ];
  }

  bool
  enums_equal ( std::size_t snd_dir_n ) const
  {
    return _enums_equal[ snd_dir_n ];
  }

  // -- Volume

  unsigned int
  num_volume_channels ( std::size_t snd_dir_n ) const;

  long
  volume ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const;

  long
  volume_min ( std::size_t snd_dir_n ) const
  {
    return _volume_min[ snd_dir_n ];
  }

  long
  volume_max ( std::size_t snd_dir_n ) const
  {
    return _volume_max[ snd_dir_n ];
  }

  // -- Decibel

  long
  dB_value ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const;

  long
  dB_min ( std::size_t snd_dir_n ) const
  {
    return _dB_min[ snd_dir_n ];
  }

  long
  dB_max ( std::size_t snd_dir_n ) const
  {
    return _dB_max[ snd_dir_n ];
  }

  long
  ask_dB_vol ( std::size_t snd_dir_n, long dB_value_n, int dir_n = -1 );

  long
  ask_vol_dB ( std::size_t snd_dir_n, long volume_n );

  long
  ask_dB_vol_nearest ( std::size_t snd_dir_n, long dB_value_n );

  // -- Switch

  unsigned int
  num_switch_channels ( std::size_t snd_dir_n ) const
  {
    return _num_switch_channels[ snd_dir_n ];
  }

  bool
  switch_state ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const;

  // -- Enumerated

  unsigned int
  num_enum_channels ( std::size_t snd_dir_n ) const;

  unsigned int
  enum_index ( std::size_t snd_dir_n, std::size_t channel_idx_n ) const;

  unsigned int
  enum_num_items () const
  {
    return _enum_num_items;
  }

  const Enum_Names_Buffer &
  enum_item_names () const
  {
    return _enum_item_names;
  }

  // -- Volume setting

  void
  set_volume ( std::size_t snd_dir_n, int channel_idx_n, long volume_n );

  void
  set_volume_all ( std::size_t snd_dir_n, long volume_n );

  void
  level_volumes ( std::size_t snd_dir_n );

  // -- Decibel setting

  void
  set_dB ( std::size_t snd_dir_n,
           int channel_idx_n,
           long dB_val_n,
           int dir_n = -1 );

  void
  set_dB_all ( std::size_t snd_dir_n, long dB_val_n, int dir_n = -1 );

  // -- Switch setting

  void
  set_switch ( std::size_t snd_dir_n, int channel_idx_n, bool index_n );

  void
  set_switch_all ( std::size_t snd_dir_n, bool index_n );

  void
  invert_switches ( std::size_t snd_dir_n );

  void
  level_switches ( std::size_t snd_dir_n );

  // -- Enum setting

  void
  set_enum_index ( std::size_t snd_dir_n,
                   int channel_idx_n,
                   unsigned int index_n );

  void
  set_enum_index_all ( std::size_t snd_dir_n, unsigned int index_n );

  /// @brief Reads all values from alsa
  void
  update_values ();

  // -- Callback methods

  /// @brief Reads all values from alsa and marks a change
  void
  update_values_mark ();

  void
  signalize_changes ();

  /// @brief Signalizes the parent that this element changed
  void
  signalize_element_changed ();

  // -- Signals

  /// @brief Gets emitted when values have changed
  Q_SIGNAL
  void
  sig_values_changed ();

  private:
  // -- Utility

  void
  value_was_set ();

  // -- Alsa callbacks

  static int
  alsa_callback_mixer_elem ( snd_mixer_elem_t * elem_n, unsigned int mask_n );

  private:
  // -- Attributes
  snd_mixer_t * _snd_mixer = nullptr;
  snd_mixer_elem_t * _snd_mixer_elem = nullptr;
  snd_mixer_selem_id_t * _snd_mixer_selem_id = nullptr;

  QString _elem_name;
  QString _display_name;

  bool _values_changed = false;

  bool _volumes_equal[ SD_COUNT ];
  bool _switches_equal[ SD_COUNT ];
  bool _enums_equal[ SD_COUNT ];

  unsigned int _num_volume_channels[ SD_COUNT ];
  unsigned int _num_switch_channels[ SD_COUNT ];
  unsigned int _num_enum_channels[ SD_COUNT ];

  Channel_Buffer _channels[ SD_COUNT ];

  long _volume_min[ SD_COUNT ];
  long _volume_max[ SD_COUNT ];

  long _dB_min[ SD_COUNT ];
  long _dB_max[ SD_COUNT ];

  unsigned int _enum_num_items = 0;

  Enum_Names_Buffer _enum_item_names;
};

} // namespace QSnd::Mixer
