#include <stdlib.h>
#include <string.h>
#include <ao/ao.h>

#include "audio.h"
#include "audio_internal.h"

typedef struct _SinkLibaoContext {
    ao_device *device;
    ao_sample_format format;
} SinkLibaoContext;

AudioPipelineStatus sink_libao_push(AudioPipelineElement *self, AudioBuffer *buffer) {
    SinkLibaoContext *context = (SinkLibaoContext *)self->ctx;

    if (context->device == NULL) {
        int default_driver = ao_default_driver_id();
        
        context->device = ao_open_live(default_driver, &context->format, NULL);
        if (context->device == NULL) {
            fprintf(stderr, "Error opening device.\n");
            return PipelineError;
        }
    }

    int result = ao_play(context->device, (char *)buffer->data, buffer->buf_size);
    return (result == 0) ? PipelineError : PipelineRunning;
}

AudioPipelineStatus sink_libao_link(AudioPipelineElement *self, AudioPipelineElement *source) {
    SinkLibaoContext *context = (SinkLibaoContext *)self->ctx;

    if (context->device != NULL) {
        ao_close(context->device);
        context->device = NULL;
    }
    
    memset(&context->format, 0, sizeof(ao_sample_format));
    context->format.bits = source->bits_per_sample;
    context->format.channels = source->channels;
    context->format.rate = source->sample_rate;
    context->format.byte_format = AO_FMT_LITTLE;
    
    source->next = self;

    return PipelineStopped;
}

char *sink_libao_describe(AudioPipelineElement *self) {
    return "libao sink";
}

void sink_libao_destroy(AudioPipelineElement *self) {
    SinkLibaoContext *context = (SinkLibaoContext *)self->ctx;
    
    ao_close(context->device);
    ao_shutdown();
    
    free(self->ctx);
    free(self);
}

AudioPipelineElement *audio_sink_libao(void) {
    AudioPipelineElement *self = calloc(1, sizeof(AudioPipelineElement));
    SinkLibaoContext *context = calloc(1, sizeof(SinkLibaoContext));

    ao_initialize();
    
    self->ctx = context;
    self->describe = sink_libao_describe;
    self->start = sink_nop;
    self->reset = sink_nop;
    self->stop = sink_nop;
    self->push = sink_libao_push;
    self->link = sink_libao_link;
    self->destroy = sink_libao_destroy;
    self->type = AudioElementSink;
    
    return self;
}