#include <pipewire/pipewire.h>
#include "../../../l1/core/util.h"
#include <spa/param/audio/format-utils.h>
#include <math.h>
#include <stdio.h>
#include "../../../l1/system/pthread.h"
#include "../../margaret/time_utils.h"

// todo: delete this file

#define DEFAULT_RATE 44100
#define DEFAULT_CHANNELS 2
#define DEFAULT_VOLUME 0.1

typedef struct {
    /* self (takes ownership) */
    void (*drop)(void*);
    /* self, returns: pcm value */
    double (*next)(void*);
    /* self, returns: duration in frames */
    size_t (*get_duration)(const void*);
} LizaSound_Table;

typedef struct {
    const void* r;
    const LizaSound_Table* t;
} RefLizaSound;

typedef struct {
    void* r;
    const LizaSound_Table* t;
} MutRefLizaSound;

/* Existence of Box<Trait> type implies that _drop method is virtual according to this trait  */
typedef struct {
    void* m; /* Owns memory block r and object in it */
    const LizaSound_Table* t;
} BoxLizaSound;


typedef struct {
    double* buffer;
    int buffer_size;
    int i;
    double decay;
    double planned_time;
} StringTwitchSound;

StringTwitchSound StringTwitchSound_new(double frequency, double decay_time) {
    int buffer_size = (int)(DEFAULT_RATE / frequency);
    double* buffer = safe_calloc(buffer_size, sizeof(double));
    for (int i = 0; i < buffer_size; i++) {
        /* random noise in [-1, 1] */
        buffer[i] = ((double)rand() / RAND_MAX) * 2.0 - 1.0;
    }
    double decay_factor = pow(0.01, 1 / (decay_time * frequency));
    return (StringTwitchSound){.buffer = buffer, .buffer_size = buffer_size, .i = 0,
        .decay = decay_factor, .planned_time = decay_time};
}

void StringTwitchSound_drop(StringTwitchSound self) {
    free(self.buffer);
}

double StringTwitchSound_next(StringTwitchSound* self) {
    int j = (self->i + 1) % self->buffer_size;
    double A = self->buffer[self->i];
    double B = self->buffer[j];
    self->buffer[self->i] = self->decay * 0.5 * (A + B);
    self->i = j;
    return A;
}

size_t StringTwitchSound_get_duration(const StringTwitchSound* self) {
    return (size_t)ceil(self->planned_time * DEFAULT_RATE);
}

void LizaSound_Table_StringTwitchSound_drop(void* g) {
    StringTwitchSound_drop(*(StringTwitchSound*)g);
}
double LizaSound_Table_StringTwitchSound_next(void* g) {
    return StringTwitchSound_next(g);
}
size_t LizaSound_Table_StringTwitchSound_get_duration(const void* g) {
    return StringTwitchSound_get_duration(g);
}
const LizaSound_Table LizaSound_Table_StringTwitchSound = {
    .drop = LizaSound_Table_StringTwitchSound_drop,
    .next = LizaSound_Table_StringTwitchSound_next,
    .get_duration = LizaSound_Table_StringTwitchSound_get_duration
};

BoxLizaSound BoxLizaSound_from_StringTwitchSound(StringTwitchSound obj) {
    void* mem = safe_malloc(sizeof(StringTwitchSound));
    memcpy(mem, &obj, sizeof(StringTwitchSound));
    return (BoxLizaSound){.m = mem, .t = &LizaSound_Table_StringTwitchSound};
}

void BoxLizaSound_drop(BoxLizaSound self) {
    self.t->drop(self.m);
    free(self.m);
}

#include "../../../../gen/l2/eve/r2/VecBoxLizaSound.h"

typedef struct {
    bool stop;
    VecBoxLizaSound new_sounds;
} AudioThreadCommands;

void AudioThreadCommands_drop(AudioThreadCommands self) {
    VecBoxLizaSound_drop(self.new_sounds);
}

/* Not movable and even so more it isn't trivially movable */
typedef struct {
    pthread_mutex_t mut;
    AudioThreadCommands cmd;
} AudioThreadBridge;

/* non-copyable trivially movable (because of BoxLizaSound field) */
typedef struct {
    BoxLizaSound gen;
    size_t remaining_frames;
} PlayingSound;

void PlayingSound_drop(PlayingSound self) {
    BoxLizaSound_drop(self.gen);
}

