#include "input/pulse.h"
#include "input/common.h"

#include <pulse/error.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>

pa_mainloop *m_pulseaudio_mainloop;

void cb(__attribute__((unused)) pa_context *pulseaudio_context, const pa_server_info *i,
        void *userdata) {

    // getting default sink name
    struct audio_data *audio = (struct audio_data *)userdata;
    pthread_mutex_lock(&audio->lock);
    free(audio->source);
    audio->source = malloc(sizeof(char) * 1024);

    strcpy(audio->source, i->default_sink_name);

    // appending .monitor suufix
    audio->source = strcat(audio->source, ".monitor");
    pthread_mutex_unlock(&audio->lock);

    // quitting mainloop
    pa_context_disconnect(pulseaudio_context);
    pa_context_unref(pulseaudio_context);
    pa_mainloop_quit(m_pulseaudio_mainloop, 0);
}

void pulseaudio_context_state_callback(pa_context *pulseaudio_context, void *userdata) {

    // make sure loop is ready
    switch (pa_context_get_state(pulseaudio_context)) {
    case PA_CONTEXT_UNCONNECTED:
        // printf("UNCONNECTED\n");
        break;
    case PA_CONTEXT_CONNECTING:
        // printf("CONNECTING\n");
        break;
    case PA_CONTEXT_AUTHORIZING:
        // printf("AUTHORIZING\n");
        break;
    case PA_CONTEXT_SETTING_NAME:
        // printf("SETTING_NAME\n");
        break;
    case PA_CONTEXT_READY: // extract default sink name
        // printf("READY\n");
        pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, userdata));
        break;
    case PA_CONTEXT_FAILED:
        fprintf(stderr, "failed to connect to pulseaudio server\n");
        exit(EXIT_FAILURE);
        break;
    case PA_CONTEXT_TERMINATED:
        // printf("TERMINATED\n");
        pa_mainloop_quit(m_pulseaudio_mainloop, 0);
        break;
    }
}

void getPulseDefaultSink(void *data) {

    struct audio_data *audio = (struct audio_data *)data;
    pa_mainloop_api *mainloop_api;
    pa_context *pulseaudio_context;
    int ret;

    // Create a mainloop API and connection to the default server
    m_pulseaudio_mainloop = pa_mainloop_new();

    mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop);
    pulseaudio_context = pa_context_new(mainloop_api, "cava device list");

    // This function connects to the pulse server
    pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOFLAGS, NULL);

    //        printf("connecting to server\n");

    // This function defines a callback so the server will tell us its state.
    pa_context_set_state_callback(pulseaudio_context, pulseaudio_context_state_callback,
                                  (void *)audio);

    // starting a mainloop to get default sink

    // starting with one nonblokng iteration in case pulseaudio is not able to run
    if (!(ret = pa_mainloop_iterate(m_pulseaudio_mainloop, 0, &ret))) {
        fprintf(stderr,
                "Could not open pulseaudio mainloop to "
                "find default device name: %d\n"
                "check if pulseaudio is running\n",
                ret);

        exit(EXIT_FAILURE);
    }

    pa_mainloop_run(m_pulseaudio_mainloop, &ret);
    pa_mainloop_free(m_pulseaudio_mainloop);
}

void *input_pulse(void *data) {

    struct audio_data *audio = (struct audio_data *)data;
    uint16_t buffer_size = audio->input_buffer_size * audio->format / 8;
    unsigned char buf[buffer_size];

    /* The sample type to use */
    static const pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2};

    pa_buffer_attr pb = {.maxlength = (uint32_t)-1, // BUFSIZE * 2,
                         .fragsize = buffer_size};

    pa_simple *s = NULL;
    int error;

    if (!(s = pa_simple_new(NULL, "cava", PA_STREAM_RECORD, audio->source, "audio for cava", &ss,
                            NULL, &pb, &error))) {
        sprintf(audio->error_message, __FILE__ ": Could not open pulseaudio source: %s, %s. \
		To find a list of your pulseaudio sources run 'pacmd list-sources'\n",
                audio->source, pa_strerror(error));

        audio->terminate = 1;
        pthread_exit(NULL);
        return 0;
    }

    while (!audio->terminate) {
        if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
            sprintf(audio->error_message, __FILE__ ": pa_simple_read() failed: %s\n",
                    pa_strerror(error));
            audio->terminate = 1;
        }

        write_to_cava_input_buffers(audio->input_buffer_size, buf, data);
    }

    pa_simple_free(s);
    pthread_exit(NULL);
    return 0;
}
