#pragma once

#ifdef USE_ALSA_ADAPTER

#include <alsa/asoundlib.h>
#include <spdlog/spdlog.h>
#include <exception>
#include <stdexcept>
#include <functional>
#include <cstring>
#include <thread>
#include <atomic>

#include "Concepts/AudioAdapterConcept.h"

namespace snoop {

class AlsaAudioAdapter {
    using TAudioCallback = std::function<void( const char* input, size_t size )>;

    snd_pcm_t* m_handle = nullptr;
    TAudioCallback m_audioCallback;
    int m_framesPerBuffer;
    std::thread m_thread;
    std::atomic<bool> m_running;

public:
    AlsaAudioAdapter( int sampleRate, int channels, int framesPerBuffer, TAudioCallback audioCallback, const std::string& deviceName = "default" )
        : m_audioCallback( std::move( audioCallback ) ), m_framesPerBuffer( framesPerBuffer ), m_running( true ) {

        int err;

        if( ( err = snd_pcm_open( &m_handle, deviceName.c_str(), SND_PCM_STREAM_CAPTURE, 0 ) ) < 0 ) {
            spdlog::error( "ALSA error while opening device: {}", snd_strerror( err ) );
            throw std::runtime_error( std::string( "ALSA error while opening device: " ) + snd_strerror( err ) );
        }

        spdlog::info( "ALSA device opened successfully: {}", deviceName );

        snd_pcm_hw_params_t* hw_params;
        snd_pcm_hw_params_malloc( &hw_params );
        snd_pcm_hw_params_any( m_handle, hw_params );
        snd_pcm_hw_params_set_access( m_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED );
        snd_pcm_hw_params_set_format( m_handle, hw_params, SND_PCM_FORMAT_FLOAT_LE );
        snd_pcm_hw_params_set_rate( m_handle, hw_params, sampleRate, 0 );
        snd_pcm_hw_params_set_channels( m_handle, hw_params, channels );
        snd_pcm_hw_params_set_period_size( m_handle, hw_params, framesPerBuffer, 0 );
        snd_pcm_hw_params_set_buffer_size( m_handle, hw_params, framesPerBuffer * 4 );

        if( ( err = snd_pcm_hw_params( m_handle, hw_params ) ) < 0 ) {
            spdlog::error( "ALSA error while setting hardware parameters: {}", snd_strerror( err ) );
            snd_pcm_hw_params_free( hw_params );
            throw std::runtime_error( std::string( "ALSA error while setting hardware parameters: " ) + snd_strerror( err ) );
        }

        snd_pcm_hw_params_free( hw_params );
        spdlog::info( "ALSA hardware parameters set successfully." );

        if( ( err = snd_pcm_prepare( m_handle ) ) < 0 ) {
            spdlog::error( "ALSA error while preparing device: {}", snd_strerror( err ) );
            throw std::runtime_error( std::string( "ALSA error while preparing device: " ) + snd_strerror( err ) );
        }

        spdlog::info( "ALSA device prepared successfully." );

        m_thread = std::thread( &AlsaAudioAdapter::start, this );
    }

    ~AlsaAudioAdapter() {
        m_running = false;
        if( m_thread.joinable() ) {
            m_thread.join();
        }

        snd_pcm_close( m_handle );
        spdlog::info( "ALSA device closed." );
    }

private:
    void start() {
        int err;
        std::vector<float> buffer( m_framesPerBuffer );

        while( m_running ) {
            err = snd_pcm_readi( m_handle, buffer.data(), m_framesPerBuffer );
            if( err == -EPIPE ) {
                spdlog::warn( "ALSA buffer overrun detected!" );
                snd_pcm_prepare( m_handle );
            } else if( err < 0 ) {
                spdlog::error( "ALSA error while reading: {}", snd_strerror( err ) );
            } else {
                m_audioCallback( reinterpret_cast<const char*>(buffer.data()), err * sizeof( float ) );
            }
        }
    }
};

static_assert( AudioAdapterConcept<AlsaAudioAdapter>, "AlsaAudioAdapter does not satisfy AudioAdapterConcept" );

}

#endif