#include "../../../../gen/l2/eve/r2/PlayingSound.h"

double fix_loud_sound(double a) {
    return a;
    const double L = 0.5;
    const double b = (1 - L) / exp(-L / (1 - L));
    const double d = 1 / (1 - L);
    if (a < -L) {
        printf("Whhohps: %lf\n", a);
        return - (1 - d * exp(b * a));
    } else if (a < L) {
        return a;
    } else {
        return 1 - d * exp(-b * a);
    }
}

typedef struct {
    struct pw_main_loop *pw_main_loop;
    struct pw_context *pw_context;
    struct pw_core *pw_core;
    struct pw_registry *pw_registry;
    struct spa_hook registry_listener_hook;
    struct pw_stream *pw_stream;
    struct spa_hook stream_listener_hook;
    AudioThreadBridge* bridge;

    VecPlayingSound playing_sounds;
    double time_written;
} audio_thread_state_t;

static void main_h_pw_stream_process(void *ug){
    audio_thread_state_t* state = ug;
    safe_pthread_mutex_lock(&state->bridge->mut);
    AudioThreadCommands* incoming_cmd = &state->bridge->cmd;
    if (incoming_cmd->stop) {
        pw_main_loop_quit(state->pw_main_loop);
        safe_pthread_mutex_unlock(&state->bridge->mut);
        return;
    }
    for (size_t i = 0; i < incoming_cmd->new_sounds.len; i++) {
        /* effectively moved out (will resize incoming_cmd->new_sounds to 0 soon) */
        BoxLizaSound snd = *VecBoxLizaSound_at(&incoming_cmd->new_sounds, i);
        VecPlayingSound_append(&state->playing_sounds, (PlayingSound){
            .gen = snd, .remaining_frames = snd.t->get_duration(snd.m)
        });
        printf("Appended note when wrote: %lf\n", state->time_written);
    }
    incoming_cmd->new_sounds.len = 0; /* We moved everything out */
    safe_pthread_mutex_unlock(&state->bridge->mut);

    struct pw_buffer *b = pw_stream_dequeue_buffer(state->pw_stream);
    if (!b) {
        pw_log_warn("out of buffers: %m");
        return;
    }
    struct spa_buffer *buf = b->buffer;
    uint32_t stride = sizeof(int16_t) * DEFAULT_CHANNELS;
    uint32_t n_frames = buf->datas[0].maxsize / stride;
    if (b->requested)
        n_frames = SPA_MIN(b->requested, n_frames);

    int16_t *dst = buf->datas[0].data;
    if (dst == NULL)
        return;

    for (uint32_t f = 0; f < n_frames; f++) {
        double a = 0;
        for (size_t i = 0; i < state->playing_sounds.len;) {
            PlayingSound* snd = VecPlayingSound_mat(&state->playing_sounds, i);
            if (snd->remaining_frames == 0) {
                /* snd invalidated, but iteration continues */
                VecPlayingSound_unordered_pop_and_drop(&state->playing_sounds, i);
                continue;
            }
            a += snd->gen.t->next(snd->gen.m);
            snd->remaining_frames--;
            i++;
        }
        int16_t val = (int16_t)round(fix_loud_sound(a * DEFAULT_VOLUME) * 32767.0);
        for (int c = 0; c < DEFAULT_CHANNELS; c++)
            *dst++ = val;
    }
    state->time_written += (double)n_frames / DEFAULT_RATE;


    buf->datas[0].chunk->offset = 0;
    buf->datas[0].chunk->stride = (int32_t)stride;
    buf->datas[0].chunk->size = n_frames * stride;

    pw_stream_queue_buffer(state->pw_stream, b);
    // printf("Time written = %lf\n", state->time_written);
}

const static struct pw_stream_events main_h_pw_stream_listener = {
    PW_VERSION_STREAM_EVENTS,
    .process = main_h_pw_stream_process
};

void AudioThreadBridge_init(AudioThreadBridge* place) {
    safe_pthread_mutex_init(&place->mut, NULL);
    memset(&place->cmd, 0, sizeof(AudioThreadCommands));
}

void AudioThreadBridge_destroy(AudioThreadBridge* resid) {
    AudioThreadCommands_drop(resid->cmd);
    pthread_mutex_destroy(&resid->mut);
}

