#pragma once

#include <assert.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef __GNUC__
#define __inline__ inline
#endif

#include <alsa/asoundef.h>
#include <alsa/version.h>
#include <alsa/global.h>
#include <alsa/input.h>
#include <alsa/output.h>
#include <alsa/error.h>
#include <alsa/conf.h>
#include <alsa/pcm.h>
#include <alsa/rawmidi.h>
#include <alsa/timer.h>
#include <alsa/hwdep.h>
#include <alsa/control.h>
#include <alsa/mixer.h>
#include <alsa/seq_event.h>
#include <alsa/seq.h>
#include <alsa/seqmid.h>
#include <alsa/seq_midi_event.h>

#include <cmath>
#include <functional>
#include <string>

#include "common.hpp"
#include "config.hpp"
#include "errors.hpp"
#include "utils/concurrency.hpp"

#define MAX_LINEAR_DB_SCALE 24

POLYBAR_NS

DEFINE_ERROR(alsa_exception);
DEFINE_CHILD_ERROR(alsa_ctl_interface_error, alsa_exception);
DEFINE_CHILD_ERROR(alsa_mixer_error, alsa_exception);

// class definition : alsa_ctl_interface {{{

template <typename T>
void throw_exception(string&& message, int error_code) {
  const char* snd_error = snd_strerror(error_code);
  if (snd_error != nullptr)
    message += ": " + string{snd_error};
  throw T(message.c_str());
}

class alsa_ctl_interface {
 public:
  explicit alsa_ctl_interface(int numid);
  ~alsa_ctl_interface();

  int get_numid();
  bool wait(int timeout = -1);
  bool test_device_plugged();
  void process_events();

 private:
  std::mutex m_lock;

  int m_numid{0};

  snd_hctl_t* m_hctl{nullptr};
  snd_hctl_elem_t* m_elem{nullptr};

  snd_ctl_t* m_ctl{nullptr};
  snd_ctl_elem_info_t* m_info{nullptr};
  snd_ctl_elem_value_t* m_value{nullptr};
  snd_ctl_elem_id_t* m_id{nullptr};
};

// }}}
// class definition : alsa_mixer {{{

class alsa_mixer {
 public:
  explicit alsa_mixer(string mixer_control_name);
  ~alsa_mixer();

  string get_name();

  bool wait(int timeout = -1);
  int process_events();

  int get_volume();
  int get_normalized_volume();
  void set_volume(float percentage);
  void set_normalized_volume(float percentage);
  void set_mute(bool mode);
  void toggle_mute();
  bool is_muted();

 private:
  std::mutex m_lock;

  string m_name;

  snd_mixer_selem_id_t* m_mixerid{nullptr};
  snd_mixer_t* m_hardwaremixer{nullptr};
  snd_mixer_elem_t* m_mixerelement{nullptr};
};

// }}}

POLYBAR_NS_END