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

#pragma once

#include "qsnd/alsa.hpp"
#include "qsnd/hctl/info_db.hpp"
#include <QObject>
#include <QString>
#include <vector>

namespace QSnd::HCtl
{

/// @brief HCtl Elem
///
/// Qt binding for ALSA's hctl element interface
class Elem : public QObject
{
  Q_OBJECT

  public:
  // -- Public types

  using Enum_Names_Buffer = std::vector< QString >;

  public:
  // -- Construction

  Elem ( const Info_Db & info_db_n,
         QObject * parent_n = nullptr,
         snd_hctl_t * hctl_n = nullptr,
         snd_hctl_elem_t * hctl_elem_n = nullptr );

  ~Elem ();

  // -- ALSA structs

  snd_hctl_t *
  snd_hctl () const
  {
    return _snd_hctl;
  }

  snd_hctl_elem_t *
  snd_hctl_elem () const
  {
    return _snd_hctl_elem;
  }

  const snd_ctl_elem_info_t *
  snd_ctl_info () const
  {
    return _snd_ctl_elem_info;
  }

  // -- Display name

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

  void
  set_display_name ( const QString & name_n );

  // -- Element attributes

  const char *
  elem_name () const
  {
    return snd_ctl_elem_info_get_name ( _snd_ctl_elem_info );
  }

  unsigned int
  elem_numid () const
  {
    return snd_ctl_elem_info_get_numid ( _snd_ctl_elem_info );
  }

  unsigned int
  elem_index () const
  {
    return snd_ctl_elem_info_get_index ( _snd_ctl_elem_info );
  }

  unsigned int
  device () const
  {
    return snd_ctl_elem_info_get_device ( _snd_ctl_elem_info );
  }

  unsigned int
  subdevice () const
  {
    return snd_ctl_elem_info_get_subdevice ( _snd_ctl_elem_info );
  }

  snd_ctl_elem_iface_t
  iface () const
  {
    return snd_ctl_elem_info_get_interface ( _snd_ctl_elem_info );
  }

  unsigned int
  iface_type_idx () const
  {
    return _info_db.iface_type_idx ( iface () );
  }

  const QString &
  iface_name () const
  {
    return _info_db.iface_name ( iface () );
  }

  const QString &
  iface_display_name () const
  {
    return _info_db.iface_display_name ( iface () );
  }

  unsigned int
  count () const
  {
    return snd_ctl_elem_info_get_count ( _snd_ctl_elem_info );
  }

  // -- Type

  snd_ctl_elem_type_t
  elem_type () const
  {
    return snd_ctl_elem_info_get_type ( _snd_ctl_elem_info );
  }

  const QString &
  elem_type_name () const
  {
    return _info_db.elem_type_name ( elem_type () );
  }

  const QString &
  elem_type_display_name () const
  {
    return _info_db.elem_type_display_name ( elem_type () );
  }

  bool
  is_boolean () const
  {
    return ( elem_type () == SND_CTL_ELEM_TYPE_BOOLEAN );
  }

  bool
  is_integer () const
  {
    return ( elem_type () == SND_CTL_ELEM_TYPE_INTEGER );
  }

  bool
  is_enumerated () const
  {
    return ( elem_type () == SND_CTL_ELEM_TYPE_ENUMERATED );
  }

  bool
  is_bytes () const
  {
    return ( elem_type () == SND_CTL_ELEM_TYPE_BYTES );
  }

  bool
  is_IEC958 () const
  {
    return ( elem_type () == SND_CTL_ELEM_TYPE_IEC958 );
  }

  bool
  is_integer64 () const
  {
    return ( elem_type () == SND_CTL_ELEM_TYPE_INTEGER64 );
  }

  // -- State flags

  bool
  is_volatile () const
  {
    return snd_ctl_elem_info_is_volatile ( _snd_ctl_elem_info );
  }

  bool
  is_readable () const
  {
    return snd_ctl_elem_info_is_readable ( _snd_ctl_elem_info );
  }