/* Full of audio */
void* audio_thread(void* ug) {
    audio_thread_state_t state = { .playing_sounds = VecPlayingSound_new(), .bridge = ug };

    state.pw_main_loop = pw_main_loop_new(NULL /* properties */);
    if (!state.pw_main_loop)
        abortf("pw_main_loop_new");
    state.pw_context = pw_context_new(pw_main_loop_get_loop(state.pw_main_loop), NULL, 0);
    if (!state.pw_context)
        abortf("pw_context_new\n");
    state.pw_core = pw_context_connect(state.pw_context, NULL /* properties */, 0 /* user_data size */);
    if (!state.pw_core)
        abortf("pw_context_connect\n");
    state.pw_registry = pw_core_get_registry(state.pw_core, PW_VERSION_REGISTRY, 0 /* user_data size */);
    if (!state.pw_registry)
        abortf("pw_core_get_registry\n");

    state.pw_stream = pw_stream_new(state.pw_core, "audio-src", pw_properties_new(
        PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL));

    struct spa_hook stream_listener_hook;
    pw_stream_add_listener(state.pw_stream, &stream_listener_hook, &main_h_pw_stream_listener, &state);

    const struct spa_pod *stream_params[1];
    uint8_t pod_buffer[1024];
    struct spa_pod_builder stream_params_b = SPA_POD_BUILDER_INIT(pod_buffer, sizeof(pod_buffer));
    stream_params[0] = spa_format_audio_raw_build(&stream_params_b, SPA_PARAM_EnumFormat,
                        &SPA_AUDIO_INFO_RAW_INIT(
                                .format = SPA_AUDIO_FORMAT_S16,
                                .channels = DEFAULT_CHANNELS,
                                .rate = DEFAULT_RATE ));

    pw_stream_connect(state.pw_stream,
                  PW_DIRECTION_OUTPUT,
                  PW_ID_ANY,
                  PW_STREAM_FLAG_AUTOCONNECT |
                  PW_STREAM_FLAG_MAP_BUFFERS |
                  PW_STREAM_FLAG_RT_PROCESS,
                  stream_params, 1);

    pw_main_loop_run(state.pw_main_loop);
    printf("Exited mainloop\n");

    pw_stream_destroy(state.pw_stream);
    pw_proxy_destroy((struct pw_proxy*)state.pw_registry);
    pw_core_disconnect(state.pw_core);
    pw_context_destroy(state.pw_context);
    pw_main_loop_destroy(state.pw_main_loop);

    VecPlayingSound_drop(state.playing_sounds);
    return NULL;
}

int main() {
    pw_init(NULL, NULL);

    AudioThreadBridge bridge;
    AudioThreadBridge_init(&bridge);
    pthread_t th;
    margaret_ns_time start = margaret_clock_gettime_monotonic_raw();
    if (pthread_create(&th, NULL, audio_thread, &bridge))
        abortf("pthread_create\n");
    printf("Press random key to get help\n");
    while (true) {
        char line[50];
        if (!fgets(line, sizeof(line), stdin))
            break;
        line[strcspn(line, "\n")] = '\0';
        if (strcmp(line, "1") == 0) {
            safe_pthread_mutex_lock(&bridge.mut);
            VecBoxLizaSound_append(&bridge.cmd.new_sounds, BoxLizaSound_from_StringTwitchSound(StringTwitchSound_new(440, 0.9)));
            printf("Pressed 1 at %f\n", margaret_ns_time_sec_diff(start, margaret_clock_gettime_monotonic_raw()));
            safe_pthread_mutex_unlock(&bridge.mut);
        } else if (strcmp(line, "2") == 0) {
            safe_pthread_mutex_lock(&bridge.mut);
            VecBoxLizaSound_append(&bridge.cmd.new_sounds, BoxLizaSound_from_StringTwitchSound(StringTwitchSound_new(440, 9)));
            safe_pthread_mutex_unlock(&bridge.mut);
        } else if (strcmp(line, "q") == 0) {
            safe_pthread_mutex_lock(&bridge.mut);
            bridge.cmd.stop = true;
            safe_pthread_mutex_unlock(&bridge.mut);
            break;
        } else {
            printf("?\n");
        }
    }

    safe_pthread_join(th);
    printf("The End!\n");
    AudioThreadBridge_destroy(&bridge);

    pw_deinit();
    return 0;
}