  bool
  is_writable () const
  {
    return snd_ctl_elem_info_is_writable ( _snd_ctl_elem_info );
  }

  bool
  is_active () const
  {
    return ( snd_ctl_elem_info_is_inactive ( _snd_ctl_elem_info ) == 0 );
  }

  // -- Boolean type

  bool
  switch_state ( unsigned int idx_n ) const
  {
    return snd_ctl_elem_value_get_boolean ( _snd_ctl_elem_value, idx_n );
  }

  bool
  switches_equal () const;

  void
  set_switch_state ( unsigned int idx_n, bool state_n );

  void
  set_switch_all ( bool state_n );

  void
  invert_switch_state ( unsigned int idx_n );

  void
  invert_switch_all ();

  void
  level_switches ();

  // -- Integer type

  long
  integer_min () const
  {
    return snd_ctl_elem_info_get_min ( _snd_ctl_elem_info );
  }

  long
  integer_max () const
  {
    return snd_ctl_elem_info_get_max ( _snd_ctl_elem_info );
  }

  long
  integer_value ( unsigned int idx_n ) const
  {
    return snd_ctl_elem_value_get_integer ( _snd_ctl_elem_value, idx_n );
  }

  bool
  integers_equal () const;

  void
  set_integer ( unsigned int idx_n, long value_n );

  void
  set_integer_all ( long value_n );

  void
  level_integers ();

  // -- Decibel

  bool
  has_dB () const;

  /// @return < 0 on error
  int
  dB_range ( long * min_n, long * max_n ) const;

  long
  dB_value ( unsigned int idx_n ) const;

  long
  ask_dB_from_int ( long int_value_n ) const;

  long
  ask_int_from_dB ( long value_n, int round_dir_n = 0 ) const;

  // -- Enum type

  unsigned int
  enum_num_items () const
  {
    return snd_ctl_elem_info_get_items ( _snd_ctl_elem_info );
  }

  const char *
  enum_item_name ( unsigned int enum_idx_n ) const
  {
    snd_ctl_elem_info_set_item ( _snd_ctl_elem_info, enum_idx_n );
    snd_hctl_elem_info ( snd_hctl_elem (), _snd_ctl_elem_info );
    return snd_ctl_elem_info_get_item_name ( _snd_ctl_elem_info );
  }

  QString
  enum_item_display_name ( unsigned int enum_idx_n ) const;

  unsigned int
  enum_index ( unsigned int channel_idx_n ) const
  {
    return snd_ctl_elem_value_get_enumerated ( _snd_ctl_elem_value,
                                               channel_idx_n );
  }

  bool
  enum_idices_equal () const;

  void
  set_enum_index ( unsigned int channel_idx_n, unsigned int enum_idx_n );

  void
  set_enum_index_all ( unsigned int enum_idx_n );

  void
  level_enums ();

  // -- General

  bool
  values_equal () const;

  void
  level_values ();

  bool
  values_changed () const
  {
    return _values_changed;
  }

  /// @brief Reads all value from alsa
  void
  update_value ();

  // -- Callback methods

  /// @brief Reads all value from alsa and flags a change
  void
  update_value_mark ();

  void
  signalize_changes ();

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

  // -- Alsa callbacks

  static int
  alsa_callback_hctl_elem ( snd_hctl_elem_t * elem_n, unsigned int mask_n );

  // -- Signals

  /// @brief Gets emitted when a value has changed
  Q_SIGNAL
  void
  sig_values_changed ();

  protected:
  // -- Protected methods

  void
  value_was_set ();

  private:
  // -- Attributes
  const Info_Db & _info_db;

  snd_hctl_t * _snd_hctl = nullptr;
  snd_hctl_elem_t * _snd_hctl_elem = nullptr;
  snd_ctl_elem_info_t * _snd_ctl_elem_info = nullptr;
  snd_ctl_elem_value_t * _snd_ctl_elem_value = nullptr;

  bool _values_changed = false;

  QString _display_name;
  Enum_Names_Buffer _enum_item_names;
};

} // namespace QSnd::HCtl
